annie.media.convert¶
Re-encode an audio/video dataset to a consistent, validated form (service).
Some source clips are subtly broken: their container metadata disagrees with the
real frame count (torchcodec’s approximate mode derives num_frames from
duration × fps, so a clip can report more frames than it has and blow up decoding
near the end). Re-encoding every file to constant-frame-rate H.264 — and
validating the result with torchcodec — makes the dataset consistent for Annie
and for downstream dataloaders / feature extraction.
The module is split into pure, unit-tested pieces (ffmpeg command builders, stem
pairing, length math, the batch runner with an injected work function) and thin
impure wrappers (run_ffmpeg, validate_video, convert_*_file) that need a
system FFmpeg and the media extra and are exercised manually.
Audio/video length contract when muxing: the audio filter apad pads the audio
with silence and -shortest trims the output to the (finite) video stream, so the
muxed audio is exactly the video’s duration — short audio is padded, long audio is
cut — which is what audio_video_consistent() checks.
Attributes¶
Accepted source-audio suffixes (lower-case, dotted). |
|
Accepted source-video suffixes (lower-case, dotted). |
|
Offered audio output containers/codecs. |
|
Offered video containers/codecs. |
|
Sample rates the UI offers as quick picks. |
|
Quick-pick constant frame rates. |
|
Black-frame fallback size for a synthesised audio-only video. |
|
Fps of the synthesised black video when the source has none. |
|
Lifecycle state of a batch conversion run. |
Classes¶
Audio re-encode settings. |
|
Video re-encode settings. |
|
One planned audio conversion (source → destination). |
|
One planned video conversion. |
|
A snapshot of a running (or finished) conversion batch. |
|
Runs one conversion batch at a time on a background thread, with progress. |
Functions¶
|
Build the ffmpeg argv to re-encode one audio file. |
|
Build the |
|
Build the ffmpeg argv to re-encode one video (constant frame rate H.264). |
|
Build the ffmpeg argv for an audio-only item: a black-frame video + audio. |
|
Plan the audio batch: one |
|
Plan the video batch: one |
|
Return the number of frames a |
|
Return the number of audio samples a |
|
Whether an audio track and a video stream have matching durations. |
|
Run an ffmpeg command, raising |
|
Return the torchcodec frame count, verified by actually decoding, or |
|
Validate a re-encoded video with torchcodec. |
|
Re-encode one audio file, skipping it if a non-empty output already exists. |
|
Re-encode one video (or synthesise a black-frame one), then validate it. |
|
Write a one-column CSV ( |
|
Write a two-column CSV ( |
Module Contents¶
- annie.media.convert.AUDIO_INPUT_SUFFIXES: tuple[str, ...] = ('.wav', '.mp3', '.flac', '.m4a', '.aac', '.ogg', '.opus')¶
Accepted source-audio suffixes (lower-case, dotted).
- annie.media.convert.VIDEO_INPUT_SUFFIXES: tuple[str, ...] = ('.mp4', '.avi', '.mov', '.mkv', '.webm', '.m4v')¶
Accepted source-video suffixes (lower-case, dotted).
- annie.media.convert.AUDIO_OUTPUT_FORMATS: tuple[str, ...] = ('wav', 'flac', 'mp3')¶
Offered audio output containers/codecs.
- annie.media.convert.VIDEO_OUTPUT_FORMATS: tuple[str, ...] = ('mp4',)¶
Offered video containers/codecs.
- annie.media.convert.COMMON_SAMPLE_RATES: tuple[int, ...] = (8000, 16000, 22050, 44100, 48000)¶
Sample rates the UI offers as quick picks.
- annie.media.convert.COMMON_FPS: tuple[float, ...] = (10, 24, 25, 30)¶
Quick-pick constant frame rates.
- class annie.media.convert.AudioOptions[source]¶
Audio re-encode settings.
- out_format¶
Output container/codec (
"wav"default, PCM 16-bit).
- sample_rate¶
Target sample rate in Hz.
- channels¶
Channel count (
1= mono, the default).
- class annie.media.convert.VideoOptions[source]¶
Video re-encode settings.
- width¶
Target width in px, or
Noneto keep the source width.
- height¶
Target height in px, or
Noneto keep the source height.
- grayscale¶
Desaturate to black-and-white when
True(still yuv420p).
- fps¶
Target constant frame rate, or
Noneto keep the source fps.
- add_audio¶
Mux the matching-stem audio into the output when
True.
- min_frames¶
Reject (skip) any source whose output would have fewer than this many decodable frames.
2(default) drops degenerate single-frame “videos” (audio-only files that carry one thumbnail-like frame);1only requires that at least one frame decodes.
- out_format¶
Output container (
"mp4").
- property keep_resolution: bool¶
Whether the source resolution is kept (no scaling).
- annie.media.convert.BLACK_DEFAULT_SIZE = 256¶
Black-frame fallback size for a synthesised audio-only video.
Used when the options keep the original size/fps (of which there is none).
- annie.media.convert.BLACK_DEFAULT_FPS = 25.0¶
Fps of the synthesised black video when the source has none.
- annie.media.convert.audio_command(src: str | pathlib.Path, dst: str | pathlib.Path, opts: AudioOptions) list[str][source]¶
Build the ffmpeg argv to re-encode one audio file.
- Parameters:
src – Source audio (or a media file with an audio stream).
dst – Destination path.
opts – Audio settings.
- Returns:
The ffmpeg argv list (no shell).
- annie.media.convert.video_filters(opts: VideoOptions) str[source]¶
Build the
-vffilter chain string for the given video options.- Parameters:
opts – Video settings.
- Returns:
A comma-joined filter chain, or
""when no spatial filter is needed. A single given dimension scales the other by-2(keeps aspect, even).
- annie.media.convert.video_command(src: str | pathlib.Path, dst: str | pathlib.Path, opts: VideoOptions, audio_path: str | pathlib.Path | None = None) list[str][source]¶
Build the ffmpeg argv to re-encode one video (constant frame rate H.264).
Audio handling: when
opts.add_audioandaudio_pathis given, the audio is muxed and matched to the video length viaapad+-shortest; otherwise the output has no audio (-an).- Parameters:
src – Source video.
dst – Destination
.mp4.opts – Video settings.
audio_path – Matching audio file to mux, or
None.
- Returns:
The ffmpeg argv list (no shell).
- annie.media.convert.black_video_command(dst: str | pathlib.Path, audio: str | pathlib.Path, opts: VideoOptions) list[str][source]¶
Build the ffmpeg argv for an audio-only item: a black-frame video + audio.
The black
lavfisource is infinite and-shortesttrims the output to the (finite) audio, so the synthesised video has exactly the audio’s duration. The frame size / fps come fromoptsor fall back toBLACK_DEFAULT_SIZE/BLACK_DEFAULT_FPSwhen the options keep the (non-existent) original.- Parameters:
dst – Destination
.mp4.audio – The audio to carry.
opts – Video settings (used for size / fps).
- Returns:
The ffmpeg argv list (no shell).
- class annie.media.convert.AudioItem[source]¶
One planned audio conversion (source → destination).
- property label: str¶
Display name for progress (the source filename).
- class annie.media.convert.VideoItem[source]¶
One planned video conversion.
srcis the source video, orNonefor an audio-only item that should be synthesised as a black-frame video carryingaudio.- property label: str¶
Display name for progress (the source filename, or the output name).
- property is_black: bool¶
Whether this item synthesises a black-frame video (no source video).
- annie.media.convert.plan_audio(in_dir: str | pathlib.Path | None, out_dir: str | pathlib.Path, opts: AudioOptions) list[AudioItem][source]¶
Plan the audio batch: one
AudioItemper input file.- Parameters:
in_dir – Folder of source audio files.
out_dir – Destination folder.
opts – Audio settings (its
out_formatsets the destination suffix).
- Returns:
The planned items (empty if the input folder is missing).
- annie.media.convert.plan_video(in_dir: str | pathlib.Path | None, out_dir: str | pathlib.Path, opts: VideoOptions, audio_dir: str | pathlib.Path | None = None) list[VideoItem][source]¶
Plan the video batch: one
VideoItemper input file.When
opts.add_audioandaudio_diris given, each video is paired by stem with an audio file of any supported format inaudio_dir. Whenopts.audio_only_blackis also set, an audio file with no matching video becomes a black-frame video carrying that audio; otherwise such audio yields no video at all (it stays only in the audio folder).- Parameters:
in_dir – Folder of source videos.
out_dir – Destination folder.
opts – Video settings (its
out_formatsets the destination suffix).audio_dir – Folder of audio to mux (typically the audio output folder).
- Returns:
The planned items (empty if the input folder is missing).
- annie.media.convert.expected_frame_count(duration_s: float, fps: float) int[source]¶
Return the number of frames a
duration_sclip atfpsshould have.
- annie.media.convert.expected_sample_count(duration_s: float, sample_rate: int) int[source]¶
Return the number of audio samples a
duration_sclip atsample_ratehas.
- annie.media.convert.audio_video_consistent(num_frames: int, fps: float, num_samples: int, sample_rate: int, *, tolerance_s: float = 0.04) bool[source]¶
Whether an audio track and a video stream have matching durations.
- Parameters:
num_frames – Decoded video frame count.
fps – Video frame rate.
num_samples – Decoded audio sample count (per channel).
sample_rate – Audio sample rate.
tolerance_s – Allowed absolute difference in seconds (≈ one frame at 25 fps).
- Returns:
Trueif the two durations agree withintolerance_s.
- annie.media.convert.run_ffmpeg(cmd: list[str]) None[source]¶
Run an ffmpeg command, raising
RuntimeErrorwith its tail on failure.
- annie.media.convert.decodable_frames(path: str | pathlib.Path) int[source]¶
Return the torchcodec frame count, verified by actually decoding, or
0.A file with a declared video stream but no real frames (an audio-only
.mp4that VLC plays as audio) can still reportnum_frames >= 1in its metadata, so the count alone is not enough — we decode the first (and last) frame to confirm they really exist. Any decode failure yields0.- Parameters:
path – The media file to inspect.
- Returns:
The number of decodable frames (
0if the frames cannot be decoded).
- annie.media.convert.validate_video(path: str | pathlib.Path, min_frames: int = 1) tuple[bool, str][source]¶
Validate a re-encoded video with torchcodec.
Requires at least
min_framesframes (so a degenerate single-frame, audio-only result is rejected), decodes every frame inexactmode (so an unreadable frame raises), and confirms theexactandapproximateframe counts agree — the discrepancy that makes some source clips fail late in decoding.- Parameters:
path – The video to validate.
min_frames – Minimum decodable frame count required to pass.
- Returns:
An
(ok, message)pair.
- annie.media.convert.convert_audio_file(item: AudioItem, opts: AudioOptions) None[source]¶
Re-encode one audio file, skipping it if a non-empty output already exists.
On any failure the (possibly partial) output is removed, so the output folder only ever contains good files.
- annie.media.convert.convert_video_file(item: VideoItem, opts: VideoOptions) None[source]¶
Re-encode one video (or synthesise a black-frame one), then validate it.
Grace handling: a source
.mp4is skipped with an error (and never lands in the output folder) when it has no video stream or yields fewer thanopts.min_framesdecodable frames — which drops degenerate single-frame “videos” (audio-only files carrying one thumbnail-like frame, which VLC plays as audio-only). A stale bad output from a previous run is removed in those cases too. If a torchcodec-valid output already exists it is skipped (resume); any failure removes a partial output.
- annie.media.convert.export_ids(path: str | pathlib.Path, ids: list[str]) pathlib.Path[source]¶
Write a one-column CSV (
id) of the given filenames’ stems, sorted.- Parameters:
path – Destination CSV path.
ids – Filenames (or ids); their stems are written, de-duplicated and sorted.
- Returns:
The path written.
- annie.media.convert.export_failures(path: str | pathlib.Path, failures: list[tuple[str, str]]) pathlib.Path[source]¶
Write a two-column CSV (
id,error) for the failed files, sorted by id.- Parameters:
path – Destination CSV path.
failures –
(filename, error)pairs.
- Returns:
The path written.
- annie.media.convert.ConvertStatus¶
Lifecycle state of a batch conversion run.
- class annie.media.convert.ConvertProgress[source]¶
A snapshot of a running (or finished) conversion batch.
- title¶
Human label of the batch (e.g.
"Audio"/"Video").
- total¶
Number of files in the batch.
- done¶
Number processed so far (succeeded or failed).
- current¶
The file currently being processed.
- succeeded¶
Filenames that converted (or were skipped as already done).
- failed¶
(filename, error)pairs for files that failed.
- started_at¶
Epoch seconds when the batch started, or
None.
- finished_at¶
Epoch seconds when it finished, or
Nonewhile running.
- status¶
One of
ConvertStatus.
- property elapsed: float¶
Seconds elapsed since the batch started (frozen once finished).
- property fraction: float¶
Progress as a fraction in
[0, 1].
- property eta_seconds: float | None¶
Estimated seconds remaining, or
Noneif not yet estimable.
- property succeeded_count: int¶
Number of files processed without error.
- class annie.media.convert.ConversionRunner[source]¶
Runs one conversion batch at a time on a background thread, with progress.
The batch loop is generic: it is given a list of items, a
workcallable that converts one item (raising on failure), and alabelcallable for display. Failures are recorded per item and never stop the batch.- progress() ConvertProgress[source]¶
Return a consistent snapshot of the current progress.
- start(title: str, items: list, work: collections.abc.Callable[[object], None], *, label: collections.abc.Callable[[object], str] = lambda it: ...) concurrent.futures.Future[source]¶
Begin a batch on the worker thread.
- Parameters:
title – Human label for the batch.
items – The planned items to convert.
work – Converts one item, raising on failure.
label – Maps an item to its display name.
- Returns:
The
Futureof the batch (mostly for tests).- Raises:
RuntimeError – If a batch is already running.