`feat/bom-generator` carried one commit that never landed on master: file
content hashes in the BOM ("v2 with hashes"). The Python rewrite dropped that
capability, so port it forward rather than losing it when the branch goes.
- `--hash` records a short BLAKE2b digest per file. Opt-in, because it reads
every asset in full and that is wasted work for a plain inventory.
- Hashes are streamed in chunks with `hashlib.blake2b`, not `md5` via haveged
imports, and not whole-file reads — these assets run to hundreds of megabytes.
- YAML gains `hash:` after `size:`; Markdown gains a hash column only when
hashes were requested, so the common table stays readable.
Fix a pre-existing bug found while testing: the BOM marker was written as
`; generated by ...`, but `;` is not a YAML comment, so every generated BOM
failed to parse with "mapping values are not allowed here". Now `#`. Verified by
re-parsing output from both the PyYAML path and the built-in fallback writer.
Sizes alone cannot tell you whether an asset changed; a same-length re-render
keeps the byte count identical. That is the gap the hash closes.
Replaces the scattered TypeScript/shell helpers with one Python toolchain that
runs as plain files (`python3 scripts/tools.py <tool> ...`) with no third-party
dependencies.
New tools
- `_common.py` shared plumbing (Report, run, ffmpeg discovery, argparse ergonomics)
- `hwprobe.py` what this machine can actually encode (software + hardware)
- `srt2ass.py` SRT -> ASS, the missing step between lrc2srt and baking
- `ffmpeg_tools.py` mux / bake / still / strip / probe, with hardware encode
- `bom_gen.py` BOM generator, ported from scripts/bom-gen.ts
- `tools.py` dispatcher so any tool is one command away
- `README.md` usage for all of the above
Rewritten
- `lrc2srt.py` now handles multiple timestamps per line, timestamp-less cue lines
and blank/comment lines, and survives malformed input. The legacy version
emitted an empty cue plus a stray cue for every line.
Hardware encoding
This host is AMD (Strix Halo, VCN 4.0.5), so nvenc in the legacy notes is
reference only. `hwprobe.py` probes and reports what genuinely works here:
VA-API via /dev/dri/renderD128 gives h264_vaapi, hevc_vaapi and av1_vaapi.
`ffmpeg_tools.py --video auto` picks the hardware encoder when the render node
is usable and falls back to libx264/libx265 when it is not.
Two bugs found and fixed while wiring this up:
- the VA-API capability probe fed frames straight to the encoder without
`format=nv12,hwupload`, so ffmpeg failed with "Function not implemented" and
the toolchain silently fell back to software on a machine where hardware
works. VA-API encoders only accept GPU-resident frames; the probe now uploads.
- `bake --video copy` only failed when the copied stream happened to be VA-API.
Burning subtitles always changes pixels, so it now refuses `copy` outright
with a clear message.
Also makes -q/-n usable on either side of the mode word, and ignores
__pycache__ in .gitignore.