Files
catena/crates/avplan/README.md
T

5.8 KiB

avplan

Says whether a recording plays as it is, and prints the one ffmpeg call that fixes it when it does not.

Part of catena.

What it does

Reads what a file's streams actually are and does the least that fixes them. An already-H.264 picture is copied rather than re-encoded, which is the difference between forty minutes and forty seconds.

It runs nothing. What comes out is a command to read, pipe, or run — which is the point: a re-encode is expensive and the original is irreplaceable.

When you would use it

  • Before a flight. Convert the six files that need it rather than the ninety in the directory.
  • Sorting a download folder. A file that plays needs nothing; knowing which ones those are saves the rest of the work.
  • Diagnosing a video that opens to a black screen — or plays with no sound at all, which is what Opus in an MP4 looks like on a device that cannot decode it.

Install

cargo install --git https://git.pumpin.online/funkpower/catena avplan

Needs ffprobe and ffmpeg on PATH. They ship together.

Example

A VP9/Opus recording in a container nothing will open:

$ avplan lecture.webm
ffmpeg -nostdin -y -i lecture.webm -c:v h264_videotoolbox -q:v 60 -profile:v high -pix_fmt yuv420p -c:a aac -b:a 160k -ac 2 -movflags +faststart lecture.h264.mp4

Nothing to do:

$ avplan talk.mp4
$ echo $?
1

An H.264 picture in a Matroska container, with the sound in the wrong codec. The picture is copied — only the sound is re-encoded, and the file is rewritten into a container that opens:

$ avplan film.mkv
ffmpeg -nostdin -y -i film.mkv -c:v copy -c:a aac -b:a 160k -ac 2 -movflags +faststart film.h264.mp4

Why, rather than what:

$ avplan --json film.mkv | jq -c '{playable, reasons, encoder}'
{"playable":false,"reasons":["the sound is opus","a .mkv container cannot be opened"],"encoder":"copy"}

Converting everything in a directory that needs it:

for f in *.webm *.mkv; do
  plan=$(avplan "$f") && eval "$plan"
done

The line is quoted for a shell, so eval runs it as written. A file that already plays produces no line and a non-zero status, so && skips it.

Options

Option Effect
--json Emit a record — the streams, the reasons, the arguments
--output <PATH> Where the converted copy goes. Defaults to <name>.h264.mp4 beside the original
--encoder <NAME> Use this encoder rather than the best one installed
--vaapi-device <PATH> The render node to encode on. Defaults to /dev/dri/renderD128
--man Write this page as a manual page, in roff

Exit codes

Code Meaning
0 It needs converting, and the command is on stdout
1 It plays as it is
2 ffprobe could not read it
64 The command line was wrong
69 ffmpeg or ffprobe is not installed, or has no H.264 encoder
74 The file is not there

Exit 1 is the good answer, not a failure.

How it decides

Three questions, and any one of them failing means the file needs work.

The picture. H.264 or HEVC plays; anything else — VP9, AV1, VP8, MPEG-4 — does not. This is the only expensive question: a picture that already plays is copied through untouched.

The sound. AAC plays. Opus in an MP4 is the quiet failure worth naming, because the file opens, the picture runs, and there is simply no sound.

The container. .mp4, .m4v and .mov can be opened. Matroska cannot, whatever is inside it — which is why a .mkv full of H.264 and AAC still has to be rewritten, though rewriting it is nearly free.

Encoders

The encoder is found rather than configured: ffmpeg -encoders is asked what this machine has, and the best of them is taken.

Encoder Where
h264_nvenc NVIDIA
h264_videotoolbox Apple silicon and recent Macs
h264_vaapi AMD and Intel graphics
libx264 Software. Always there, and slower by a wide margin

Hardware first, because the difference on a two-hour recording is minutes against most of an hour. Each gets asked for roughly the same quality in whichever of the four quality dialects it speaks:

$ avplan --encoder libx264 lecture.webm
ffmpeg -nostdin -y -i lecture.webm -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac -b:a 160k -ac 2 -movflags +faststart lecture.h264.mp4

An ffmpeg built without any of the four cannot produce H.264 at all, and avplan says so with exit 69 rather than printing a command that would fail.

As a library

let streams = avplan::Streams {
    video: Some("vp9".into()),
    audio: Some("opus".into()),
    container: avplan::container_of("lecture.webm"),
};
let plan = avplan::plan(
    "lecture.webm",
    &streams,
    Some(avplan::Encoder::Libx264),
    "lecture.h264.mp4",
    avplan::DEFAULT_VAAPI_DEVICE,
);
assert!(!plan.playable);

video_plays, audio_plays and container_plays answer the three questions on their own, for anything that needs one of them without the rest.

The record

--json writes one of these, whatever the verdict:

Field
path The file it is about
playable Whether it plays as it stands
streams video, audio and container, as they were found
reasons Why it does not play, in the order the reasons were found. Empty when it does
encoder Which encoder the picture would go through, or copy. Absent when nothing needs doing
copies_audio Whether the sound is copied rather than re-encoded
argv The call that would fix it, argument by argument. Empty when nothing needs doing

It is a shape of its own rather than one of the records the tools pass between them, because nothing downstream consumes it.

See also

  • cuenorm — the subtitles that go with the recording.