Command-line tools for ingesting, transcoding and tidying up personal media
libraries — audiobooks, music, movies, comics and image galleries. Every one that
renames files follows the same "never clobber, never lose a file" rules,
described under File safety. What they print, and the -v and
-q that set how much, is described under
What a run prints.
Each is an installed command. From a checkout, pip install . puts all twenty
on your PATH; pip install -e . does the same and keeps them running the
checkout, which is what you want while editing it. Under a plain pip install .
the per-machine files — the data/ tables, the logs/ logs — live under
~/.local/share/medialib (or $XDG_DATA_HOME/medialib if it is set); the
environment variable CLI_SCRIPT_DIR points them somewhere else.
These commands are written for an enthusiast desktop, and try to keep all of it busy — a many-core CPU, a large NVIDIA GPU, plenty of RAM, fast disks and a network connection — rather than leaving most of it idle while one part works:
- GPU first, CPU as the fallback. Where a job has a GPU road, it takes it: AVIF pages and images go to the card through av1gpu, video can encode on NVENC and upscale on TensorRT, whisper transcribes on CUDA. Without the GPU, or for the pieces it declines, the same job runs on the CPU and still finishes.
- Different resources at the same time. Work that leans on different parts
of the machine is overlapped instead of queued: GPU transcription beside CPU
transcodes, remuxes to disk while the next film is still encoding, slow
network lookups on a background thread while the local work carries on (see
how
ingest-moviesdoes it). - Parallel where it pays. CPU-bound work is spread over the cores in jobs sized to them, and kept narrower where it would only fight a GPU job or another encoder for the same cores.
- RAM before disk. Heavy intermediate work is kept in RAM (
/dev/shm/ tmpfs) wherever possible, so only final outputs are written back to disk. Each run gets a scratch directory of its own in there, so several can run at once without sharing one.
A smaller machine runs them too — just with the CPU roads and less overlap.
Platform: these target Linux. macOS support is aspirational: everything is written to work there, but no macOS machine has run it yet, so treat it as untested rather than as broken (see Running on a Mac). On a Windows machine WSL2 is the way to run them. Native Windows is experimental for now: the tool-free cases import and run there, but the commands that drive the media tools and POSIX coreutils expect a POSIX host. Neither Windows nor macOS has a RAM-backed filesystem, so the RAM scratch described below lands in
%TEMP%/$TMPDIRon a normal disk; a RAM disk mounted by hand works iframScratchBasepoints at it.
Each command has a page of its own under docs/commands/, laid
out the same way: what it does and its usage line, an at a glance table (what it
takes and writes, whether it changes the input, what a rerun does, whether it
reaches the network), then the details, and at the foot the shared rules that
apply to it.
| Command | Does |
|---|---|
concat-audio |
One audiobook file per input subfolder, with chapters |
convert-audio |
Spoken-word audio → low-bitrate Opus or xHE-AAC |
convert-and-concat |
The two above chained, intermediate tree in RAM |
transcribe-audio |
Audio and video → whisper transcripts in a mirrored folder |
ingest-music |
Downloads → clean lossless FLAC library |
ytdlp |
Download tables of podcast feeds as audio or video, Windows or Linux |
| Command | Does |
|---|---|
convert-video |
Re-encode a video library (AV1/x265 + Opus) |
ingest-movies |
Sort, clean and improve a movie library in place |
plex-letterboxd-sync |
Sync watched films between Plex and Letterboxd |
| Command | Does |
|---|---|
ingest-books |
E-books → clean, uniform epub/pdf library |
read-library |
E-books → audiobooks (Opus + lossless), read aloud by a TTS engine |
convert-comics |
.cbr/.cbz/.cb7 and comic PDFs → .cbz of AVIF pages |
convert-images |
Batch image → AVIF / WebP / JPEG XL (or back to JPEG) |
| Command | Does |
|---|---|
clean-folder-structure |
Apply the shared name cleaners across a tree |
find-fragment-candidates |
Report the recurring name fragments a library still carries |
find-gaps |
A tree of what is missing from the numbered, episode and dated runs |
cue-to-chapters |
A .cue sheet → an OGM chapter file |
| Command | Does |
|---|---|
content-census |
Census one or more libraries into one CSV per content type |
content-census-bi |
Roll those reports up into DuckDB hypercubes and a pivot-table page |
Every command prints its arguments, options and defaults with -h (or
--help), which needs none of the media tools installed — so these pages are about
what the commands do and the rules they all follow, and name a flag only where it
changes what a command does.
cue-to-chapters is the one exception: it takes two file names and no options,
so any argument list but those two prints its usage and fails.
Every option has both forms: -j 8 and --jobs 8 (or --jobs=8) are the same
option. Abbreviations are not accepted — --job is not --jobs — so a new
option can never change what a command you already type means.
- Python 3.11.4+, and one package with it:
mutagen, which the install brings in - A UTF-8 locale (accented/multibyte filenames are handled character-wise)
- Per-tool external dependencies (see each command's
-houtput). Across the set:ffmpeg,mkvtoolnix,dovi_tool,hdr10plus_tool,mediainfo,ImageMagick,rsync,yt-dlp,fdupes,unrar/unzip/7z/tar/zstd,zip, poppler-utils (pdftoppm,pdfinfo,pdfimages,pdftotext),whisper-ctranslate2,ffsubsync,tree,beets, Calibre'sebook-convert, Ghostscript (gs),duckdb, subcleaner,curl(for TheMovieDB, Plex and Letterboxd) andwc— that last one being the only piece of coreutils anything here still shells out to. convert-audio -o xheaacadditionally needs exhale, which ffmpeg cannot stand in for: it decodes the codec but does not encode it. No distribution packages exhale, so it has to be built (seeconvert-audio).read-libraryadditionally needs a local ebook2audiobook checkout and a Python 3.10–3.12 to build its environment from; everything inside that environment is installed by the checkout itself (seeread-library).convert-video -uadditionally needs an NVIDIA GPU and a VapourSynth + TensorRT environment of its own, one piece of which has to be built (seeconvert-video).- The GPU road for AVIF in
convert-imagesandconvert-comicsis av1gpu, which has to be built (seeconvert-images); without it they take the CPU road described under Built to use the whole machine.
What a run does when one of these is missing, what does not work on a Mac yet, and which ffmpeg build a run picks are in Requirements in detail. How to keep all of them up to date with one command is under Keeping them up to date.
Logins, API keys and the knobs the commands are tuned with are environment
variables, read from the environment a command starts in or from one settings
file, ~/.config/medialib/settings, written the way a shell profile writes
them. A file that holds a secret has to be private (chmod 600). Every
setting, what it does and which commands read it is listed in
Settings.
Renaming never overwrites an existing file and never writes outside the input and output folders you gave, and the renames skipped for safety are listed at the end of the run. A run can be stopped at any point and still reports what it did. The full rules, including which commands reach the network, are in File safety.
MIT — see LICENSE. Use it, change it, ship it; there is no warranty.
Authored by David Ernst, qwen and claude opus.