Film tools (ComfyUI + Coqui TTS)#

Home-manager modules for the solo animated-short pipeline on Apple Silicon (modules/home-manager/film-tools/). Enabled on work-laptop-m4 (M4 Pro), where they share the 24 GB unified-memory budget with the Ollama model set — avoid running ComfyUI generation concurrently with the qwen2.5:14b batch jobs.

Both tools are deliberately venv-based rather than nix-packaged: PyTorch Metal wheels are not in nixpkgs, and forcing them through derivations breaks on every torch release. Nix provides the interpreters and wrapper scripts; pip owns the ML dependency tree inside a per-tool venv.

Enabling#

home-manager.users.<user>.imports = [
  ../../../modules/home-manager/film-tools
];
home-manager.users.<user>.tsunaminoai.film-tools = {
  comfyui.enable = true;
  tts.enable = true;
};

ComfyUI (image generation, Metal/MPS)#

Command Purpose
comfyui Start the server on http://127.0.0.1:8188 (first run clones ComfyUI and builds the venv)
comfyui-update git pull ComfyUI + reinstall requirements

Options: tsunaminoai.film-tools.comfyui.{dataDir,port,fp16,python,extraArgs}. Everything lives under dataDir (default ~/comfyui) — point it at external storage when checkpoints outgrow the internal disk. Drop model checkpoints (.safetensors) into <dataDir>/models/checkpoints/, LoRAs into <dataDir>/models/loras/.

Coqui TTS (scratch voices)#

Command Purpose
film-tts-setup One-time venv bootstrap (PyTorch + coqui-tts[codec] + nix ffmpeg for torchcodec) into ~/.film-tts/.venv
film-tts "text" [out.wav] Synthesize a single line
film-tts-batch script.txt [outdir/] One wav per non-empty line (line_001.wav, …); speakerID<TAB>text lines cast a voice per line
film-tts-update Upgrade the TTS package in the venv

The venv installs coqui-tts (the maintained idiap fork — the original PyPI TTS package is abandoned and does not resolve on Python ≥ 3.12; the fork keeps the same tts CLI and model names).

Override the model and voice per session:

FILM_TTS_MODEL="tts_models/en/vctk/vits" FILM_TTS_SPEAKER=p225 film-tts "Hello." out.wav

Useful models: tts_models/en/ljspeech/tacotron2-DDC (default; fast, single speaker), tts_models/en/vctk/vits (multi-speaker, ~109 voices), tts_models/multilingual/multi-dataset/xtts_v2 (best quality, heavy). List a multi-speaker model’s voices with tts --model_name tts_models/en/vctk/vits --list_speaker_idxs.

Multi-voice workflow (scratch dialogue / radio segments)#

For scenes with a cast, use a multi-speaker model and give film-tts-batch a TSV script — plain lines use the default/FILM_TTS_SPEAKER voice, and speakerID<TAB>text lines pick a voice per line:

p270<TAB>Three a.m. The schoolyard where she died is dark and empty.
p294<TAB>So, Naru-chan. How does it feel to be the new candidate?
p225<TAB>How could you? All of you. I thought you were my friends!

(<TAB> = a literal tab character.)

FILM_TTS_MODEL="tts_models/en/vctk/vits" film-tts-batch scene01.tsv audio/scene01/

That yields one numbered wav per line (line_001.wav, …) for assembly in an editor or ffmpeg. The full production pattern — per-character ffmpeg FX chains (ghost reverb, radio EQ), layered crowd lines, an all-generated drone bed, loudnorm mastering — is demonstrated end-to-end by the film project’s radio-segment/build.sh (in the film working tree at ~/code/film/usagi-is-dead/, not this repo), which produced the first finished audio segment from chapter 1 of the source fic.