aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-11 18:21:38 -0400
committerhistoria <historiavg@proton.me>2026-09-11 18:21:38 -0400
commit742093b18cd31e9d25c43f475b94c232fc28a5a7 (patch)
tree056cc617f2e32cd1d43fcf681728ece4df8af6b2
parent3eccf822e14f5f4dd1f6b96e785c0217db647c3e (diff)
downloadproducer-742093b18cd31e9d25c43f475b94c232fc28a5a7.tar.gz
feat: config.toml added
-rw-r--r--README.md31
-rw-r--r--config.toml (renamed from lib/project/examples/voice.toml)14
-rw-r--r--lib/project/pyproject.toml2
-rw-r--r--lib/project/src/producer/cli.py8
-rw-r--r--lib/project/tests/test_cli.py16
-rwxr-xr-xproducer.sh31
6 files changed, 78 insertions, 24 deletions
diff --git a/README.md b/README.md
index c405e75..63f5944 100644
--- a/README.md
+++ b/README.md
@@ -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"