How to Generate SRT Subtitles with Offset Timelines After Segment Concatenation in Video-Use

Video-use accumulates segment durations into a running seg_offset variable to shift transcript timestamps onto the final concatenated timeline, outputting a master SRT file via the build_master_srt function in helpers/render.py.

The browser-use/video-use repository provides a complete pipeline for extracting video segments, concatenating them, and generating accurate SRT subtitles that reflect the final output timeline. When multiple clips are stitched together, the subtitles must account for the cumulative duration of preceding segments to maintain synchronization. This article explains the exact mechanism used to calculate these offsets and produce the master subtitle file.

The Subtitle Generation Pipeline

The entire process is orchestrated by the build_master_srt function located in helpers/render.py at line 315. This routine is invoked automatically when you pass the --build-subtitles flag to the render script. It processes an Edit Decision List (EDL) to determine which segments appear in the final video and adjusts all subtitle timestamps accordingly.

Parsing the Edit Decision List (EDL)

The function iterates over the ranges array in the EDL JSON file. Each range represents one extracted clip with a specific start and end time from a source video:

for r in edl["ranges"]:
    src_name = r["source"]
    seg_start = float(r["start"])
    seg_end   = float(r["end"])
    seg_duration = seg_end - seg_start

During this iteration, the code maintains a seg_offset variable initialized to 0.0 at line 326. This accumulator tracks the total duration of all previously processed segments.

Accumulating Segment Offsets

The offset accumulation happens immediately after processing each range. Even if a segment lacks a transcript, the offset increases to ensure subsequent subtitles align correctly with the concatenated timeline. As implemented in the source code at line 326, the offset preserves the exact position of each clip within the final output.

Loading and Filtering Transcripts

For each EDL range, the system attempts to load a transcript JSON file from edit_dir/transcripts/<source>.json. If the file is missing, the range is skipped, but seg_offset is still incremented to maintain timeline integrity (line 335).

When a transcript exists, the helper function _words_in_range (line 300) scans the word-level timing data to select only those utterances that fall within the current segment's boundaries:

words = _words_in_range(transcript, seg_start, seg_end)

Chunking and Timestamp Shifting

Selected words are grouped into caption chunks of at most two words, breaking early on punctuation marks defined by PUNCT_BREAK (line 443). This creates readable captions that align with natural speech pauses.

The critical offset calculation occurs at line 562. For each word chunk, the code first determines the local time relative to the source segment, then shifts it by the cumulative seg_offset:

out_start = max(0.0, local_start - seg_start) + seg_offset
out_end   = max(0.0, local_end   - seg_start) + seg_offset

This math ensures that out_start and out_end represent absolute positions on the final concatenated video timeline, not the original source timestamps.

Formatting and Writing the SRT File

The helper _srt_timestamp (line 292) converts floating-point second counts into the standard HH:MM:SS,mmm format required by the SRT specification. After processing all EDL ranges, the complete subtitle entries are sorted by start time and written to edit_dir/master.srt (line 776).

Code Implementation Details

The offset logic relies on precise arithmetic to handle edge cases such as segments starting mid-sentence. The max(0.0, ...) guard prevents negative timestamps while the seg_offset accumulator ensures continuity across clip boundaries.

Here is the core timestamp transformation as implemented in the source:


# seg_offset accumulates the duration of all previous segments

seg_offset = 0.0

for r in edl["ranges"]:
    # ... load transcript and get words ...

    
    for chunk in word_chunks:
        local_start = chunk[0]["start"]
        local_end   = chunk[-1]["end"]
        
        # Shift from source-relative to output-relative time

        out_start = max(0.0, local_start - seg_start) + seg_offset
        out_end   = max(0.0, local_end   - seg_start) + seg_offset
        
        # Convert to SRT format

        start_ts = _srt_timestamp(out_start)
        end_ts   = _srt_timestamp(out_end)

The SUB_FORCE_STYLE constant defined at line 41 in helpers/render.py specifies the default subtitle appearance (font, size, margins) when the SRT is later burned into the video using ffmpeg.

Command-Line Usage

To generate the offset-adjusted subtitle file during rendering, invoke the script with the --build-subtitles flag:


# Render video and generate master.srt with corrected timelines

python helpers/render.py edl.json -o final.mp4 --build-subtitles

This command processes the EDL, calculates all offsets, and deposits master.srt in the edit_dir directory.

To inspect the generated file:

1
00:00:00,000 --> 00:00:02,400
THIS IS

2
00:00:02,400 --> 00:00:04,800
AN EXAMPLE

3
00:00:04,800 --> 00:00:05,600
OF CAPTION

Notice that the timestamps reflect the concatenated timeline—the second cue starts at 00:00:02,400 because the first segment had a duration of 2.4 seconds.

To burn these subtitles into the final video with the forced style:

ffmpeg -i base.mp4 \
  -vf "subtitles='edit/master.srt':force_style='FontName=Helvetica,FontSize=18,Bold=1,MarginV=90'" \
  -c:a copy final_with_subs.mp4

Summary

  • Offset Accumulation: The seg_offset variable tracks cumulative segment duration to align subtitles with the concatenated output timeline.
  • Core Function: build_master_srt in helpers/render.py (line 315) orchestrates the entire pipeline.
  • Timestamp Math: Each cue's start and end times are shifted by seg_offset after being normalized to the segment start (line 562).
  • Input Data: Per-source transcripts are loaded from edit_dir/transcripts/<source>.json and filtered by the _words_in_range helper.
  • Output: A single master.srt file containing chronologically sorted, offset-corrected captions ready for ffmpeg overlay.

Frequently Asked Questions

What happens if a video segment has no transcript file?

If the expected JSON transcript is missing from edit_dir/transcripts/, the range is skipped without generating subtitles, but seg_offset is still increased by the segment duration (line 335). This ensures that subsequent subtitles remain synchronized with the video timeline even when gaps exist in the transcript coverage.

How does the system handle words that span across segment boundaries?

The _words_in_range function (line 300) selects words whose temporal intervals overlap the segment's start and end times. When a word is split across segments, it appears only in the segment where its start time falls, ensuring no duplicate captions while maintaining the offset continuity through the seg_offset accumulator.

Can I adjust the number of words per caption line?

Yes, the chunking logic in helpers/render.py groups words into captions of at most two words by default (line 443). You can modify the chunking parameters or the PUNCT_BREAK logic in the source to allow longer lines, though this requires editing the _chunk_words implementation directly.

Why are my subtitles appearing at the wrong time in the final video?

Incorrect timing usually indicates that seg_offset was not properly accumulated, which happens if the EDL ranges are processed out of order or if segment durations are miscalculated. Ensure that your edl.json contains correct start and end values for each range, and verify that --build-subtitles was used during rendering to trigger the offset calculation logic.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →