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