Split a long video into Shorts episodes: Python trim ranges

Generate back-to-back video trim ranges for a long vertical video with a tail rule, so no episode is a sliver. Respects the 1800 s source and 900 s output caps.

6 min readSume
All posts

Why a tail rule

Cutting a long vertical video into equal episodes is easy until the last piece. A 412-second source cut at 60 seconds leaves a 52-second tail; a 361-second source leaves a one-second tail that is not an episode and, below 0.2 seconds, is not a valid trim at all. Video trim requires a duration between 0.2 and 900 seconds, and accepts a source up to 1800 seconds, so the splitter has to deal with both limits and with the tail.

The October platform roundup reports Shorts series, with seasons, episodes and sequential playback, rolling out from 23 September. An episode run needs the same range logic every time, and a function you can unit test is better than counting on a spreadsheet.

The limits the splitter respects

Each constraint below is printed on the trim page. The script raises on the first two and chooses ranges inside the third.

Video trim limits that shape the episode ranges (read 2026-10-06)
LimitValueWhat the splitter does
Source lengthUp to 1800 sRaises before submitting anything
Output length0.2 to 900 sRaises if an episode would exceed 900 s
Range inputsstart plus exactly one of end or durationUses start and duration
Price$0.02 per jobCounts jobs, prints the sum
Idempotency-KeyRequiredBuild one per episode from season and number

The splitter

episodes(412) returns seven ranges: six of 60 seconds and a final 52-second tail, since the tail is at least min_last. For a 370-second source the tail is ten seconds, below the minimum, so it folds into the sixth episode, which becomes 70 seconds. Change length and min_last to match your series. The script uses no network and runs as written.

def episodes(source_seconds, length=60, min_last=15):
    """Split a source into back-to-back trim ranges; fold a short tail into the last episode."""
    if source_seconds > 1800:
        raise ValueError("video trim sources are limited to 1800 s")
    n = max(1, source_seconds // length)
    if source_seconds - n * length >= min_last:
        n += 1
    ranges = []
    for i in range(n):
        start = i * length
        end = source_seconds if i == n - 1 else start + length
        if end - start > 900:
            raise ValueError("a trim output is limited to 900 s")
        ranges.append({"start": start, "duration": round(end - start, 3)})
    return ranges

for r in episodes(412):
    print(r)
print(len(episodes(412)), "trim jobs,", round(len(episodes(412)) * 0.02, 2), "USD")

Sending the ranges

For each range, post to /v1/video-trim with the same video_url, the start and the duration, and an Idempotency-Key such as s1-ep03-trim-v1. Each response is a job: poll its status and read video_url from its result. If a keyframe-precision cut would start before the point you asked for, the result reports the real value in actual_start_seconds; use the default exact precision when the first frame of an episode matters.

A range that ends after the source clamps and returns trim_clamped_to_source. Since the splitter ends the last range at the source length, you should never see it. If you do, your idea of the source length is stale: read the real duration with a probe-only video inspect call, which is free.

Choosing the length

YouTube's help page says Shorts can be up to three minutes, so 60 seconds is a choice and not a limit. Pick the length from the content, not from the platform: a one-minute length leaves room for captions at the quoted price, a three-minute length does not.

Budget the run before you submit it. Seven ranges is seven jobs at $0.02 each, so the script's last line prints $0.14, and a thirty-minute source cut into 60-second episodes is thirty jobs, or $0.60. Compare that with the number of episodes you will actually publish, because an unpublished episode is still billed. The source limit is 1800 seconds, which is exactly thirty minutes; a source longer than that has to be cut in two passes.

Finally, store each range next to its job id when you submit. If a job fails, you want to re-submit that range with its original key, not recompute the list, and a stored range makes that a lookup instead of a calculation.

Test the function before you run it on real files. Four cases are enough: a source exactly divisible by the length, a source with a long tail, a source with a short tail, and a source over 1800 seconds. The first three return ranges that sum to the source length; the fourth raises. Put the cases in a unit test and the splitter stays correct when you change the numbers next season.

If a sentence straddles a boundary, the cut will split it. Equal ranges are a starting point. Read a transcript with video inspect, which bills transcription at $0.01 per audio minute, and move each boundary to the nearest pause. The splitter then takes a list of cut points instead of a fixed length, and the rest of the pipeline does not change.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume