Sign inSign up

fnndsc/pl-ffmpeg

By fnndsc

•Updated about 20 hours ago

ffmpeg 7.1: each recording's audio as 16 kHz mono WAV

Image
0

95

fnndsc/pl-ffmpeg repository overview

⁠pl-ffmpeg

ffmpeg 7.1 over every recording of an input tree, as a ChRIS plugin.

pl-ffmpeg is a ChRIS <https://chrisproject.org/>_ DS plugin that runs ffmpeg once per recording, video or audio, found in its input tree, the first positional argument, /incoming below. By default it writes each recording's audio as a 16 kHz mono WAV, the form speech recognisers read, into the output tree, the second positional argument, /outgoing, in the directory matching the recording's own and under the recording's name: /incoming/weekly.mp4 gives /outgoing/weekly.wav. The common case needs no flags:

.. code-block:: bash

pl-ffmpeg /incoming /outgoing

Any other ffmpeg command line runs the same way through --exec. In the Transcribe pipeline pl-ffmpeg is the first step and pl-whisper <https://github.com/FNNDSC/pl-whisper>_ the second, so the decoding runs on any node and only the transcription asks for a GPU.

pl-ffmpeg has no engine of its own. It is pl-shexec <https://github.com/FNNDSC/pl-shexec>_, the plugin that runs a command template once per file of a tree, built into an image that also holds ffmpeg and given an ffmpeg command line as its default. Walking the tree, choosing the files, running the command and recording each run are pl-shexec's code; pl-ffmpeg supplies the image, the default arguments and its manual.

⁠What it runs

The default command is a template that pl-shexec resolves once per recording:

.. code-block:: text

ffmpeg -nostdin -hide_banner -y -i %{inputWorkingDir}/%{inputWorkingFile}
       -vn -ac 1 -ar 16000 -c:a pcm_s16le %{outputWorkingDir}/%{inputWorkingFile|rmext}.wav

%{inputWorkingDir} is the recording's directory in the input tree, %{inputWorkingFile} the recording, %{outputWorkingDir} the matching directory in the output tree, and %{inputWorkingFile|rmext} the recording's name less its extension. The command drops any video, mixes to one channel and resamples to 16000 samples a second as 16-bit PCM, about 115 MB an hour of audio.

To write something else, pass another template:

.. code-block:: bash

pl-ffmpeg --exec "ffmpeg -nostdin -y -i %{inputWorkingDir}/%{inputWorkingFile}
                  -vn -ac 1 -b:a 64k %{outputWorkingDir}/%{inputWorkingFile|rmext}.mp3" \
    /incoming /outgoing

pl-ffmpeg --man lists every tag and function.

⁠Which files are recordings

Every file, at any depth, that passes --fileFilter. The default is one regular expression, blind to case, for the usual video and audio extensions: .mp4, .m4a, .m4v, .mov, .mkv, .webm, .avi, .mp3, .wav, .flac, .ogg, .oga, .opus, .aac and .wma. Notes, slides and sidecars beside the recordings are passed over. --analyzeFileIndex v, the default, runs every recording, each on its own.

.. code-block:: text

/incoming/weekly.mp4               ->  /outgoing/weekly.wav
/incoming/2026-11/standup.m4a      ->  /outgoing/2026-11/standup.wav
/incoming/2026-11/review.MOV       ->  /outgoing/2026-11/review.wav
/incoming/2026-11/slides.pdf           passed over

Two recordings of one directory that differ only in extension, weekly.mp4 and weekly.m4a, write the same WAV, the later over the earlier.

⁠What each run leaves behind

Beside the WAVs of each directory, jobs.jsonl holds one line per recording: the command, its output, its exit status, the host and the timing. pflog read /outgoing/**/jobs.jsonl prints them as a table. The plugin's own run is one line of JSON on standard error, which CUBE keeps with the plugin instance. A recording that fails is recorded and the run goes on; the plugin exits 0 when every recording of at least one directory completed, 1 otherwise, 2 on a usage error.

⁠Running it

From ChRIS, attach it to a node holding recordings, or run the Transcribe pipeline, which starts with it. From the command line, with the image:

.. code-block:: bash

docker run --rm -v $PWD/in:/incoming:ro -v $PWD/out:/outgoing:rw fnndsc/pl-ffmpeg \
    pl-ffmpeg /incoming /outgoing

docker run --rm fnndsc/pl-ffmpeg pl-ffmpeg --man

One instance runs the recordings of a directory one after another; --threads N runs up to N directories at once.

⁠Built on pl-shexec

This plugin is pl-shexec's plugin_build called with a spec: the name, the version, the title and resource limits ChRIS sees, the default template, and the manual's words. The filters, the tags, the records and the exit status are pl-shexec's and are documented there. The plugin's version is independent of ffmpeg's: pl-ffmpeg --version prints both, and docs/history.adoc records which ffmpeg each plugin version carried.

⁠Development

.. code-block:: bash

uv venv --python /usr/bin/python3.14 .venv
uv pip install --python .venv/bin/python -e ".[dev]"
.venv/bin/ruff check . && .venv/bin/ruff format --check . && .venv/bin/python style_check.py pl_ffmpeg tests
.venv/bin/pytest -q

The tests run the plugin in-process with a stand-in ffmpeg on PATH, and once with the real ffmpeg when one is installed. ruff.toml and style_check.py are the lint configuration of the style guide <https://github.com/rudolphpienaar/style-guides>_. The CI workflow lints and tests on Python 3.11 to 3.14, builds the image on every push to main, pushes it to Docker Hub and ghcr.io, and keeps the plugin's description as a build artifact for registering it on a CUBE. The tag must match __version__ in pl_ffmpeg/__init__.py.

docs/purpose.adoc says why the plugin exists, docs/architecture.adoc how it is built, docs/history.adoc where it came from.

Tag summary

Content type

Image

Digest

sha256:45195d7c2…

Size

208.2 MB

Last updated

about 20 hours ago

docker pull fnndsc/pl-ffmpeg