diff options
| author | historia <historiavg@proton.me> | 2026-09-11 18:21:38 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-11 18:21:38 -0400 |
| commit | 742093b18cd31e9d25c43f475b94c232fc28a5a7 (patch) | |
| tree | 056cc617f2e32cd1d43fcf681728ece4df8af6b2 | |
| parent | 3eccf822e14f5f4dd1f6b96e785c0217db647c3e (diff) | |
| download | producer-742093b18cd31e9d25c43f475b94c232fc28a5a7.tar.gz | |
feat: config.toml added
| -rw-r--r-- | README.md | 31 | ||||
| -rw-r--r-- | config.toml (renamed from lib/project/examples/voice.toml) | 14 | ||||
| -rw-r--r-- | lib/project/pyproject.toml | 2 | ||||
| -rw-r--r-- | lib/project/src/producer/cli.py | 8 | ||||
| -rw-r--r-- | lib/project/tests/test_cli.py | 16 | ||||
| -rwxr-xr-x | producer.sh | 31 |
6 files changed, 78 insertions, 24 deletions
@@ -16,46 +16,49 @@ dependencies and models automatically. ```bash git clone https://git.historia.vg/git/producer ./producer.sh setup -./producer.sh process recording.wav +./producer.sh recording.wav ``` First run installs the runtime into `lib/runtime/` and may take a few minutes. +The `process` keyword is optional; `./producer.sh process recording.wav` is +equivalent. The master WAV is always **mono, 32-bit PCM**, at 48 kHz (default) or 44.1 kHz; stereo inputs must be downmixed to mono first. MP3 is encoded as constant bitrate at 192 kb/s by default. Loudness is normalized to **-19 -LUFS with a -1.5 dBTP true-peak ceiling**. A diagnostic report for each run -is written to a `logs/` directory next to the outputs, not beside the audio. +LUFS with a -1.5 dBTP true-peak ceiling**. ### Examples -```bash # Optional MP3 alongside the WAV -./producer.sh process recording.wav --mp3 +./producer.sh recording.wav --mp3 # Other sound profiles -./producer.sh process recording.wav --profile narrator -./producer.sh process recording.wav --profile radio +./producer.sh recording.wav --profile narrator +./producer.sh recording.wav --profile radio # Process several files at once -./producer.sh process take1.wav take2.wav --mp3 +./producer.sh take1.wav take2.wav --mp3 # Write outputs somewhere else -./producer.sh process recording.wav --output-dir delivery --mp3 +./producer.sh recording.wav --output-dir delivery --mp3 # Skip AI noise removal (FFT fallback or none at all) -./producer.sh process recording.wav --denoiser fft -./producer.sh process recording.wav --no-denoise +./producer.sh recording.wav --denoiser fft +./producer.sh recording.wav --no-denoise # LibriVox-style delivery file: 44.1 kHz, 128 kbps CBR, -19 LUFS, mono -./producer.sh process recording.wav \ +./producer.sh recording.wav \ --sample-rate 44100 --mp3 --mp3-bitrate 128 \ --target-lufs -19 --true-peak-db -1.5 ``` All of these settings are configurable; run `./producer.sh config` to see every -setting and its default, or `--help` on any command for the flags. A fully -documented example config lives at `lib/project/examples/voice.toml`. +setting and its default, or `--help` on any command for the flags. A +`config.toml` in the same directory as `producer.sh` is loaded automatically: +`./producer.sh config` then shows those resolved values, and an explicit +`--config FILE` or any CLI flag still overrides it. The shipped `config.toml` +documents every setting. ## Profiles diff --git a/lib/project/examples/voice.toml b/config.toml index ba0fa5c..a574717 100644 --- a/lib/project/examples/voice.toml +++ b/config.toml @@ -1,17 +1,17 @@ -# Complete natural-profile defaults. Load with --config lib/project/examples/voice.toml. -# Flat TOML: no section headers. Named CLI flags override these values; -# --set KEY=VALUE overrides named flags. Use --profile, not --set profile=... -# All explicit values below override preset defaults, even with --profile radio. -# Remove settings you want a different profile to supply. +# Producer configuration. This file is loaded automatically because it sits +# next to producer.sh. Flat TOML: no section headers. Named CLI flags override +# these values; --set KEY=VALUE overrides named flags. Use --profile, not +# --set profile=... All explicit values below override preset defaults, even +# with --profile radio. Remove settings you want a different profile to supply. -profile = "natural" # natural, narrator, radio, cleanup-only +profile = "narrator" # natural, narrator, radio, cleanup-only denoiser = "deepfilter" # DeepFilterNet3 only; fft is explicit non-AI fallback; none device = "auto" # auto, cpu, cuda; explicit cuda fails if unavailable denoise_strength = 0.85 # 0..1; AI dry/wet blend; zero skips denoising and AI setup fft_reduction_db = 8.0 # 0.01..30; FFT reduction is scaled by denoise_strength highpass = true -highpass_hz = 70.0 # 20..300 +highpass_hz = 80.0 # 20..300 lowpass = false lowpass_hz = 16000.0 # 4000..22000; must be below output Nyquist when enabled hum_hz = 0 # 0 (off), 50 or 60; notches fundamental and next two harmonics diff --git a/lib/project/pyproject.toml b/lib/project/pyproject.toml index 625ee23..6c42cc9 100644 --- a/lib/project/pyproject.toml +++ b/lib/project/pyproject.toml @@ -26,4 +26,4 @@ testpaths = ["tests"] packages = ["src/producer"] [tool.hatch.build.targets.sdist] -include = ["/src", "/tests", "/examples", "/pyproject.toml"] +include = ["/src", "/tests", "/pyproject.toml"] diff --git a/lib/project/src/producer/cli.py b/lib/project/src/producer/cli.py index 4788fb5..374455a 100644 --- a/lib/project/src/producer/cli.py +++ b/lib/project/src/producer/cli.py @@ -80,7 +80,15 @@ def require_input(source: Path) -> None: raise ValueError(f"Input must be an existing WAV file: {source}") +# The 'process' keyword is optional: any first argument that is not a known +# command (or a global option) is treated as the start of a process run. +COMMANDS = ("process", "preview", "config", "doctor", "setup") +GLOBAL_OPTIONS = ("-h", "--help", "--version") + + def main(): + if len(sys.argv) > 1 and sys.argv[1] not in COMMANDS and sys.argv[1] not in GLOBAL_OPTIONS: + sys.argv.insert(1, "process") app = parser() args = app.parse_args() console = Console(stderr=True) diff --git a/lib/project/tests/test_cli.py b/lib/project/tests/test_cli.py index e02aad1..05c8292 100644 --- a/lib/project/tests/test_cli.py +++ b/lib/project/tests/test_cli.py @@ -176,6 +176,22 @@ def test_default_outputs_are_written_beside_each_input(wav, tmp_path, ffmpeg, in assert captured.err.count("Written:") == 2 +def test_process_keyword_is_optional(wav, tmp_path, ffmpeg, invoke, capsys): + source = wav() + original = source.read_bytes() + invoke(source, "--no-denoise") + assert {path.name for path in tmp_path.iterdir()} == { + "input.wav", "input.natural.wav", "logs"} + assert source.read_bytes() == original + assert capsys.readouterr().err.count("Written:") == 1 + + +def test_leading_flag_still_selects_process(wav, tmp_path, ffmpeg, invoke, capsys): + source = wav() + invoke("--no-denoise", source) + assert (tmp_path / "input.natural.wav").exists() + + def test_output_and_output_dir_cannot_be_combined(wav, tmp_path, invoke, capsys): with pytest.raises(SystemExit) as error: invoke("process", wav(), "-o", tmp_path / "x.wav", "--output-dir", tmp_path / "outs") diff --git a/producer.sh b/producer.sh index c6f2759..f2dc6be 100755 --- a/producer.sh +++ b/producer.sh @@ -35,9 +35,11 @@ usage() { cat <<'USAGE' Producer - local speech cleanup and podcast mastering. -Usage: ./producer.sh COMMAND [options] +Usage: ./producer.sh COMMAND [options], or simply: + ./producer.sh RECORDING.wav... - process RECORDING.wav... Master one or more WAV files + RECORDING.wav... Master one or more WAV files ('process' is optional) + process RECORDING.wav... The same, with the explicit command keyword preview RECORDING.wav Render loudness-matched profile previews config Print resolved settings (TOML, or JSON with --json) doctor Check FFmpeg, data directory and CUDA driver @@ -72,6 +74,31 @@ if [[ ${#ARGS[@]} -eq 1 && ( "${ARGS[0]}" == "-h" || "${ARGS[0]}" == "--help" ) exit 0 fi +# A config.toml next to this script is loaded automatically for every +# command that accepts --config, unless one is given explicitly. Named CLI +# flags and --set still override values from the file. +HAVE_CONFIG=0 +for arg in ${ARGS[@]+"${ARGS[@]}"}; do + case "$arg" in --config|--config=*) HAVE_CONFIG=1 ;; esac +done +if [[ $HAVE_CONFIG -eq 0 && -f "$ROOT/config.toml" ]]; then + case "${ARGS[0]-}" in + # After the command word: the CLI treats a leading non-command + # argument as the implicit 'process' command and then as an input. + process|preview|config) + ARGS=("${ARGS[0]}" "--config=$ROOT/config.toml" "${ARGS[@]:1}") + ;; + # These commands take no --config (or no arguments at all). + doctor|setup|-h|--help|--version|"") + ;; + *) + # Bare inputs or options: the CLI inserts 'process' first, so the + # flag can go in front. + ARGS=("--config=$ROOT/config.toml" ${ARGS[@]+"${ARGS[@]}"}) + ;; + esac +fi + mkdir -p -- "$RUNTIME/cache/pip" "$RUNTIME/cache/uv" "$RUNTIME/uv" "$RUNTIME/tmp" \ || fail "Cannot create the runtime directory under $LIB" |
