wrolpi/app/src/components/CodecSettings.test.js
lrnselfreliance 0b61d3ab08
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Controller Tests / controller-tests (push) Has been cancelled
Jest Tests / jest-tests (push) Has been cancelled
videos: codec download preferences with transcode and strict-fail options (#701)
* videos: codec download preferences with transcode and strict-fail options

Add video_codecs/audio_codecs ordered preference lists to the video,
channel, and RSS downloaders, alongside the existing resolution
selector.  Codec preference is codec-major: a preferred codec at a
lower resolution beats another codec at a higher resolution.

When 'transcode' is enabled, a downloaded file whose streams do not
match the preferences is re-encoded with ffmpeg after download (only
the mismatching stream; targets are extensible maps, currently h264
and aac/opus/mp3).  When 'strict_codecs' is enabled and transcode is
not, a download with no matching format fails permanently instead of
falling back.

Empty codec lists produce a byte-identical yt-dlp command to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: honor strict_codecs when transcode cannot produce the preference

Review fixes:
- strict_codecs is now suppressed only when transcoding can produce a
  preferred codec for every non-empty preference list (new
  transcode_can_satisfy_codecs); av1-only preferences with transcode
  enabled fail under strict instead of silently keeping the file.
- No partial transcode: an unfixable video mismatch blocks an
  audio-only conversion (and vice versa); the mismatch falls through
  to strict/keep.
- fsync the transcode output before rename, and the directory after,
  so power loss on a Pi cannot leave a truncated final file.
- mp3 yt-dlp pattern matches mp4a.6B as well as mp4a.6b.
- ffmpeg maps use 0:V:0 (excludes attached pictures) and 0🅰️0?
  (tolerates missing audio).
- Strict toggle is no longer disabled by transcode in the UI; popup
  wording corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: serialize transcodes machine-wide via shared_ctx lock

Sanic runs multiple worker processes, so an asyncio primitive cannot
serialize transcodes across download workers.  A multiprocessing.Lock
in shared_ctx now guarantees only one ffmpeg transcode runs at a time
on the machine; waiting workers poll the lock asynchronously so the
event loop stays responsive while a transcode holds it for hours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* videos: transcode-lock review fixes

- Document that a crashed lock holder requires an API restart; this is
  accepted rather than adding owner-death recovery.
- Move the 'Transcoding' user notification to after the lock is
  acquired, so it fires when work begins rather than before a
  potentially hours-long wait.
- Soften the lock test docstring to what it measures (in-process
  wait/release); trim the acquire docstring to the polling rationale.
- Give transcode_lock its own section header in contexts.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 09:12:00 -06:00

53 lines
2.3 KiB
JavaScript

import React from 'react';
import {screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {CodecSettingsForm} from './Download';
import {createTestForm, renderUI} from '../test-utils';
/** createTestForm implements getCustomProps but not getSelectionProps (used by
* MultiSelectField); the shapes are compatible for these tests. */
const createCodecForm = (initialData) => {
const form = createTestForm(initialData);
form.getSelectionProps = form.getCustomProps;
return form;
};
describe('CodecSettingsForm', () => {
it('renders codec selectors and toggles', () => {
const form = createCodecForm({settings: {video_codecs: ['h264'], audio_codecs: []}});
renderUI(<CodecSettingsForm form={form}/>);
expect(screen.getByText('Video Codecs')).toBeInTheDocument();
expect(screen.getByText('Audio Codecs')).toBeInTheDocument();
expect(screen.getByText('Transcode to preferred codecs')).toBeInTheDocument();
expect(screen.getByText('Fail if codecs unavailable')).toBeInTheDocument();
});
it('keeps the strict toggle usable while transcode is enabled', () => {
// Strict still matters with transcode on: a preference with no transcode target
// (e.g. av1) cannot be fixed by transcoding, so the backend honors strict then.
const form = createCodecForm({settings: {transcode: true, strict_codecs: true}});
renderUI(<CodecSettingsForm form={form}/>);
const toggles = screen.getAllByTestId('toggle');
const [transcodeToggle, strictToggle] = toggles.map(i => i.querySelector('input') || i);
expect(transcodeToggle).toBeChecked();
expect(strictToggle).toBeChecked();
expect(strictToggle).not.toBeDisabled();
});
it('toggling strict updates the form', async () => {
const form = createCodecForm({settings: {transcode: false, strict_codecs: false}});
renderUI(<CodecSettingsForm form={form}/>);
const toggles = screen.getAllByTestId('toggle');
const [, strictToggle] = toggles.map(i => i.querySelector('input') || i);
expect(strictToggle).not.toBeDisabled();
await userEvent.click(strictToggle);
expect(form.formData.settings.strict_codecs).toBe(true);
});
});