Python CLI for carving long recordings into metadata-preserved FLAC/Opus files.
Convert supported audio recordings to FLAC or, only when needed to fit each output under a target size, high-bitrate Opus. The tool preserves originals and writes generated files to a separate output directory. Each generated output is kept below the configured size target and below four hours; longer sources are split at long silence intervals when possible.
Requires Python 3.10+ and ffmpeg/ffprobe on PATH.
pip install -e . # CLI core (stdlib only)
pip install -e ".[web]" # + FastAPI upload service
pip install -e ".[mcp]" # + MCP serverThis installs the codec-carver console command:
codec-carver /path/to/recordings --execute --output-dir under_2gbdocker build -t codec-carver .
docker run -p 8000:8000 codec-carver # upload UI at http://localhost:8000Run from media_shrink_tool/:
python3 media_shrinker.py .. \
--execute \
--download-icloud \
--include-under-limit \
--flac-all \
--exclude-dir-prefix split_over \
--max-duration-seconds 14400 \
--workers 2 \
--ffmpeg-threads 0 \
--output-dir under_2gb \
--report under_2gb/conversion_report.jsonOutputs are written under ../under_2gb/. Existing generated output directories and split_over* directories should be excluded from scans to avoid reconverting generated media. Files under 2GB are included by default; use --over-limit-only only when intentionally processing oversized sources exclusively.
Instead of re-typing long flag sets, store them once in a .codec-carver.json file in the scan root (checked first) or the current working directory:
{
"flac_all": true,
"exclude_dir_prefix": ["split_over"],
"max_duration_seconds": 14400,
"workers": 2,
"output_dir": "under_2gb"
}Then repeat runs collapse to python3 media_shrinker.py .. --execute --download-icloud.
- Keys map 1:1 to CLI options with dashes replaced by underscores (
--target-bytesbecomestarget_bytes). - Explicit CLI flags always override config values; without a config file, behavior is identical to a plain invocation.
rootand--executeare intentionally not configurable: the config file is discovered via the scan root, and a config file must never silently turn a dry run into a real conversion.- Unknown keys, wrong value types, and malformed JSON abort with a clear error listing the valid keys.
- JSON is used instead of TOML because the stdlib TOML parser requires Python 3.11+, while this project also supports Python 3.10.
--max-duration-seconds 14400keeps every generated file below four hours.- When a source is at or above that duration, the tool runs FFmpeg
silencedetectand prefers the latest safe point inside a long silence before the four-hour boundary. - If no suitable silence is detected before a boundary, the tool hard-splits just under the configured maximum so the duration rule is still enforced.
- Split outputs are named with part suffixes, for example
meeting.wav.part0001.flac,meeting.wav.part0002.flac. - Tune silence detection with
--silence-noiseand--silence-min-duration-secondswhen recordings need stricter or looser silence boundaries.
--set-title,--set-artist,--set-album, and--set-commentstamp the corresponding tags on every generated output, so archived files stay searchable in players and music libraries.- Generated commands already copy source metadata with
-map_metadata 0; the--set-*values are injected after it, so each provided key overrides that specific source tag while all other source metadata is preserved (standard ffmpeg semantics). - When none of the
--set-*options are passed, generated ffmpeg commands are byte-identical to the untagged behavior. - Values are passed to ffmpeg as single argv items without a shell, so spaces, quotes, and other special characters are safe as given.
python3 media_shrinker.py .. --execute \
--set-album "Board Meetings 2026" \
--set-comment "archived by codec-carver"--format auto(default) keeps the original behaviour: FLAC for lossless (or--flac-all) input, high-bitrate Opus otherwise.--format flac/--format opusforce that codec.--format aac(.m4a) and--format mp3produce broadly-compatible lossy output fitted to the target size — useful for players/devices that don't handle FLAC or Opus.
Turn each shrunk recording into searchable text. With --transcribe, a text and
JSON transcript sidecar is written next to every generated audio file
(recording.wav.flac → recording.wav.flac.txt / .json):
python3 media_shrinker.py .. --execute --output-dir under_2gb --transcribeTranscription is opt-in and uses faster-whisper,
imported lazily. Install it to enable the feature:
pip install faster-whisper # then pass --transcribeIf it is not installed, conversion runs normally and transcription is skipped
with a TRANSCRIBE_SKIP notice. A failing transcript never aborts a conversion.
Choose a model with --transcribe-model (default base).
- Source files selected by the scan are protected from deletion or overwrite; keep
--output-diras a generated-only directory so excluded originals are never mistaken for stale generated outputs. - Generated output names include the original filename and suffix, for example
clip.wav.flacandclip.m4a.flac, so same-stem inputs cannot collide during parallel conversion. - For lossy sources,
--flac-allfirst creates FLAC to avoid additional loss; if that output exceeds the target size, the generated FLAC is removed and a high-bitrate Opus output is created instead. - Filesystem metadata preservation is best effort: permissions, nanosecond access/modified times, extended attributes, and macOS creation date are copied when the operating system allows it.
- Video-containing files with supported container extensions are rejected unless
--allow-videois set to extract their audio track. - For real media runs, keep
--output-diras a generated-only directory such asunder_2gband avoid--overwriteunless that directory contains no original source files.
python3 -m unittest discover -s tests
python3 -m py_compile media_shrinker.py