How pack_transcripts.py Segments Phrases by Silence or Speaker Change
The group_into_phrases function in helpers/pack_transcripts.py detects phrase boundaries by monitoring three conditions: explicit silence entries (spacing objects) exceeding a configurable threshold, changes in speaker_id between consecutive tokens, and temporal gaps between words that exceed the silence threshold even without explicit spacing annotations.
The browser-use/video-use repository provides a specialized transcription pipeline that converts raw Scribe output into compact, phrase-level markdown files. The pack_transcripts.py script performs the critical segmentation step, analyzing word-level JSON to identify natural breaks in speech. This article examines the exact implementation details of how the script determines when one phrase ends and another begins.
The Core Segmentation Logic
The segmentation engine resides in the group_into_phrases function, defined at lines 38-44 of helpers/pack_transcripts.py. This function iterates through the list of word-level entries from the Scribe transcript and maintains a running buffer of current words, tracking the active speaker and start time.
When specific conditions are met during iteration, the function triggers a flush() operation that finalizes the current phrase and resets the buffers for the next segment. The decision logic implements three distinct detection rules to handle various transcript edge cases.
Three Rules for Phrase Boundary Detection
The script employs a multi-layered approach to detect phrase boundaries, ensuring robust segmentation even with noisy or incomplete transcript data.
Silence Threshold Detection via Spacing Entries
Scribe transcripts include explicit spacing entries representing silent intervals between words. The script detects these entries by checking if t == "spacing" (lines 90-99) and calculates the gap duration as gap = end - start.
When gap >= silence_threshold (default 0.5 seconds), the script immediately calls flush() to end the current phrase. This handles natural pauses in speech where the speaker stops for half a second or longer.
Speaker Change Detection via speaker_id
Each word or audio event in the transcript carries a speaker_id field. The script maintains a current_speaker variable tracking the active speaker for the phrase under construction.
At lines 108-110, the code checks: if current_speaker is not None and speaker is not None and speaker != current_speaker: flush(). When the incoming token's speaker differs from the active phrase's speaker, the current phrase is finalized and a new phrase begins with the new speaker ID.
Safety Gap Detection Between Tokens
As a defensive measure against transcripts missing explicit spacing entries, the script implements a fallback gap detection mechanism. It maintains a prev_end variable tracking the end timestamp of the previous token.
At lines 112-114, the code calculates start - prev_end and compares it against the silence threshold. If this gap meets or exceeds the threshold, flush() triggers even without an explicit spacing entry, ensuring long pauses are never swallowed into adjacent phrases.
How the flush() Function Constructs Phrases
When any boundary condition triggers, the nested flush() helper (lines 54-82) assembles the accumulated words into a structured phrase dictionary. The construction process follows four steps:
- Text Collection: Concatenates words from
current_words, strips whitespace, and wraps audio events in parentheses. - Punctuation Cleaning: Fixes stray spaces before commas and periods using regex cleanup (line 74).
- Timestamp Determination: Sets
startfrom the first word's timestamp andendfrom the last word'sendfield (or itsstartifendis missing) (line 75). - Phrase Assembly: Appends a dictionary containing
start,end,text, andspeaker_idto the results list (lines 76-80).
After flushing, the script resets current_words, current_start, and current_speaker to None, preparing the buffers for the next phrase accumulation.
Complete Processing Pipeline
The script executes a three-stage pipeline to transform raw transcripts:
- JSON Parsing: Loads the transcript file and extracts the word list via
words = data.get("words", [])(line 28). - Phrase Grouping: Calls
group_into_phrases(words, silence_threshold=0.5)to apply the segmentation rules (line 30). - Markdown Rendering: The
render_markdownfunction (lines 59-61) formats each phrase as[start-end]with optional speaker tags, producing the finaltakes_packed.mdoutput.
Practical Usage Examples
Command-Line Segmentation
Run the script from the repository root to process all transcripts in an edit directory:
python helpers/pack_transcripts.py --edit-dir /path/to/edit --silence-threshold 0.6
This reads all *.json files under <edit>/transcripts/, applies the segmentation rules with a 0.6-second silence threshold, and writes takes_packed.md in the edit directory.
Programmatic Phrase Extraction
Import and use the segmentation logic directly in Python:
from pathlib import Path
import json
from helpers.pack_transcripts import group_into_phrases
# Load a transcript JSON file
transcript_path = Path("my_edit/transcripts/scene1.json")
data = json.loads(transcript_path.read_text())
words = data["words"]
# Segment into phrases (silence threshold = 0.5s)
phrases = group_into_phrases(words, silence_threshold=0.5)
for p in phrases:
print(f"[{p['start']:.2f}-{p['end']:.2f}] {p.get('speaker_id','?')}: {p['text']}")
The function returns a list of dictionaries: [{ "start": 1.23, "end": 3.45, "text": "...", "speaker_id": "speaker_0" }, ...].
Manual Markdown Generation
To render phrases to markdown format programmatically:
from helpers.pack_transcripts import render_markdown
# entries is a list of (name, duration, phrases) tuples
markdown = render_markdown(entries, silence_threshold=0.5)
print(markdown)
This produces the same formatted output as the CLI, with timestamps and speaker annotations.
Summary
- Three detection rules drive phrase segmentation: explicit spacing entries (≥0.5s default),
speaker_idchanges, and inferred gaps between consecutive tokens. - The
flush()function (lines 54-82) handles phrase finalization by cleaning text, fixing punctuation spacing, and calculating accurate start/end timestamps. - The
group_into_phrasesfunction serves as the primary API, accepting a word list and silence threshold parameter to return structured phrase dictionaries. - Source location: All logic resides in
helpers/pack_transcripts.pywithin thebrowser-use/video-userepository.
Frequently Asked Questions
What is the default silence threshold in pack_transcripts.py?
The default silence threshold is 0.5 seconds (500 milliseconds). This value is passed to group_into_phrases as the silence_threshold parameter and applies to both explicit spacing entries and inferred gaps between consecutive words.
How does pack_transcripts.py handle transcripts without speaker IDs?
If speaker_id fields are missing or None, the speaker change detection at lines 108-110 evaluates to False due to the is not None checks. The script continues processing but omits speaker labels from the output markdown, focusing solely on temporal gaps for segmentation.
Can I adjust phrase segmentation without modifying the source code?
Yes. Use the --silence-threshold CLI argument to specify a custom threshold in seconds (e.g., --silence-threshold 1.0 for 1-second pauses). When calling group_into_phrases programmatically, pass your desired threshold as the second argument: group_into_phrases(words, silence_threshold=0.3).
Where does the raw word-level data processed by pack_transcripts.py originate?
The input data comes from Scribe transcription JSON files, typically generated by helpers/transcribe.py or batch-processed via helpers/transcribe_batch.py in the same repository. These files contain word-level entries with start, end, text, and optional speaker_id fields, plus explicit spacing entries representing silent intervals.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →