aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: fcf3b64406952fd52c3e7bccc23f62f810eecb6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# Producer

Local, offline speech cleanup and mono mastering for WAV recordings, launched
with a single script: `producer.sh`. The only AI backend is **DeepFilterNet3**;
FFmpeg provides filtering, dynamics, loudness normalization and encoding. There
is no MossFormer backend. Audio processing is local; initial dependency and
model setup requires internet access.

Defaults produce a **48 kHz, 24-bit mono WAV**, using two-pass loudness
normalization targeting **-19 LUFS** and **-1.5 dBTP**. MP3 is optional, at
192 kb/s by default. Originals are never overwritten, including with
`--overwrite`.

## Quick Start

```bash
./producer.sh process recording.wav
```

The first run bootstraps everything the program needs into `lib/runtime/`:
a managed Python for the application (3.13), the application dependencies, a
bundled FFmpeg, and — on first AI use — the separate pinned Python 3.11
DeepFilterNet3 environment and model weights. Every
later run reuses that runtime, including fully offline once the needed assets
are present. Nothing is installed outside this directory: no `PATH` changes,
no binaries on your system, no sudo, and nothing written to your home
directory.

Requirements for the first bootstrap: a glibc Linux with `bash`, and either
an existing `uv` or a Python 3 with venv support (`python3 -m venv`). Download
trust is PyPI (uv, wheels), Astral's Python distribution, and — for AI —
download.pytorch.org plus the pinned model host. There is no curl-to-shell.

```bash
./producer.sh doctor                       # check tools and driver, no AI downloads
./producer.sh setup --device cuda          # install and self-test the AI backend
./producer.sh process recording.wav --device cuda --mp3
```

**Manage the NVIDIA driver yourself**, using the appropriate packages for your
GPU and kernel. Check `nvidia-smi` before CUDA setup. The launcher never
installs drivers or a system CUDA toolkit. The pinned CUDA backend uses
PyTorch's CUDA 12.1 wheels and needs a compatible NVIDIA driver on Linux
x86_64. For CPU use, replace `cuda` with `cpu`. The default `auto` selects
based on availability; explicit `cuda` fails rather than silently using CPU.

### Moving or renaming the directory

Move or rename the whole directory freely; nothing breaks. The next launch
detects that the runtime moved, drops only the path-bound environments, and
rebuilds them from the intact download caches — usually within seconds and
without re-downloading. Models, caches, and recordings are preserved.
`./producer.sh --rebuild process ...` forces the same repair after you edit
the source code under `lib/project/`.

### Removing the program

Delete the directory. Recordings and their outputs always live wherever the
inputs are (or where you point `--output`/`--output-dir`), never inside the
runtime, so there is nothing else to clean up.

## Everyday Use

```bash
# Master with the natural profile; outputs appear next to the input:
# recording.natural.wav, recording.natural.report.json
./producer.sh process recording.wav

# Other starting points; optional MP3 alongside WAV and report
./producer.sh process recording.wav --profile narrator --mp3
./producer.sh process recording.wav --profile radio
./producer.sh process recording.wav --profile cleanup-only

# Batch: every input keeps its outputs beside itself, so same-named files in
# different directories never collide
./producer.sh process take1.wav take2.wav --mp3

# Explicit destinations instead of the default
./producer.sh process recording.wav --output-dir delivery --mp3
./producer.sh process recording.wav -o delivery/episode.wav

# Stereo requires an explicit choice; final export is always mono
./producer.sh process stereo.wav --channel left
# Alternatives: --channel right, or --channel mix (equal-weight average)

# Explicit non-AI fallback: never selected automatically after an AI error
./producer.sh process recording.wav --denoiser fft --fft-reduction-db 6
./producer.sh process recording.wav --no-denoise

# Measurements as JSON on stdout, progress/logs on stderr
./producer.sh analyze recording.wav > analysis.json
```

Default outputs are written **beside each input**:
`<input-dir>/<stem>.<profile>.wav`, plus `<stem>.<profile>.report.json`, and
`<stem>.<profile>.mp3` when requested. Previews default to
`<input-dir>/<stem>.<profile>.preview.wav`. `--output-dir` collects all
results in one directory instead; `-o/--output` sets one explicit WAV
destination (single input) and cannot be combined with `--output-dir`.
Existing outputs or sidecars cause an error unless `--overwrite` is supplied.
Duplicate batch output names require separate runs with distinct `--output`
paths. Processing accepts mono or stereo WAV inputs, not surround audio; it
does not preserve stereo. Avoid `mix` for opposite-polarity channels, which
can cancel the voice. The whole batch plan (all inputs, all outputs, all
sidecars) is validated before any input is processed.

| Profile | Intended starting point |
| --- | --- |
| `natural` | Mild EQ, 2:1 compression, light de-essing and normalization |
| `narrator` | Gentler 1.6:1 compression, slower attack, wider loudness range |
| `radio` | Stronger 3.5:1 compression, more warmth/presence, narrower range |
| `cleanup-only` | Denoising and highpass; no leveling, EQ, compression, de-essing, normalization or limiter by default |

These presets are engineering starting points, **not tuned by listening** to
your recording or validated as universally good sounding. Listen before
publishing.

## Configuration

There is no config file at the root on purpose: configuration is only loaded
from an explicit `--config` path. A complete annotated reference with
natural-profile defaults lives at
[`lib/project/examples/voice.toml`](lib/project/examples/voice.toml).

```bash
./producer.sh process recording.wav --config lib/project/examples/voice.toml
./producer.sh process recording.wav --denoise-strength 0.65 --no-eq --hum-hz 50
./producer.sh process recording.wav --set 'denoise_strength=0.65' --set 'device="cpu"'

# Inspect or generate resolved settings without downloading AI
./producer.sh config --profile narrator
./producer.sh config --profile radio --json
./producer.sh config --profile natural > voice.toml
./producer.sh config --config voice.toml --no-compression
./producer.sh process --help
```

Settings have named flags: replace underscores with hyphens, such as
`true_peak_db` -> `--true-peak-db`. Booleans support both forms, such as
`--eq` and `--no-eq`. Repeated `--set KEY=VALUE` accepts TOML values (strings
may also be unquoted). Unknown keys, incorrect types and out-of-range values
are rejected. Select presets with `--profile`; `--set profile=radio` also
selects and validates the profile before applying its defaults — except for
`preview`, where profiles come from `--profiles` and a profile override via
`--set` is rejected so file names always match the rendered configuration.

Precedence is defaults, selected profile, TOML values, named flags, then
`--set` (last assignment wins). `--profile` selects ahead of a file's
`profile`, but other explicit file values still override the selected preset.
A full natural config therefore masks narrator/radio settings; remove keys you
want the preset to supply.

`--no-normalize` alone **leaves the default limiter on**. Use
`--no-normalize --no-limiter` to disable both. Normalization requires
limiting, so `--no-limiter` alone is invalid with normalizing profiles.
Denoising can be bypassed with `--no-denoise`, `--denoiser none`, or
`--denoise-strength 0`.

## Previews

```bash
./producer.sh preview recording.wav --start 10 --duration 30 \
  --profiles natural narrator radio cleanup-only
```

Previews go beside the input by default, with reports and optional MP3s.
Start must be nonnegative; duration must be greater than zero and at most 300
seconds. The default comparison includes natural, narrator and radio.
Previews **force normalization and limiting**, even for `cleanup-only` and
when `--no-normalize` is given, to compare at the configured loudness target.
They are not a raw A/B comparison, and there is currently no raw-original
preview export. Explicit config overrides can also make presets sound
identical.

## Limits and Reports

- Leveling is one **static recording-level gain correction**, estimated from
  the cleaned audio and capped by `max_gain_db`. The estimate comes from a
  histogram of 100 ms RMS blocks; on pause-dominated recordings it falls back
  to the loud tail so sparse speech is not mistaken for silence, and if the
  estimate is still indistinguishable from the noise floor, leveling is
  skipped with a warning instead of applying a maximum gain. It is not slow
  automatic gain riding and does not independently repair quiet phrases.
- Speech/noise estimates use that same histogram, not speech recognition.
  Noise floor is a low-confidence quiet-block heuristic and may be absent
  without sufficient room tone. It is not a certified noise measurement.
- Denoising can affect timbre or introduce artifacts. Clipped peaks, severe
  room reverb and missing detail cannot be perfectly restored. There is no
  promise of perfect restoration or ACX compliance; check the actual delivery
  specification.
- Normalization uses a measured first pass and a render pass, then verifies
  the exported WAV and decoded MP3. The final resample dithers explicitly at
  24-bit depth. FFmpeg may use dynamic rather than linear normalization; the
  report records the mode. Loudness misses over 0.5 LU and limited WAV
  true-peak overshoots over 0.1 dB are **warn-only**, not automatic rejection
  or correction. MP3 overshoots also warn; use more headroom and rerun.
- Reports include resolved settings, input/cleaned/master measurements, gain
  correction, loudness and warnings. Inspect `warnings` before publication;
  successful exit alone does not certify delivery targets.
- AI working memory is bounded by 10-second chunks, with 2-second context and
  50 ms overlap, rather than loading the whole recording. Chunked inference is
  not bit-identical to whole-recording inference. Measurement also uses
  bounded memory.
- Float intermediates trade RAM for disk and multiple full-file passes. A mono
  48 kHz float file costs about 0.69 GB per hour on the output filesystem;
  intermediates are deleted as soon as each stage has consumed them. Allow
  several GB of free space per hour on the output filesystem, separately from
  runtime storage. Runtime includes repeated disk I/O.
- Files are staged beside the output and published only after processing and
  verification. Publication is **atomic per file**, and a directory lock
  prevents concurrent jobs writing to the same output directory from
  interleaving their WAV/report/MP3 sets. Publication across the three files
  of one job is not transactional: a failure between publications can leave a
  partial set, or mixed old/new outputs with `--overwrite`.

## Progress and Troubleshooting

The CLI sends progress, diagnostics and completion messages to **stderr**.
`analyze` and `config --json` emit JSON on **stdout**; `config` emits TOML
there. Interactive terminals show stage progress and elapsed/remaining time;
redirected logs get stage changes and periodic updates. Unknown totals show
activity, not a meaningful percentage. Internal worker stdout carries
prefixed JSON progress records, consumed by the parent; this is not a public
CLI JSON-progress mode. Ctrl-C exits with code 130 and terminates the whole
active process group — including grandchildren that outlive their parent —
escalating from SIGTERM to SIGKILL after five seconds if needed. Ordinary
failures exit 1. Temporary-file cleanup runs on handled cancellation/errors;
SIGKILL or power loss can leave temporary directories. Already published
files remain.

- **First bootstrap fails:** the launcher needs `uv`, or `python3` with venv
  support, plus network access to PyPI. Install the missing tool with your
  system package manager and rerun; the launcher never runs sudo itself.
- **CUDA unavailable:** run `./producer.sh doctor`, inspect `nvidia-smi`, the
  driver and `CUDA_VISIBLE_DEVICES`, then retry `./producer.sh setup
  --device cuda`. Use `--device cpu` explicitly if needed. `doctor` probes
  tools/driver without AI downloads; `setup` actually self-tests Torch and
  DeepFilterNet.
- **Download/checksum or incomplete model failure:** check network access and
  free space; never bypass the checksum. For an incomplete-model error, remove
  only the reported `lib/runtime/models/df3-0.5.6` directory when no jobs are
  running, then retry setup.
- **FFmpeg missing/filter or MP3 encoder missing:** the bundled FFmpeg is
  preferred; a system `ffmpeg` is used where the bundle cannot be installed.
  Run `./producer.sh doctor`.
- **Changed source code:** the runtime runs an installed copy of
  `lib/project`; run `./producer.sh --rebuild` after editing it.
- **Musl/Alpine:** AI requires Linux glibc >= 2.28; musl is explicitly
  unsupported. On musl, the launcher falls back to a system FFmpeg, and
  soundfile needs a system libsndfile with an unversioned symlink (for
  example `apk add libsndfile-dev`). A separate temporary glibc test
  confirmed DeepFilterNet CPU execution; CUDA was not tested. These are test
  boundaries, not a claim of end-to-end CUDA validation.
- **Silent/too-short audio or bad stereo mix:** inspect the source with
  `analyze`, select the correct channel and use enough audible speech for
  loudness analysis. If cleanup sounds watery or thin, lower denoise strength
  and compare previews.

## Layout

```text
producer.sh            the only entry point; bootstrap + run
LICENSE                0BSD
README.md
lib/
  project/             application source, tests, example configuration
  runtime/             managed Python, dependencies, bundled FFmpeg, AI
                       environments, model weights, download caches (gitignored)
  archive/             previously generated listening samples and reports
```

`lib/runtime/` is fully regenerable: deleting it only costs re-downloads on
the next launch.

## Development

On a glibc development host with Python >= 3.11, pip and FFmpeg:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install -e 'lib/project[test]'
.venv/bin/python -m pytest -q lib/project/tests
./producer.sh --rebuild config --config lib/project/examples/voice.toml --json
```

Alternatively run the same bootstrap the launcher uses. Tests cover
configuration, synthetic audio, FFmpeg processing, output safety, the
launcher contract, and AI subprocess handling with controlled model/download
stubs; they do not download/exercise the real AI backend or substitute for
listening. Real CPU inference was verified separately.

For the musl test container only, where the declared bundled-FFmpeg wheel is
unavailable, use system FFmpeg and explicitly install core/test dependencies:

```bash
python3 -m venv .venv
.venv/bin/python -m pip install 'hatchling==1.27.0' 'rich==14.1.0' \
  'numpy>=1.26.4,<3' 'soundfile==0.13.1' 'pytest>=8,<10'
.venv/bin/python -m pip install --no-deps -e 'lib/project'
.venv/bin/python -m pytest -q lib/project/tests
```

## License

This application is licensed under [0BSD](LICENSE). Review upstream terms
before redistributing dependencies, binaries or weights:
[DeepFilterNet code and models](https://github.com/Rikorose/DeepFilterNet/tree/v0.5.6),
[PyTorch](https://github.com/pytorch/pytorch/blob/v2.5.1/LICENSE),
[Torchaudio](https://github.com/pytorch/audio/blob/v2.5.1/LICENSE),
[FFmpeg](https://ffmpeg.org/legal.html),
[imageio-ffmpeg](https://github.com/imageio/imageio-ffmpeg), and
[uv](https://github.com/astral-sh/uv). FFmpeg obligations depend on the build
and enabled components. Upstream licenses do not assign a license to this
application's code.