Skip to content

Reject --astreams numbers that don't match an audio track, and show setting errors on the CLI without a traceback - #122

Merged
zfleeman merged 3 commits into
mainfrom
zf/astreams-range-check
Oct 22, 2026
Merged

zfleeman merged 3 commits into
mainfrom
zf/astreams-range-check

Conversation

@zfleeman

@zfleeman zfleeman commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Closes #113. Closes #121.

Changes

  1. --astreams numbers must match an audio track (Reject --astreams numbers that don't match an audio track #113). TwoPass.run() now calls a new _check_audio_tracks() right after _check_video_size(), before ffmpeg runs. Any --astreams number past the last audio track raises a ValueError, e.g. "Audio track 7 doesn't exist. This file has 2 audio tracks, numbered 0 to 1." A file with no audio says "This file has no audio tracks." Before this, bad numbers were silently dropped, so --astreams 7 gave a video with no audio but still spent part of the bitrate budget on audio.

    • _apply_audio_filters() no longer filters the list. It uses settings.astreams as given, because run() has already checked it.
    • With --no-audio, the check is skipped. No audio goes into the output anyway, so a leftover astreams value in a config file shouldn't stop the encode.
    • Negative numbers never get this far. _parse_astreams() in arguments.py already drops them with a warning.
  2. Setting errors on the CLI without a traceback (Show TwoPass setting errors on the CLI without a traceback #121). A new context manager in __main__.py, exit_on_setting_error(), catches a ValueError, logs only its message, and exits with status 1. main() uses it in two places:

    • Around the --dry-run call to twopass.run() and the normal twopass_loop() call. This covers every ValueError that TwoPass raises: the new audio track check, the crop and even-size checks, the target-size check, bad --from/--to times, and the "no bitrate left for video" error on a retry.
    • Around the twopass.clip_times() check that runs before the web server starts, so bad --from/--to times with --web don't show a traceback either.

    Errors from the web page's own form were already shown on the page, and they're unchanged.

Things to look at closely

  • raise SystemExit(1) from None in exit_on_setting_error() hides the ValueError behind it. Without from None, Python would still print the original traceback above the exit.
  • The "Time Paradox?" message from clip_times() now prints without a traceback, but the message itself still has its old color codes and extra indentation. That formatting was already there before this PR, so I didn't change it.
  • Existing tests changed to use real tracks. test_apply_audio_filters_without_mixing_keeps_one_track dropped its [5, 1] case, and test_apply_audio_filters_mixes_selected_tracks now mixes [0, 2] instead of [0, 2, 5]. Both were testing the old drop-bad-numbers behavior. In test_main.py, test_encode_audio_stream_selection posts track 0 instead of 0 and 2, because the web fixture's file has one audio track and the web UI's dry run now rejects track 2 with a 400.
  • run_main fixture. It now replaces twopass_loop when the fixture is set up, not inside _run(). That lets the new test swap in a twopass_loop that raises before calling run_main(). The other tests get the same mock as before.

Testing

  • uv run pytest --cov: 285 passed, 99% coverage. ruff format and ruff check are clean.
  • New tests in test_twopass.py: a track number past the end raises (alone, and mixed with a valid one), --astreams 0 on a file with no audio raises, and --no-audio with a bad number still encodes.
  • New tests in test_main.py: a ValueError from twopass.run() (with --dry-run) or twopass_loop() (without it) exits with status 1 and logs only the message. A bad --from/--to with --web does the same, without starting Flask.
  • With the CLI on example.mp4 (2 audio tracks), trimmed to 10 seconds:
    • --astreams 7 --dry-run prints ERROR:root:Audio track 7 doesn't exist. This file has 2 audio tracks, numbered 0 to 1. and exits 1.
    • -x 0x0x9999x9999 (normal run) prints the crop error on one line and exits 1.
    • --from 00:00:20 --to 00:00:10 --web prints the "Time Paradox?" message and exits 1 without starting the server.
    • --astreams 0,1 --amix -s 5 encodes with [0:a:0][0:a:1]amix=inputs=2 and lands at 4.69 MB.

@zfleeman
zfleeman merged commit ffaaf7c into main Oct 22, 2026
6 checks passed
@zfleeman
zfleeman deleted the zf/astreams-range-check branch October 22, 2026 17:09
@zfleeman zfleeman mentioned this pull request Oct 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Show TwoPass setting errors on the CLI without a traceback Reject --astreams numbers that don't match an audio track

1 participant