Skip to content

About

Split a voice recording into one clip per utterance, cutting on the silence between them.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

splitvoice

Split a voice recording into one clip per utterance, cutting on the silence between them.

Point it at a file or a folder, tell it how long a pause has to be before it counts as a break, and it writes numbered clips plus a manifest of where each one came from. Useful for cutting a take into words, sentences, or prompts — TTS/ASR datasets, voice-over reels, sample libraries, transcript-aligned chunks.

  • One dependency: ffmpeg. No Python audio stack, nothing to compile. Detection and cutting both run through ffmpeg, so files are never fully decoded into memory.
  • Dry run first. Without -o it prints the split it would make, so you can tune the threshold without writing a thing.
  • Every knob is a flag, and every flag can be a default or a per-file rule in a config file.
  • Optional: transcribe once and name each clip after the words in it.
$ splitvoice interview.wav
splitvoice 0.1.0  |  1 file(s)  |  dry run, nothing will be written

------------------------------------------------------------------------
[1/1] interview.wav
  00:00:41.320  pcm_s16le  44100 Hz  1 ch
  kind=auto  min_silence=0.7s  thresh=-40.0dBFS
  17 silence gaps -> 18 segments
     #         start           end    dur(s)  clip
     1  00:00:00.000  00:00:02.118      2.12  001_untitled.wav
     2  00:00:02.940  00:00:05.512      2.57  002_untitled.wav
     ...

Install

ffmpeg must be on your PATH:

brew install ffmpeg          # macOS
sudo apt install ffmpeg      # Debian/Ubuntu
winget install Gyan.FFmpeg   # Windows

Then:

pip install splitvoice
# or, for the optional transcription feature:
pip install "splitvoice[stt]"

From a clone:

git clone https://github.com/ulvxa/splitvoice
cd splitvoice
pip install -e ".[dev]"

Prefer not to install it at all? The package runs straight from the source tree with python -m splitvoice ....

Usage

splitvoice recording.wav                        # preview the split
splitvoice recording.wav -o clips/              # write the clips
splitvoice takes/ -o clips/ -r                  # a whole folder, subfolders included
splitvoice recording.wav -o clips/ -d 0.35      # split on shorter pauses
splitvoice recording.wav -o clips/ -t auto      # derive the threshold from the file
splitvoice recording.wav -o clips/ -k sentences # start from the sentence preset

Clips land in clips/<recording name>/001_....wav, next to a manifest.csv and manifest.json listing every segment's source, start, end, duration and filename. Use --flat to put everything in one folder instead (filenames get the recording name as a prefix, so they stay unique).

The two knobs that matter

Splitting is decided by exactly two numbers:

Flag Means Turn it…
-d, --min-silence-len how long a pause must last to count as a break (seconds) down if utterances get glued together, up if one utterance gets chopped mid-word
-t, --silence-thresh how quiet audio must be to count as silence (dBFS) up toward -25 for noisy/roomy recordings, down toward -55 for a clean studio take

If you get 0 segments, the file never got quiet enough — raise the threshold (-t -30). If you get 1 giant segment, same fix, or lower -d. If you get far too many, raise -d or add --min-segment-len 0.3 to throw away scraps.

-t auto measures the file's mean loudness and sets the threshold 12 dB below it, which is a good starting point for anything recorded at an unfamiliar level.

Kinds

--kind describes what a recording holds. It is only a starting point — a bundle of default values that any explicit flag or config entry overrides.

Kind min silence min segment also
auto (default) 0.7 s — plain silence splitting
letters 0.5 s 0.05 s one-token labels, uniformity check
words 0.7 s 0.1 s uniformity check, resegment when transcribing
phrases 0.9 s 0.2 s resegment when transcribing
sentences 1.2 s 0.3 s resegment when transcribing
paragraphs 2.0 s 0.5 s merges segments < 0.3 s apart
splitvoice alphabet.wav -o letters/ --kind letters
splitvoice audiobook.m4a -o chapters/ --kind paragraphs

Output shape

--format mp3            # clip container/extension (default wav)
--codec libmp3lame      # force a specific ffmpeg encoder
--sample-rate 16000     # resample on the way out
--mono                  # downmix
--padding 0.15          # audio kept on each side of a cut (default 0.1)
--name-mode index       # 001.wav instead of 001_label.wav; `label` drops the number
--ascii-names           # strip diacritics from filenames
--manifest csv          # csv | json | both | none
--json                  # print the manifest to stdout and nothing else

--json makes the tool scriptable:

splitvoice take.wav --json | jq '[.[] | select(.duration_s > 3)] | length'

Config file

Anything you would type as a flag can live in splitvoice.toml — in the current folder, next to your audio, or passed with -c. [defaults] applies to every file; each [[profile]] applies to files whose name matches its glob. Later matching profiles win, and a flag on the command line beats all of it.

[defaults]
kind = "sentences"
silence_thresh = -38.0
padding = 0.15
format = "wav"

[[profile]]
match = "*_letters*"        # matched against the filename and the stem
kind = "letters"
min_silence_len = 0.5

[[profile]]
match = "noisy_*"
silence_thresh = -28.0

[[profile]]
match = "scratch_*"
skip = true                 # ignored unless you pass --all

Valid keys: kind, min_silence_len, silence_thresh, min_segment_len, merge_gap, padding, format, transcribe, resegment, sentence_gap, anchor, label_regex, max_label_words, uniform, skip. Unknown keys are an error rather than a silent no-op. JSON (splitvoice.json) works too, with "profiles": [...].

Naming clips after their words (optional)

With --transcribe, each file is sent once to ElevenLabs speech-to-text and the words are mapped onto the segments, so clips come out as 003_good_morning.wav instead of 003_untitled.wav. The transcript is cached in .transcripts/, so re-running with different splitting flags costs nothing extra.

export ELEVENLABS_API_KEY=...          # or put it in a .env file
splitvoice take.wav -o clips/ --transcribe --language en

Related flags:

--model scribe_v2         # STT model id
--language en             # omit to auto-detect
--keyterm "Kubernetes"    # bias the transcript toward a term (repeatable)
--keyterms-file terms.txt # one term per line
--refresh-transcript      # ignore the cache
--label-regex "say (\w+)" # capture groups become the filename
--max-label-words 4       # cap how many words end up in a filename
--align sequential        # force 1 word per segment instead of timestamp overlap

--resegment rebuilds the segments from the transcript instead of trusting silence alone: words are grouped into utterances by the gaps between them (--sentence-gap), then each group's edges snap out to the nearest silence boundaries. This fixes both halves of the usual problem — utterances merged because the pause fell under the threshold, and utterances split because the speaker paused mid-sentence.

When a recording repeats the same carrier phrase, --anchor is more reliable than any gap: give it a regex matching the first word of each repetition and it starts a new utterance there, whatever the timing.

splitvoice prompts.wav -o clips/ --transcribe --resegment \
  --anchor '^[Ss]ay$' --label-regex 'say (.+?)[.!?]*$'

--uniform flags segments whose word count differs from the file's usual count with a ! in the table — a quick way to spot a bad cut in a recording where every take should have the same shape.

Library use

from splitvoice import audio, segment

duration = audio.probe("take.wav")["duration"]
silences = audio.detect_silence("take.wav", min_silence_len=0.7,
                                threshold_db=-40, duration=duration)
segments = segment.invert_silence(silences, duration)

for i, seg in enumerate(segment.pad(segments, 0.1, duration), 1):
    audio.extract_clip("take.wav", f"{i:03d}.wav", seg.start, seg.end)

Development

pip install -e ".[dev]"
pytest
ruff check .

The end-to-end tests synthesize their own audio with ffmpeg and skip if it is missing.

Exit codes

0 success · 1 finished with per-file failures · 2 bad usage or environment · 130 interrupted.

License

MIT — see LICENSE.

About

Split a voice recording into one clip per utterance, cutting on the silence between them.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages