← Back to blog

How to Download Just a Section of an HLS / m3u8 Stream (Not the Whole Thing)

Extract a specific time range from an m3u8/HLS stream instead of the full VOD — ffmpeg seeking, segment-index math, and an in-browser clip method.

You have a three-hour HLS VOD and you want minutes 47 through 52. Downloading the whole manifest — hundreds of segments, gigabytes of data, then a five-minute trim in a video editor — is the wrong shape of work. HLS is built out of small, independently-fetchable chunks, which means there is no technical reason to download anything outside your time window. You just have to figure out which chunks overlap it.

This post is the technical one in the cluster. It assumes you already know roughly what HLS and m3u8 are — if you don’t, start with How to Download m3u8 / HLS Streams for the full protocol walkthrough, then come back here. The goal below is narrower: given a media playlist and a [start, end] time range, get only the bytes you need.

We’ll cover three methods — ffmpeg seeking against the manifest URL, range-aware CLI downloaders, and an in-browser clip extension that fetches only the overlapping segments — plus the segment-index math that makes all of them work.

Why a section-download is even possible

An HLS media playlist is, at heart, an ordered list of segments with durations. Strip a real one down and it looks like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD

#EXTINF:6.000,
segment000.ts
#EXTINF:6.000,
segment001.ts
#EXTINF:6.000,
segment002.ts
#EXTINF:6.000,
segment003.ts
...
#EXTINF:4.200,
segment842.ts
#EXT-X-ENDLIST

The key line is #EXTINF. It declares the exact duration of the segment that follows. Because the player concatenates segments in order, the start time of segment n is simply the sum of all the #EXTINF durations before it. Segment 0 covers [0, 6), segment 1 covers [6, 12), segment 2 covers [12, 18), and so on.

That property is what makes a partial download tractable. To get the window [T_start, T_end), you don’t need the whole playlist — you need the contiguous run of segments whose time spans overlap that window. Everything before the first overlapping segment and everything after the last one is dead weight you can skip.

Two consequences fall out of this immediately:

  1. You can’t cut on an arbitrary frame for free. Segments begin on keyframe boundaries (a new IDR frame opens each one, in a well-formed stream). If you want a copy-only, no-re-encode extraction, the cheapest cut points are segment boundaries. Finer accuracy means re-encoding the boundary segments.
  2. The math depends only on the #EXTINF values, not on segment file names, byte offsets, or anything the CDN hides. If you can read the media playlist, you can compute the segment range yourself.

Mapping a time range to segment indices: a worked example

Say segments are a uniform 6 seconds and you want 10:00 to 15:00 — i.e. [600s, 900s).

  • First overlapping segment index: floor(600 / 6) = 100. Segment 100 covers [600, 606).
  • Last overlapping segment index: ceil(900 / 6) - 1 = 149. Segment 149 covers [894, 900).

So you need segments 100 through 149 inclusive — 50 segments, 300 seconds, instead of all 843. If you concatenate exactly those, you get a clean five-minute clip that starts precisely at 10:00 (because 600 is a segment boundary) and ends precisely at 15:00.

Real playlists rarely have perfectly uniform durations — the last segment is short, ad-insertion points reset timing, and live encoders drift. So the robust approach is don’t divide by a constant — accumulate:

# Pseudocode: time range -> segment index range
t = 0.0
first = last = None
for i, dur in enumerate(extinf_durations):
    seg_start, seg_end = t, t + dur
    if seg_end > T_start and first is None:
        first = i                      # first segment that reaches into the window
    if seg_start < T_end:
        last = i                       # keeps advancing while segments still overlap
    t = seg_end
# fetch segments[first .. last] inclusive

That loop is exactly what every tool below does internally. The difference between the methods is how much of the surrounding plumbing — auth, key fetching, audio/video pairing, URL signing — they handle for you.

Method 1: ffmpeg seeking against the m3u8 URL

ffmpeg understands HLS natively, so the most direct partial download is to hand it the manifest URL plus a seek and a duration:

ffmpeg -ss 00:10:00 -t 00:05:00 -i "https://example.com/video/720p/playlist.m3u8" -c copy clip.mp4

-ss is the start (seek to 10:00), -t is the duration to capture (5 minutes), and -c copy streams the packets straight through with no re-encode. With -c copy, ffmpeg can only cut on keyframe boundaries, so the actual clip starts at the keyframe at or just before 10:00 — usually the start of segment 100 in our example. That’s exactly what you want for a fast, lossless grab.

-ss before -i vs after -i

The position of -ss relative to -i is the single most important detail here, and it trades speed against accuracy:

  • -ss before -i (input seeking) — ffmpeg seeks at the demuxer level before decoding. For HLS this means it can skip whole segments it never needs to download. Fast, network-cheap, and the right default for grabbing a section. Accuracy is to the nearest keyframe.
  • -ss after -i (output seeking) — ffmpeg reads from the start, decodes, and discards frames until it reaches the timestamp. Frame-accurate, but it has to pull (and often decode) everything up to your start point. For a clip starting at 10:00 of a 3-hour VOD, that’s slow and defeats the purpose.

For a fast section download, keep -ss before -i. If you need frame accuracy at the in-point, the usual move is to input-seek to just before your target (fast) and output-seek the small remainder (accurate), which means re-encoding the head:

# Fast input seek to ~9:58, then frame-accurate trim to 10:00, re-encoding only the boundary
ffmpeg -ss 00:09:58 -i "https://example.com/video/720p/playlist.m3u8" \
  -ss 00:00:02 -t 00:05:00 -c:v libx264 -c:a aac clip.mp4

You lose the -c copy speed, but only across the seconds you re-encode, not the whole clip.

The caveats, honestly

ffmpeg-against-a-manifest is the right tool when the stream is reachable and unsigned. It gets fiddly fast when it isn’t:

  • Expiring signed URLs. Many CDNs sign the manifest (and sometimes each segment) with a token that expires in 60–300 seconds. If you copy a URL from DevTools and ffmpeg is still chewing through a long clip when the token dies, you get a 403 mid-download. For a five-minute copy this is often fine; for anything longer, or anything you fumble the timing on, it’s a real problem.

  • AES-128 encryption. If the playlist carries #EXT-X-KEY:METHOD=AES-128,URI="key.bin", ffmpeg will fetch and apply the key automatically — as long as the key URL is reachable with the request context it has. If the key endpoint needs a cookie or header your bare ffmpeg call doesn’t send, decryption fails.

  • Separate audio and video manifests. Modern HLS usually splits audio and video into separate media playlists. Seeking the video manifest alone gives you a silent clip. You have to pass both as inputs and seek both:

    ffmpeg \
      -ss 00:10:00 -t 00:05:00 -i "https://example.com/video/720p/playlist.m3u8" \
      -ss 00:10:00 -i "https://example.com/audio/en/playlist.m3u8" \
      -c copy -bsf:a aac_adtstoasc clip.mp4

    (More on the silent-clip failure mode in HLS Download: Audio and Video Are Separate.)

  • Referer / custom headers. CDNs that gate on origin need the header passed explicitly, e.g. -headers "Referer: https://example.com/watch". Get it wrong and every segment request 403s.

None of this is a deal-breaker for scripting — ffmpeg is unbeatable when you’ve already got the URL, headers, and keys lined up and want a repeatable, automatable cut. It’s just genuinely fiddly when the stream is behind a login or rotating signatures, because you’re hand-reconstructing the request context the browser produced for free.

Method 2: range-aware CLI downloaders (N_m3u8DL-RE, yt-dlp)

If you’d rather work in segment/time space than in ffmpeg seek flags, dedicated HLS downloaders can select a subset of the playlist.

N_m3u8DL-RE exposes range selection directly. Its --custom-range flag accepts a time or segment range and downloads only those segments — which is exactly the “fetch the overlapping run” operation, done for you:

# Time range
N_m3u8DL-RE "https://example.com/master.m3u8" --custom-range "00:10:00-00:15:00" --save-name clip

# Or by segment index
N_m3u8DL-RE "https://example.com/master.m3u8" --custom-range "100-149" --save-name clip

It also resolves the master playlist, pairs audio and video tracks, and applies AES-128 keys, which removes most of the ffmpeg pain. The trade-off is that it’s another binary to install and its cut accuracy is segment-grained — you get whole segments, so your clip snaps to the nearest segment boundaries on each end.

yt-dlp is the broader tool, but its range support is narrower and worth being honest about. The --download-sections flag works well for sites with a real extractor (most famously YouTube via chapter/timestamp ranges):

yt-dlp --download-sections "*00:10:00-00:15:00" -f "bv*+ba/b" "https://www.example.com/watch?v=..."

For a raw .m3u8 URL, though, --download-sections frequently falls back to downloading the full stream and trimming with ffmpeg afterward, because yt-dlp’s generic HLS handling doesn’t always do native segment-range fetching. So you save disk-and-trim effort but not necessarily bandwidth. Check what it actually fetched before assuming it grabbed only the window.

Both tools share the same blind spot as bare ffmpeg: they work cleanly on public, unsigned streams. Point them at an auth-walled or short-expiry signed manifest and you’re back to copying cookies, headers, and freshly-signed URLs out of DevTools by hand.

Method 3: clip it in the browser with OFA (no CLI, inherits the session)

Every method above shares one root problem: the manifest and segments were served to a specific authenticated browser session, and a command-line tool isn’t that session. You end up reverse-engineering the request context — cookies, Referer, signed query strings, the key endpoint’s auth — and racing a token that expires in a minute.

The way around that is to do the clipping inside the page, where the session already exists. Video Downloader One-for-All’s Clip & Trim Download (added in v1.1.38, June 2026) does exactly this:

  1. Open the page and let the player load the stream as normal.
  2. Open OFA’s clip view — you get a seekable preview of the detected HLS/DASH stream.
  3. Drag the in-point and out-point to your exact range (e.g. 10:00 → 15:00).
  4. Download. OFA fetches only the segments that overlap your selected range — running the same accumulate-and-select logic from the top of this article — and muxes them into a single MP4.

Because it runs as a Chrome/Edge extension inside the tab, it:

  • Inherits the player’s auth — cookies, signed URLs, and Referer are whatever the page already sent, so login-gated and short-expiry streams just work without you copying anything.
  • Handles AES-128 transparently, fetching the key with the page’s own request context.
  • Pairs separate audio and video manifests automatically, so the clip has sound — no -bsf:a aac_adtstoasc incantation to remember.
  • Fetches only your window, not the whole VOD, so a five-minute clip out of a three-hour stream downloads roughly five minutes’ worth of data.

Clip & Trim is available on both the Free and Paid plans. Cut accuracy is segment-grained at the boundaries (same constraint as N_m3u8DL-RE — a copy-only extraction can only cut where keyframes already are), which for typical 2–6 second segments lands you within a few seconds of the marks you set. If you need exact-frame in/out, take the slightly-larger clip here and do the final frame-accurate trim locally with the output-seek ffmpeg pattern from Method 1.

The product details for the underlying HLS engine live on the HLS downloader page, and for capturing live (non-VOD) windows where the manifest is a sliding window rather than a fixed list, see the live stream recorder.

This is also the path the parallel guide How to Download Part of a Video recommends for the general case, and the same approach applies when you want a single highlight out of a long stream — see How to Download a Twitch VOD for that specific scenario.

Which method, when

MethodNeeds CLIHandles auth/cookiesHandles AES-128Frame accuracyFetches only the range
ffmpeg -ss/-tYesManual (headers/cookies by hand)Yes (if key URL reachable)Keyframe by default; frame-accurate with re-encodeYes (with input seek)
N_m3u8DL-RE --custom-rangeYesManualYesSegment-grainedYes
yt-dlp --download-sectionsYesPartial (extractor-dependent)YesVaries; often trims after full fetchOnly when the extractor does native ranges
OFA Clip & TrimNoYes (inherits page session)Yes (transparent)Segment-grainedYes

The honest summary:

  • ffmpeg is the best tool for scripting and automation on public or self-served streams — repeatable, composable, no GUI, and you control every flag. It’s the worst tool when you’re fighting an auth wall or a 60-second token.
  • N_m3u8DL-RE is the nicest CLI middle ground when you want explicit range selection without ffmpeg’s seek-flag subtleties, and the stream isn’t locked down.
  • OFA Clip & Trim is the best tool when the stream needs your login or hands out signed URLs that expire fast, or when you simply don’t want to touch a terminal — because it never leaves the authenticated browser session in the first place.

A note on rights and DRM

Download only content you have the rights to save — your own uploads, material you’re licensed to use, or content where the source’s terms permit it. And to be clear about scope: this entire article is about clear (or AES-128-encrypted) HLS, which is decryptable because the key is delivered to the player. Streams protected by Widevine, PlayReady, or FairPlay are a different thing — the content key never leaves a hardware-backed CDM, no in-browser extension or CLI can extract it, and OFA does not support them. If you see those, the section-download techniques here don’t apply.

Bottom line

A section-download is possible because an HLS playlist is a list of timed segments: sum the #EXTINF durations, find the contiguous run that overlaps your [start, end] window, and fetch only that run. ffmpeg, N_m3u8DL-RE, and yt-dlp all do this math under the hood and are excellent on open streams — pick them for scripting and batch work. When the stream is behind a login or its URLs expire in seconds, the friction isn’t the math, it’s reproducing the browser’s session — which is precisely why clipping in-page with Video Downloader One-for-All is the path of least resistance for everything auth-walled.