4590dc0fb9
Sermon-clean v0.2.0. Five new subcommands that round out the editor: - normalize (norm): Apply EBU R128 two-pass loudness normalization. Default -16 LUFS (podcast/YouTube). Configurable target LUFS, true peak, and loudness range. Output reports measured input loudness and applied gain offset. - silence-stats (ss): Quantitative summary of silence distribution — count, total/mean/median/longest silence, silence fraction, and silence runs per minute. Outputs JSON via --json. Useful for comparing recordings and picking the right threshold. - threshold-tune (tt): Auto-pick the silence threshold for the audio. Scans a set of candidate thresholds (default -25..-50), scores each against the target silence-runs-per-minute (default 4.0), picks the closest match. Shows the full scoring table. - denoise: Apply ffmpeg's afftdn filter for light FFT-based noise reduction. Configurable noise reduction dB (default 12) and noise floor dB (default -50). Output at 48kHz to match normalize. - batch: Run any of the subcommands across many files via glob. Output goes to --output-dir with --suffix (default '-fixed') and optional --extension override. Failures are collected, not raised — one bad file doesn't kill the whole batch. Implementation: - sermon_clean/processing.py: normalize_loudness + SilenceStats dataclass + silence_stats + threshold_tune. - sermon_clean/denoise.py: DenoiseResult + denoise. - sermon_clean/batch.py: run_batch + _expand_globs + _make_output_path. - sermon_clean/cli.py: 5 new cmd_* functions + 5 subparser registrations. Tests: - tests/test_processing.py (7 tests): silence-stats on silent vs loud, threshold-tune picks closest, normalize produces output + measures loud. - tests/test_denoise_batch.py (11 tests): denoise roundtrip, batch helpers (glob expansion, output naming), batch run with normalize + denoise, unknown subcommand raises, one-bad-file-in-batch continues. Total: 82/82 tests passing in 48s (was 64/64). Bumped version to 0.2.0. README updated: step-by-step workflow adds 1d-1g; subcommand table adds the 5 new commands; new 'Batch processing' section.
169 lines
6.8 KiB
Markdown
169 lines
6.8 KiB
Markdown
# sermon-clean
|
|
|
|
A one-shot CLI for sermon audio editing: **find** bad segments, **cut** them out, **paste** in ElevenLabs replacements. Built because doing this by hand every time is unbearable.
|
|
|
|
## Why
|
|
|
|
Recording a sermon is fine. Post-production is not. The current workflow needs:
|
|
|
|
1. Manually identify where the bad words are (the painful part — Whisper stalls on long audio, and eyeballing a waveform is imprecise)
|
|
2. Hand-write ffmpeg trim commands for each bad window
|
|
3. Render replacement clips via ElevenLabs
|
|
4. Hand-write the ffmpeg concat command
|
|
5. Manually upload to Dropbox
|
|
|
|
`sermon-clean` collapses steps 1-5 into one command (or a few, if you want to eyeball the audio first).
|
|
|
|
## Install
|
|
|
|
```bash
|
|
# Requires ffmpeg in PATH (sudo apt install ffmpeg on Debian/Ubuntu)
|
|
pip install sermon-clean
|
|
|
|
# Optional: for ElevenLabs auto-rendering
|
|
export ELEVENLABS_API_KEY=...
|
|
export ELEVENLABS_VOICE_ID=IYUnpZr9CQfSylOsOOBo # your cloned voice
|
|
|
|
# Optional: for auto-detect mode (transcribe + find bad words)
|
|
pip install "sermon-clean[auto]"
|
|
```
|
|
|
|
## Usage
|
|
|
|
### One-shot: explicit timestamps + replacement text
|
|
|
|
```bash
|
|
sermon-clean pipe sermon.ogg \
|
|
--bad "21:38-21:42:the actual sentence you meant to say" \
|
|
--bad "1450.3-1453.1:the corrected phrase" \
|
|
--elevenlabs-text \
|
|
-o sermon-fixed.ogg
|
|
```
|
|
|
|
### Step-by-step workflow
|
|
|
|
```bash
|
|
# 1. Look at the audio — see its shape + find silence gaps
|
|
sermon-clean scan sermon.ogg --width 100
|
|
|
|
# 1b. Or: get a tabular index of silence runs (good for picking splice points)
|
|
sermon-clean si sermon.ogg --silence-threshold -35 --silence-min-duration 0.5
|
|
|
|
# 1c. Or: multiband waveform — N seconds per row, makes timestamp counting trivial
|
|
sermon-clean mb sermon.ogg --band-seconds 60 --width 80
|
|
|
|
# 1d. Or: silence stats — count/mean/longest silence. Use this to pick the right
|
|
# threshold for the next recording of the same speaker/room setup.
|
|
sermon-clean silence-stats sermon.ogg
|
|
|
|
# 1e. Or: auto-tune the silence threshold based on expected pause density.
|
|
# Useful when the same speaker records in different rooms and the silence
|
|
# profile changes week to week.
|
|
sermon-clean threshold-tune sermon.ogg --target-spm 4.0
|
|
|
|
# 1f. Or: pre-flight normalization — bring the recording to broadcast-standard
|
|
# loudness before doing any other processing.
|
|
sermon-clean normalize sermon.ogg -o sermon-normalized.ogg
|
|
|
|
# 1g. Or: light denoise (FFT-based) if the recording has hiss / mic preamp noise.
|
|
sermon-clean denoise sermon.ogg -o sermon-denoised.ogg
|
|
|
|
# 2. Or: extract overlapping slices for manual review
|
|
sermon-clean slices sermon.ogg --output-dir ./slices \
|
|
--slice-seconds 5 --overlap-seconds 1
|
|
# Listen to ./slices/slice_000_0-00-0-05.ogg in your audio player.
|
|
# Filename embeds start/end timestamps.
|
|
|
|
# 3. Edit the JSON to mark bad segments + replacement text:
|
|
# segs.json:
|
|
# [
|
|
# {"start": 1298.0, "end": 1302.0, "reason": "misspoke", "replacement_text": "the actual sentence"},
|
|
# {"start": 1450.3, "end": 1453.1, "reason": "misspoke", "replacement_text": "the corrected phrase"}
|
|
# ]
|
|
|
|
# 4. Trim the original around the bad windows
|
|
sermon-clean cut sermon.ogg --segments-file segs.json
|
|
|
|
# 5. Render replacements (if you haven't already) and splice
|
|
sermon-clean paste sermon.ogg --segments-file segs.json \
|
|
--replacements "replacements/*.mp3" \
|
|
-o sermon-fixed.ogg
|
|
```
|
|
|
|
### Auto-detect mode (optional, requires `faster-whisper`)
|
|
|
|
```bash
|
|
sermon-clean auto sermon.ogg --bad-words "fuck,shit,damn" --output segs.json
|
|
# transcribes the audio, finds timestamps for any of the bad words,
|
|
# prints a suggested JSON file you can edit before splicing
|
|
```
|
|
|
|
## Subcommands
|
|
|
|
| Command | Purpose |
|
|
|---|---|
|
|
| `find` | Show audio metadata + silence gaps (no transcription) |
|
|
| `scan` | ASCII waveform + silence marks (no transcription) |
|
|
| `silence-index` (`si`) | Tabular list of silence runs with timestamps + position bar |
|
|
| `multiband` (`mb`) | Multi-row ASCII waveform with band-start labels for timestamp counting |
|
|
| `silence-stats` (`ss`) | Quantitative summary: count, mean, median, longest silence + density per minute |
|
|
| `threshold-tune` (`tt`) | Auto-pick the silence threshold that matches expected pause density |
|
|
| `normalize` (`norm`) | Apply EBU R128 loudness normalization (target LUFS, true peak, LRA) |
|
|
| `denoise` | Apply light FFT-based noise reduction (`afftdn`) for hiss / mic preamp noise |
|
|
| `slices` | Extract overlapping audio chunks for manual review |
|
|
| `cut` | Trim the original around bad windows (no splice) |
|
|
| `paste` | Splice pre-rendered replacements into the trimmed original |
|
|
| `auto` | Transcribe + find bad-word timestamps |
|
|
| `pipe` | Run cut + paste in one command |
|
|
| `batch` | Run any of the above across many files |
|
|
|
|
### Batch processing
|
|
|
|
Apply the same operation to many files at once:
|
|
|
|
```bash
|
|
# Normalize every sermon from this Sunday
|
|
sermon-clean batch normalize 'sermons/*.ogg' --output-dir fixed/ --suffix=-normalized
|
|
|
|
# Denoise a batch of older recordings
|
|
sermon-clean batch denoise 'archive/*.wav' --output-dir fixed/ --suffix=-dn
|
|
|
|
# Silence stats for every recording (no output files — runs the stats print)
|
|
sermon-clean batch silence-stats 'sermons/*.ogg' --output-dir stats/
|
|
```
|
|
|
|
Failures are collected, not raised: if one file is corrupt, the rest still process.
|
|
|
|
## How it works
|
|
|
|
- **Find** → `ffmpeg silencedetect` for natural breath pauses, plus your explicit timestamps
|
|
- **Cut** → trim the original into N+1 good segments using ffmpeg, re-encoding to the source codec (no `wav` intermediate — bitrate-matched)
|
|
- **Paste** → ElevenLabs renders + ffmpeg concat with bit-exact `-c copy` (no audible clicks at splice boundaries)
|
|
- **Verify** → ffprobe the output duration against expected; if it drifted >1s, the splice silently changed the runtime
|
|
|
|
## Why this exists
|
|
|
|
The previous workflow (`audio-splice-workflow.md` in krystie-profile) was a 5-step manual recipe that's already broken twice. Each fix took 30+ minutes of bash. This package makes the same workflow a one-line command.
|
|
|
|
## Performance
|
|
|
|
- `scan` on a 27-min audio: ~50s (Opus decode is the bottleneck on this CPU)
|
|
- `slices` on a 27-min audio with 5-sec slices: ~50s
|
|
- `auto` on a 27-min audio: ~10-20 min with `tiny.en` model on CPU. Use `base.en` or `small.en` for better accuracy at 2-4x the time. Whisper `tiny.en` is the right model for finding a known bad-word list — you don't need higher accuracy than "did the word 'fuck' appear at all."
|
|
- `normalize` on a 27-min audio: ~30-60s (two-pass EBU R128 measurement + apply)
|
|
- `denoise` on a 27-min audio: ~45-90s (FFT pass over the whole file)
|
|
- `silence-stats` and `threshold-tune` on a 27-min audio: ~5s (just runs silencedetect with multiple thresholds)
|
|
- `batch`: adds ~5s of subprocess overhead per file on top of the operation cost
|
|
|
|
## Development
|
|
|
|
```bash
|
|
git clone https://git.sami/sami7777/sermon-clean.git
|
|
cd sermon-clean
|
|
pip install -e ".[test,auto]"
|
|
pytest # 61 tests, ~17s
|
|
```
|
|
|
|
## License
|
|
|
|
MIT — see LICENSE. |