diff --git a/app/src/components/VideoEditMenu.js b/app/src/components/VideoEditMenu.js
index 8a231e5d..b84ed5fa 100644
--- a/app/src/components/VideoEditMenu.js
+++ b/app/src/components/VideoEditMenu.js
@@ -285,6 +285,7 @@ export function TranscodeModal({open, onClose, fileGroupId, video, onComplete})
* refresh; a re-download of the Video's metadata will overwrite it.
*/
export function EditVideoModal({open, onClose, fileGroupId, videoFile, description: currentDescription, onSaved}) {
+ const wrolModeEnabled = useWROLMode();
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [saving, setSaving] = useState(false);
@@ -345,6 +346,9 @@ export function EditVideoModal({open, onClose, fileGroupId, videoFile, descripti
Saved to the video's info json. Refreshing the video from its source will replace it.
+ {wrolModeEnabled &&
+ Videos cannot be edited while WROL Mode is enabled.
+ }
@@ -354,7 +358,7 @@ export function EditVideoModal({open, onClose, fileGroupId, videoFile, descripti
icon='save'
onClick={handleSave}
loading={saving}
- disabled={saving || !trimmed || unchanged}
+ disabled={saving || !trimmed || unchanged || !!wrolModeEnabled}
>
Save
diff --git a/app/src/components/VideoEditMenu.test.js b/app/src/components/VideoEditMenu.test.js
index 6285c230..a7444189 100644
--- a/app/src/components/VideoEditMenu.test.js
+++ b/app/src/components/VideoEditMenu.test.js
@@ -160,6 +160,16 @@ describe('EditVideoModal', () => {
await waitFor(() => expect(updateVideo).toHaveBeenCalledWith(7, {description: ''}));
});
+ test('WROL Mode disables saving', async () => {
+ renderWithProviders(
+ ,
+ wrolMode,
+ );
+ fireEvent.change(screen.getByLabelText('Title'), {target: {value: 'Changed'}});
+ expect(screen.getByRole('button', {name: /Save/})).toBeDisabled();
+ expect(screen.getByText(/WROL Mode/)).toBeInTheDocument();
+ });
+
test('an empty title cannot be saved', () => {
renderWithProviders();
fireEvent.change(screen.getByLabelText('Title'), {target: {value: ' '}});
diff --git a/modules/videos/test/test_transcode_job.py b/modules/videos/test/test_transcode_job.py
index da433b35..0486189b 100644
--- a/modules/videos/test/test_transcode_job.py
+++ b/modules/videos/test/test_transcode_job.py
@@ -164,3 +164,26 @@ async def test_video_transcode_api(async_client, test_session, video_factory, wr
content=json.dumps({'video_codec': 'h264'}))
assert response.status == 403
await wrol_mode_fixture(False)
+
+
+@pytest.mark.asyncio
+async def test_transcode_video_job_wrol_mode(test_session, test_directory, async_client, video_factory,
+ wrol_mode_fixture):
+ """A transcode queued before WROL Mode was enabled must not run: the Job fails, ffmpeg is never
+ started, and the file is untouched."""
+ video = video_factory()
+ video_path = video.video_path
+ before = video_path.read_bytes()
+
+ job_id = transcode_video_job.enqueue(file_group_id=video.file_group_id, video_codec='h264')
+ await wrol_mode_fixture(True)
+ try:
+ with mock.patch('wrolpi.cmd.run_command') as mock_run:
+ record = await jobs.wait_for_job(job_id)
+ finally:
+ await wrol_mode_fixture(False)
+
+ assert record['status'] == jobs.FAILED, record
+ assert 'WROL Mode' in record['error']
+ mock_run.assert_not_called()
+ assert video_path.read_bytes() == before
diff --git a/modules/videos/test/test_video_edit.py b/modules/videos/test/test_video_edit.py
index 9c481d38..9a095a45 100644
--- a/modules/videos/test/test_video_edit.py
+++ b/modules/videos/test/test_video_edit.py
@@ -115,3 +115,21 @@ async def test_video_update_api(async_client, test_session, video_factory, wrol_
content=json.dumps({'title': 'Nope'}))
assert response.status == 403
await wrol_mode_fixture(False)
+
+
+@pytest.mark.asyncio
+async def test_update_video_wrol_mode(test_session, video_factory, wrol_mode_fixture):
+ """The library function refuses in WROL Mode, not only the API route, so no caller can edit."""
+ from wrolpi.errors import WROLModeEnabled
+ video = video_factory(with_info_json={'title': 'Old'})
+ test_session.commit()
+
+ await wrol_mode_fixture(True)
+ try:
+ with pytest.raises(WROLModeEnabled):
+ update_video(video.file_group_id, title='Nope')
+ finally:
+ await wrol_mode_fixture(False)
+
+ test_session.expire_all()
+ assert Video.find_by_file_group_id(test_session, video.file_group_id).file_group.title == 'Old'
diff --git a/modules/videos/transcode.py b/modules/videos/transcode.py
index 016c2d9b..1f01eeec 100644
--- a/modules/videos/transcode.py
+++ b/modules/videos/transcode.py
@@ -6,7 +6,7 @@ from typing import List, Optional, Tuple, Callable
from wrolpi.captions import FFMPEG_BIN
from wrolpi.cmd import run_command
-from wrolpi.common import logger, get_relative_to_media_directory
+from wrolpi.common import logger, get_relative_to_media_directory, wrol_mode_enabled
from wrolpi.db import get_db_session
from wrolpi.jobs import register_job, get_current_job
from wrolpi.vars import DEFAULT_FILE_PERMISSIONS, PYTEST
@@ -269,6 +269,10 @@ async def transcode_video_job(file_group_id: int, video_codec: Optional[str] = N
from wrolpi.files.lib import get_mimetype
from wrolpi.files.worker import file_worker
+ # The API refuses to queue in WROL Mode; this catches a Job queued before it was enabled.
+ if wrol_mode_enabled():
+ raise RuntimeError('Cannot transcode while WROL Mode is enabled')
+
validate_transcode_request(video_codec, audio_codec, container)
with get_db_session() as session:
diff --git a/modules/videos/video/lib.py b/modules/videos/video/lib.py
index b2ee1d69..cd3ea779 100644
--- a/modules/videos/video/lib.py
+++ b/modules/videos/video/lib.py
@@ -50,6 +50,7 @@ def get_video(file_group_id: int) -> Video:
return video
+@wrol_mode_check
def update_video(file_group_id: int, title: str = None, description: str = None) -> Video:
"""Edit a Video's details. The files are the source of truth, so the change is written to the
Video's .info.json (created if missing) and the Video is validated again from it. Only the