mixing.transcript

Transcript-driven media editing.

Speech-to-text via ElevenLabs Scribe, filler-word detection, and timeline-aware cuts of audio/video. Produces editable transcripts (plain prose, SRT, word-level JSON) and applies cuts via ffmpeg.

The HTTP client uses stdlib only (no requests or elevenlabs SDK dependency), so the package adds no required deps. The runtime needs ffmpeg on PATH for media operations, and an ElevenLabs API key (env var ELEVENLABS_API_KEY or explicit api_key=) for transcription.

Quick start:

>>> from mixing.transcript import remove_fillers
>>> result = remove_fillers("input.mov", "out/")
>>> print(result.cleaned_media)

Lower-level building blocks:

>>> from mixing.transcript import (
...     transcribe, build_cuts, keeps_from_cuts,
...     words_to_srt, words_to_prose, apply_keeps,
... )

Functions

transcribe(audio, *[, api_key, model_id, ...])

Transcribe audio with ElevenLabs Scribe.

default_cache_dir()

Default on-disk cache for Scribe responses.

is_filler(item, *[, fillers, audio_events])

Return True if item is a filler word or a removable audio event.

build_cuts(words, *[, fillers, ...])

Compute time ranges to REMOVE.

keeps_from_cuts(cuts, duration)

Return ranges to KEEP given the cut ranges and total duration.

normalize_token(text)

Lowercase and strip non-alpha so "Uh," -> "uh".

fmt_srt_time(seconds)

Back-compat aliases — historically these names lived in different modules.

words_to_srt(words, *[, max_chars, ...])

Render an SRT from a Scribe word list (no filler removal, no remapping).

words_to_srt_remapped(words, cuts, *[, ...])

SRT aligned to a post-cut timeline.

words_to_prose(words, *[, paragraph_pause, ...])

Render a Scribe word list as plain prose, with paragraph breaks on long pauses.

remap_time_after_cuts(t, cuts)

Map t from the original timeline onto the post-cut timeline.

extract_audio(input_path, output_path, *[, ...])

Extract a mono mp3 from input_path (suitable for STT).

apply_keeps(input_path, output_path, keeps, *)

Re-encode input_path keeping only the listed time ranges.

srt_for_media(media, *[, srt_path, reuse, ...])

Return (srt_text, srt_path) for media, transcribing if needed.

remove_fillers(input_media, output_dir, *[, ...])

End-to-end filler removal.

Classes

FillerRemovalResult(cleaned_media, ...[, ...])

Paths and computed ranges produced by remove_fillers().

class mixing.transcript.FillerRemovalResult(cleaned_media, transcript_md, transcript_srt, cleaned_md, cleaned_srt, scribe_json, cuts_json, keeps_json, duration, cuts=list(), keeps=list())[source]

Bases: object

Paths and computed ranges produced by remove_fillers().

mixing.transcript.apply_keeps(input_path, output_path, keeps, *, video_codec='libx264', audio_codec='aac', crf=20, preset='medium', audio_bitrate='160k', faststart=True, overwrite=True)[source]

Re-encode input_path keeping only the listed time ranges.

Parameters:
  • input_path (Union[str, Path]) – Input audio/video file.

  • output_path (Union[str, Path]) – Output file (extension picks the container).

  • keeps (Sequence[dict]) – [{"start": float, "end": float}, ...] time ranges in seconds.

  • audio_bitrate (str) – Re-encode knobs forwarded to ffmpeg.

  • faststart (bool) – Add -movflags +faststart (mp4 web-streaming).

  • overwrite (bool) – Pass -y to ffmpeg.

Return type:

Path

Returns:

The output path.

mixing.transcript.build_cuts(words, *, fillers=frozenset({'ah', 'eh', 'er', 'erm', 'hmm', 'mhm', 'mm', 'mmm', 'uh', 'uhh', 'uhm', 'um', 'umm'}), audio_events=frozenset({'(coughs)'}), absorb_trailing_space=True, merge_gap=0.08)[source]

Compute time ranges to REMOVE.

Parameters:
  • words (Sequence[dict]) – Scribe words array.

  • fillers (Iterable[str]) – Override the default filler set.

  • audio_events (Iterable[str]) – Override the default audio-event set.

  • absorb_trailing_space (bool) – Extend each cut to the end of the trailing "spacing" token, which avoids leaving a stranded pause.

  • merge_gap (float) – Merge adjacent cuts when their gap is below this many seconds.

Return type:

list[dict]

Returns:

List of {"start": float, "end": float, "label": str} dicts, sorted by start time and non-overlapping.

mixing.transcript.default_cache_dir()[source]

Default on-disk cache for Scribe responses.

Honors $MIXING_TRANSCRIPT_CACHE_DIR, then $XDG_CACHE_HOME, then ~/.cache/. Final segment is always mixing/transcript.

Return type:

Path

mixing.transcript.extract_audio(input_path, output_path, *, sample_rate=16000, channels=1, bitrate='96k', overwrite=True)[source]

Extract a mono mp3 from input_path (suitable for STT).

Returns the output path.

Return type:

Path

mixing.transcript.fmt_srt_time(seconds)

Format a time in seconds as an SRT timestamp HH:MM:SS,mmm.

Milliseconds are rounded (not truncated), with carry handled correctly, and negative inputs clamp to zero.

>>> seconds_to_srt_time(2592.187)
'00:43:12,187'
>>> seconds_to_srt_time(1.5)
'00:00:01,500'
>>> seconds_to_srt_time(-3)
'00:00:00,000'
Return type:

str

mixing.transcript.is_filler(item, *, fillers=frozenset({'ah', 'eh', 'er', 'erm', 'hmm', 'mhm', 'mm', 'mmm', 'uh', 'uhh', 'uhm', 'um', 'umm'}), audio_events=frozenset({'(coughs)'}))[source]

Return True if item is a filler word or a removable audio event.

Return type:

bool

mixing.transcript.keeps_from_cuts(cuts, duration)[source]

Return ranges to KEEP given the cut ranges and total duration.

Return type:

list[dict]

mixing.transcript.normalize_token(text)[source]

Lowercase and strip non-alpha so "Uh," -> "uh".

Return type:

str

mixing.transcript.remap_time_after_cuts(t, cuts)[source]

Map t from the original timeline onto the post-cut timeline.

If t falls inside a cut, snaps to the moment that cut would have started in the post-cut timeline.

Return type:

float

mixing.transcript.remove_fillers(input_media, output_dir, *, output_media=None, fillers=frozenset({'ah', 'eh', 'er', 'erm', 'hmm', 'mhm', 'mm', 'mmm', 'uh', 'uhh', 'uhm', 'um', 'umm'}), audio_events=frozenset({'(coughs)'}), api_key=None, scribe_kwargs=None, apply_keeps_kwargs=None, keep_intermediate=True, scribe_data=None)[source]

End-to-end filler removal.

Parameters:
  • input_media (Union[str, Path]) – Video or audio file to clean.

  • output_dir (Union[str, Path]) – Directory to write transcripts and intermediate files into. Created if missing.

  • output_media (Union[str, Path, None]) – Output cleaned media path. Defaults to output_dir / f"{stem}.cleaned.mp4".

  • audio_events (Iterable[str]) – Override the default filler / event sets.

  • api_key (str | None) – ElevenLabs API key (else env ELEVENLABS_API_KEY).

  • scribe_kwargs (Optional[Mapping[str, object]]) – Extra kwargs forwarded to transcribe().

  • apply_keeps_kwargs (Optional[Mapping[str, object]]) – Extra kwargs forwarded to apply_keeps().

  • keep_intermediate (bool) – If False, delete the extracted audio file.

  • scribe_data (dict | None) – If provided, skip the network call and use this dict (must match Scribe’s response shape). Useful for tests / replays.

Return type:

FillerRemovalResult

Returns:

A FillerRemovalResult with file paths and the computed cut/keep ranges.

mixing.transcript.srt_for_media(media, *, srt_path=None, reuse=True, refresh=False, cache=True, max_chars=80, **transcribe_kwargs)[source]

Return (srt_text, srt_path) for media, transcribing if needed.

Parameters:
  • media (Union[str, Path]) – Audio or video file (Scribe extracts audio from video).

  • srt_path (Union[str, Path, None]) – Where the SRT lives/should be written. Defaults to the media path with an .srt suffix.

  • reuse (bool) – When True (default) and the SRT already exists, read and return it without re-transcribing.

  • refresh (bool) – Force re-transcription even if the SRT exists (overwrites it).

  • cache (bool) – Pass-through to mixing.transcript.transcribe()’s on-disk response cache.

  • max_chars (int) – Max characters per SRT cue when generating.

  • transcribe_kwargs – Extra args forwarded to transcribe (e.g. language_code, diarize).

Return type:

tuple[str, Path]

Returns:

The SRT text and the path it lives at.

mixing.transcript.transcribe(audio, *, api_key=None, model_id='scribe_v1', timestamps_granularity='word', tag_audio_events=True, diarize=False, language_code=None, extra_fields=None, timeout=600.0, cache=False, refresh=False)[source]

Transcribe audio with ElevenLabs Scribe.

Parameters:
  • audio (Union[str, Path, bytes]) – Path to an audio/video file, or raw bytes.

  • api_key (str | None) – API key. Falls back to env var ELEVENLABS_API_KEY.

  • model_id (str) – Scribe model id (currently scribe_v1).

  • timestamps_granularity (str) – "word" (default), "character", or "none".

  • tag_audio_events (bool) – Whether to surface (laughs) / (coughs) etc.

  • diarize (bool) – Speaker diarization on/off.

  • language_code (str | None) – Optional BCP-47 hint to skip language detection.

  • extra_fields (Optional[Mapping[str, str]]) – Additional multipart fields to send (forward compatible).

  • timeout (float) – Total request timeout in seconds.

  • cache (Union[bool, str, Path]) – False (default) → no cache. True → use default_cache_dir(). A path → use that directory. The cache key is the SHA-256 of the audio bytes plus all request parameters that affect the response.

  • refresh (bool) – When True and cache is enabled, force a re-call and overwrite the cached entry. Useful for invalidating stale entries when Scribe upgrades its model.

Return type:

dict[str, Any]

Returns:

The raw JSON response from ElevenLabs (a dict). When timestamps_granularity="word" (default), the response contains a "words" list where each entry has at minimum {"text", "start", "end", "type", "confidence"}. Non-word events (type != "word") include (laughs) etc. when tag_audio_events=True.

Raises:
  • RuntimeError – No API key supplied and ELEVENLABS_API_KEY unset.

  • HTTPError – Request failed (4xx/5xx).

mixing.transcript.words_to_prose(words, *, paragraph_pause=1.2, drop_fillers=False, fillers=frozenset({'ah', 'eh', 'er', 'erm', 'hmm', 'mhm', 'mm', 'mmm', 'uh', 'uhh', 'uhm', 'um', 'umm'}), audio_events=frozenset({'(coughs)'}))[source]

Render a Scribe word list as plain prose, with paragraph breaks on long pauses.

Parameters:
  • words (Sequence[dict]) – Scribe words array.

  • paragraph_pause (float) – Insert a blank line when the gap between two consecutive non-filler words exceeds this many seconds.

  • drop_fillers (bool) – If True, omit filler words and removable audio events.

  • audio_events (Iterable[str]) – Override the default filler / event sets.

Return type:

str

mixing.transcript.words_to_srt(words, *, max_chars=80, sentence_endings='.?!')[source]

Render an SRT from a Scribe word list (no filler removal, no remapping).

Return type:

str

mixing.transcript.words_to_srt_remapped(words, cuts, *, max_chars=80, sentence_endings='.?!', drop_fillers=True, fillers=frozenset({'ah', 'eh', 'er', 'erm', 'hmm', 'mhm', 'mm', 'mmm', 'uh', 'uhh', 'uhm', 'um', 'umm'}), audio_events=frozenset({'(coughs)'}))[source]

SRT aligned to a post-cut timeline.

Each word’s timestamp is shifted earlier by the cumulative duration of all cuts that ended before it, so the resulting SRT drops in over the cleaned media.

Return type:

str

Modules

fillers

Filler-word detection over Scribe-shaped word lists.

formats

Transcript output formats: SRT, plain prose, time remapping.

media

ffmpeg-backed audio extraction and timeline-cut application.

persist

Reuse-or-create the SRT that lives next to a media file.

pipeline

High-level filler-removal pipeline.

scribe

ElevenLabs Scribe (speech-to-text) HTTP client.