aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: c1faf42e5c8de6aa13c5d8e32c740e2a1f742d08 (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
# Producer

Local speech cleanup and mastering for voice recordings. Noise removal uses
ClearVoice or DeepFilterNet3; speech-aware pause silencing uses Silero VAD.
Filtering, dynamics and loudness normalization use FFmpeg. Audio stays local.

`producer.sh` bootstraps its own managed Python environments and downloads
dependencies and models automatically.

## Requirements

- glibc >= 2.28 for AI denoising and speech detection
- `uv` (or Python 3)
- NVidia driver and toolkit (optional)

## Usage

```bash
git clone https://git.historia.vg/git/producer
./producer.sh setup --denoiser clearvoice
./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**.

### Examples
```
# Optional MP3 alongside the WAV
./producer.sh recording.wav --mp3

# Other sound profiles
./producer.sh recording.wav --profile narrator
./producer.sh recording.wav --profile radio

# Settings that differ from the built-in profile are named 'custom'
./producer.sh recording.wav --no-denoise   # recording.custom.wav

# Process several files at once
./producer.sh take1.wav take2.wav --mp3

# Write outputs somewhere else
./producer.sh recording.wav --output-dir delivery --mp3

# Skip AI noise removal (FFT fallback or none at all)
./producer.sh recording.wav --denoiser fft
./producer.sh recording.wav --no-denoise

# Compare ClearVoice MossFormer2_SE_48K with DeepFilterNet3
# (larger download; speech preservation depends on the recording)
./producer.sh recording.wav --denoiser clearvoice
./producer.sh setup --denoiser clearvoice --device cpu

# Silence pauses without changing the timing (enabled in config.toml)
./producer.sh recording.wav --pause-silence --no-trim-silence

# Bypass pause silencing for an A/B comparison
./producer.sh recording.wav --no-pause-silence --output-dir comparison

# Optionally shorten long gaps between takes (a separate pacing edit)
./producer.sh recording.wav --trim-silence --max-gap-s 1.5 --gap-pad-s 0.6

# LibriVox-style delivery file: 44.1 kHz, 128 kbps CBR, -19 LUFS, mono
./producer.sh recording.wav \
  --sample-rate 44100 --mp3 --mp3-bitrate 128 \
  --target-lufs -19 --true-peak-db -1.5
```

Edit default settings in `config.toml`

## Optional local browser GUI

```bash
./producer.sh gui
# Optional alternate loopback port:
./producer.sh gui --port 8766
```

The studio is a compact workbench: pick a WAV, select a passage, tune
settings, compare, and export. Use a current desktop browser (Chromium
tested); JavaScript is required. No Node/npm build, CDN, telemetry, or cloud
audio service is used. The launcher installs the optional FastAPI/uvicorn
extra only when `gui` is requested. Existing CLI commands do not need it.
Direct package installs can use `producer[gui]` and need system FFmpeg or the
`bundled-ffmpeg` extra for processing.

1. Click **Browse…** and choose a mono WAV from anywhere on this machine (the
   file picker can also batch-select several). The browser uploads a temporary
   copy for processing; your original file is never touched. The overview is a
   streaming scan reduced to 2,048 peak bins, not the whole recording loaded
   into the browser. You can enter times before scanning finishes.
2. Click or drag the waveform, or enter exact `HH:MM:SS.sss` start/end times
   (seconds also work). Zoom enlarges the same coarse overview; exact times
   are best for fine selection on multi-hour recordings.
3. Every control's help text briefly explains what it does. Turning an effect
   off greys out and disables its dependent values while keeping them. FFT
   reduction only applies to the lightweight FFT denoiser: it is scaled by
   cleanup strength and disappears entirely when you pick DeepFilter,
   ClearVoice or "none". "Pause silencing (VAD)" uses a local speech detector
   to zero non-speech pauses without moving anything; "Gap shortening (export
   only)" trims long gaps on the full export and is never auditioned in the
   comparison. Click **Render sound comparison**.
4. Play/loop the passage and switch A/B without losing your place. Optional
   matching attenuates the louder snippet for fair listening. File loudness
   and peak readings are separate from monitoring gain, not quality scores
   or full-export measurements. Changes pause playback and mark older renders;
   late jobs never replace the visible comparison after you edit the selection.
5. Enter an existing **absolute** output directory (default: the directory
   you launched Producer from — start it next to your recording and exports
   land beside the input). The audio file
   (`<name>.<profile>.wav`, optional `.mp3`) is written directly into that
   directory; the timestamped diagnostic report goes to the `logs/`
   directory next to `producer.sh`. Settings that differ from the built-in
   profile are named `custom`, and the browser asks before replacing
   existing audio. This processes the entire original even if you
   selected only a few seconds. Completed exports
   list their full local paths; find the files with your operating system's
   file manager.

The server binds only to loopback (127.0.0.1) and has no login: **anything
that can reach this port on this machine can use it**, so run it only on a
machine you control and do not expose it through a proxy, port-forwarding, or
`0.0.0.0`. Stop the server with Ctrl-C.

**Containers/remote shells:** loopback belongs to the machine/network
namespace running Producer. A browser on the host cannot normally reach a
container's `127.0.0.1`; publishing a container port does not help because the
server still only answers `127.0.0.1`. The simplest supported setup is to run
`./producer.sh gui` directly on the machine with your browser and recordings.

Settings use the same strict resolver and configuration precedence as the
CLI. The initial controls include your launch TOML and CLI overrides.
**Restore launch settings** resets edits. **Apply profile** resets through
that precedence too: explicit launch values still win. Changing only the
profile name keeps your other explicit knob values. TOML import instead loads
standalone settings (package defaults/profile plus the imported TOML).

Save up to 20 named settings presets and 12 immutable comparison snapshots in
this browser. Snapshots retain the recording identity, excerpt times and
requested settings, never audio. **Restore & render** explicitly recreates a
comparison; a changed source file must be re-uploaded and re-selected.
Download/import small TOML presets to move settings between browsers.
Browser storage can be cleared or disabled; use TOML for backup.

Uploads, waveform scans and jobs run in the background, one at a time, with
up to three waiting. Activity shows per-stage progress, frozen snapshots and
cancellation. Cancellation is cooperative: a model download may take up to 60
seconds to reach a safe callback, and files already published can remain.
There is no total recording-duration cap; typical 1–4 hour and longer
recordings still need sufficient processing time and temporary disk space.
Inputs must be readable mono WAVs; stereo needs an explicit downmix outside
Producer first. Uploaded copies live in a private temporary directory and are
deleted when the server stops.

First AI noise cleanup or pause silencing use downloads dependencies/models;
processing stays local afterwards. For a quick no-model trial, launch with
`./producer.sh gui --no-denoise --no-pause-silence`. These explicit flags
establish initial controls, not hidden permanent bypasses. Reload reconnects
to recent jobs, but does not automatically restore/play old audio. If Producer
restarts, open its printed URL again. If a connection drops during an export
request, check Activity before submitting another export; the first may have
succeeded.

Sound-comparison previews select up to 120 seconds anywhere in a recording,
process with up to 10 seconds of context on each side, then crop to the
selected interval. They are **approximate, not exact export previews**:
chapter-level normalization and inference context differ. Gap-trimming edits
are not previewed; comparisons preserve timing even when export gap trimming
is selected. Normalization and other effects are never forced on for
comparison. Optional loudness matching is playback-only attenuation, not a
change to export settings.

Exports process the whole original with the requested settings and write the
audio file directly into the chosen output directory:
`<name>.<profile>.wav` (named `custom` when settings differ from the built-in
profile), optional `<name>.<profile>.mp3`. Replacing existing audio asks for
explicit confirmation. The timestamped diagnostic report goes to the `logs/`
directory next to `producer.sh`, so reports never collide. The API
returns persistent local paths, **not browser blobs of multi-hour exports**.
The browser only fetches bounded preview snippets. TOML presets can be
imported as text or downloaded; no source recording is rewritten by the API.
The server retains at most eight repeated-result cache entries and 128 MiB of
artifacts; expired previews must be rendered again. Job history is limited to
24 entries and is lost when the server stops, while completed output files
persist. Browser preview WAVs are bounded to about 11.6 MB per side (120
seconds of mono PCM16 at 48 kHz). Full exports never become browser Blobs.
These are generic WAV/MP3 delivery files, not delivery-platform compliance
certification.
### GUI development checks (optional)

The application itself needs no browser automation or Node dependencies. With
an external test environment containing `producer[gui,test]`, use:

```bash
python -m pytest lib/project/tests -q
node --check lib/project/src/producer/web/app.js
node --test lib/project/tests/gui_state.test.mjs
# Optional real headless browser checks, no AI models:
python -m pip install playwright
python -m playwright install chromium
PRODUCER_BROWSER_TESTS=1 python -m pytest lib/project/tests/test_gui_browser*.py -q
```

Playwright may need OS-specific browser shared libraries in the test container;
these are not application runtime dependencies. Browser checks generate a
short test WAV, exercise real scan/preview/export and cancellation, and mock a
bounded four-hour overview. They do not claim a four-hour AI processing run.
Screenshots default to `/tmp/producer-browser-smoke`; set
`PRODUCER_BROWSER_ARTIFACTS` to keep them elsewhere.

## Profiles

| Profile        | Sound                                                     |
| -------------- | --------------------------------------------------------- |
| `natural`      | Mild EQ, 2:1 compression, light de-essing (default)       |
| `narrator`     | Gentler compression, slower attack, wider loudness range  |
| `radio`        | Stronger compression, more warmth/presence, tighter range |
| `cleanup-only` | Denoise + highpass only; no EQ, dynamics or normalization |

## Speech starts and silent pauses

Denoising and pause silencing are different jobs. Pushing a denoiser harder
can damage quiet consonants without eliminating all room tone. Compression
and normalization then raise the remaining noise. The shipped `config.toml`
uses ClearVoice at 0.85 strength, speech-aware pause silencing, and **no gap
trimming**. Bare package defaults leave pause silencing off (opt in with
`--pause-silence`).

Pause silencing detects speech on the **pre-denoise** audio using pinned
Silero VAD v5.1 ONNX weights. First use downloads a separate CPU runtime and
~2.3 MB model; it needs no GPU or cloud service. After EQ/dynamics, gaps
outside protected speech fade to zero before loudness normalization. This
keeps the timeline unchanged unless you separately enable `--trim-silence`.
32-bit export uses no dither so silent cores can remain digital zero;
resampling/filter tails may extend slightly into their edges. MP3 need not
retain sample-exact silence near speech boundaries.

Defaults protect 120 ms before and after detections, use 10 ms fades, and
require an additional 80 ms silent core. Shorter gaps are left alone rather
than risking chopped words. Controls:

- `--pause-threshold 0.25`: lower = preserve more possible speech.
- `--pause-pre-ms 180`: protect more of quiet sentence starts.
- `--pause-post-ms 200`: keep longer word tails and decay.
- `--pause-min-ms 40`: allow shorter silent cores after margins and fades.
- `--no-pause-silence`: bypass this stage; no detector download. Combine with
  `--no-denoise` to avoid all AI model/runtime downloads.

The detector can miss whispers, breaths, or unusual speech. If it detects
**no speech**, cleanup fails open with a warning instead of muting the file.
Other detector failures stop processing, not silently switch to a level gate.
Reports in the `logs/` directory next to `producer.sh` include the attenuated
intervals on the original,
pre-trim timeline for inspection. With VAD enabled, optional trimming only
shortens near-zero cores; without it, legacy `gap_threshold_db` trimming can
mistake quiet speech for silence.

This stage does **not** restore consonants a denoiser has already removed.
Compare denoisers and try `--denoise-strength 0.65` if attacks remain soft;
a dry/wet blend trades some noise reduction for speech preservation.
A low, consistent room tone can sound more natural than dead silence.
Professional quality requires listening for artifacts, breaths, sibilance,
clipping and reverb, not just checking a meter. LUFS targets here are not
ACX RMS/noise-floor certification; no one-click chain guarantees every
recording meets a delivery specification.

Test the difference on your own audio before committing:

```bash
./producer.sh preview recording.wav --start 10 --duration 30
```

## Troubleshooting

- **Bootstrap fails:** the first run needs `uv` or Python 3 with venv support
  and network access to PyPI. Install the missing tool and rerun.
- **CUDA unavailable:** run `./producer.sh doctor`, check `nvidia-smi` and the
  driver, then retry `./producer.sh setup --device cuda`. Or use `--device cpu`.
- **Sounds watery or thin:** lower the denoise strength (`--denoise-strength
  0.65`) or compare profiles with `preview`.
- **Word attacks sound soft or ramp up:** AI denoisers can soften consonant
  onsets. Try `--denoiser clearvoice` (larger non-causal model that sees more
  attack context), lower `--denoise-strength`, or compare against
  `--no-denoise` to locate the culprit.

## License

[0BSD](LICENSE).

Upstream components (ClearVoice, DeepFilterNet, Silero VAD, ONNX Runtime,
PyTorch, FFmpeg, uv) carry their own licenses. Silero VAD is MIT-licensed:
https://github.com/snakers4/silero-vad/tree/v5.1