32blogby StudioMitsu
Archive6 min read

Lossless Video Trimming with FFmpeg: The -c copy Approach

How to cut video clips instantly without re-encoding using FFmpeg's stream copy mode. Covers the GOP keyframe constraint, input seeking, and troubleshooting sync issues.

Firing up Premiere Pro or DaVinci Resolve to trim a clip is overkill in most situations. If all you need is to cut a section out of a video — no effects, no color correction, just a clip — FFmpeg's stream copy mode completes the job in seconds with zero quality loss.

bash
ffmpeg -y -ss 00:01:30 -to 00:05:30 -i "input.mp4" -c copy "output.mp4"

This single command extracts everything from 1:30 to 5:30, copies the encoded data as-is into a new file, and finishes before a GUI editor would even finish loading.

The Commands

Windows (PowerShell)

powershell
# Define variables
$InputVideo = "input.mp4"
$OutputVideo = "output_cut.mp4"
$StartTime = "00:01:30"  # HH:MM:SS
$EndTime = "00:01:40"

# -ss       : Seek to start time
# -to       : Set end time (-t would specify duration instead)
# -c copy   : Copy streams without re-encoding
ffmpeg -y -ss $StartTime -to $EndTime -i "$InputVideo" -c copy "$OutputVideo"

macOS / Linux (Bash)

bash
#!/bin/bash

INPUT="input.mp4"
OUTPUT="output_cut.mp4"
START="00:01:30"
END="00:01:40"

# Place -ss before -i for input seeking (fast)
ffmpeg -y -ss "$START" -to "$END" -i "$INPUT" -c copy "$OUTPUT"

How Stream Copy Works

The key option is -c copy (equivalent to -c:v copy -c:a copy). Understanding what it actually does explains both its power and its limitations.

Bypassing the decode/encode cycle

A normal video editing workflow looks like this:

  1. Decode the compressed video into raw frames (slow, lossy at boundaries)
  2. Process the frames (crop, color grade, etc.)
  3. Re-encode the processed frames into compressed video (slow, introduces generation loss)

Stream copy skips steps 1 and 3 entirely. FFmpeg reads the encoded packets from the source container (MP4, MKV, etc.) and writes them directly into the output container without touching the data. The computational load is essentially just disk I/O.

Practical speed comparison:

For a 10 GB 4K video, cutting a 10-second clip:

  • With re-encoding (-c:v libx264): several minutes
  • With stream copy (-c copy): 1–3 seconds

Input seeking: why -ss goes before -i

The position of -ss in the command matters significantly.

  • Before -i (input seeking): The demuxer jumps to the specified position in the file before decoding starts. No data before the seek point is processed. This is fast — essentially O(1) for most container formats.
  • After -i (output seeking): FFmpeg decodes from the start until it reaches the specified time, then starts copying. Correct but slow, and it produces unstable results with -c copy.

For lossless trimming, always put -ss before -i.

The keyframe constraint

Lossless trimming has one unavoidable technical limitation: the cut point snaps to the nearest keyframe.

Modern codecs (H.264, H.265, AV1) compress video using a structure called GOP (Group of Pictures). A GOP starts with an I-frame (keyframe) that contains complete image data, followed by P-frames and B-frames that store only the difference from surrounding frames.

When you use -c copy, FFmpeg can't start playback mid-GOP because P/B frames need their reference I-frame to decode. So if you specify 00:01:30 as the start time, FFmpeg automatically backs up to the nearest I-frame before that point — which might be 00:01:27, 00:01:25, or further back depending on the GOP structure.

The result: the cut point can be off by a few seconds (up to the GOP length) from what you specified.

Practical implication: Lossless cutting is ideal for "roughly here" trimming — conference recordings, long-form footage, podcast clips. It's not suitable for frame-accurate editorial work. For precision cuts, you need to re-encode.

Troubleshooting

The output video starts with a black or frozen frame

This is usually a player compatibility issue. Some players struggle when the video stream starts with a P-frame rather than an I-frame. Adding -avoid_negative_ts make_zero usually fixes it:

bash
ffmpeg -ss 00:01:30 -to 00:05:30 -i input.mp4 \
    -c copy \
    -avoid_negative_ts make_zero \
    output.mp4

Audio and video are out of sync

Stream copy preserves the original timestamps, which can cause slight sync issues at cut boundaries. Fix with:

bash
ffmpeg -ss 00:01:30 -to 00:05:30 -i input.mp4 \
    -c copy \
    -avoid_negative_ts make_zero \
    output.mp4

The clip needs to start at exactly the right frame

If you need frame-accurate precision, stream copy won't work. Use re-encoding with a fast preset:

bash
# Video re-encode, audio stream copy
ffmpeg -ss 00:01:30 -to 00:05:30 -i input.mp4 \
    -c:v libx264 -preset fast -crf 23 \
    -c:a copy \
    output.mp4

This is slower but starts exactly where you specify.

-t vs -to: what's the difference?

  • -t 60 — cut 60 seconds of duration from the start point
  • -to 00:05:30 — cut until timestamp 5:30 in the source file

For "cut from X to Y" use cases, -to is more intuitive. For "cut X seconds starting from here," use -t.

Note: When -ss is placed before -i (input seeking), -to refers to the absolute timestamp of the input file, not a duration from the seek point. For example, -ss 00:01:30 -to 00:05:30 extracts from 1:30 to 5:30 in the original file — not 4 minutes starting from 1:30.

Batch Trimming

Bash: Process a list of clips from a CSV file

Create clips.csv:

text
input,start,end,output
video1.mp4,00:01:30,00:05:00,clip1.mp4
video2.mp4,00:00:10,00:02:30,clip2.mp4
video3.mp4,00:10:00,00:15:45,clip3.mp4

Then process it:

bash
#!/bin/bash

tail -n +2 clips.csv | while IFS=',' read -r input start end output; do
    ffmpeg -y -ss "$start" -to "$end" -i "$input" -c copy "$output"
    echo "Done: $output"
done

PowerShell: Batch trim with progress reporting

powershell
$clips = Import-Csv -Path "clips.csv"
$total = $clips.Count
$i = 0

foreach ($clip in $clips) {
    $i++
    Write-Progress -Activity "Trimming clips" `
        -Status "$($clip.output) ($i/$total)" `
        -PercentComplete (($i / $total) * 100)

    ffmpeg -y -ss $clip.start -to $clip.end `
        -i $clip.input `
        -c copy `
        $clip.output 2>$null

    if ($LASTEXITCODE -eq 0) {
        Write-Host "OK: $($clip.output)"
    } else {
        Write-Host "FAILED: $($clip.output)" -ForegroundColor Red
    }
}

Summary

FFmpeg's lossless trimming is the right tool when:

SituationRecommended approach
Rough cuts where ±5 seconds is acceptable-c copy (this article's method)
Frame-accurate precision cutsRe-encode with -c:v libx264 -preset fast
Batch trimming many filesScript + -c copy
Extracting clips from conference recordings-c copy
Preparing clips for social media-c copy (platforms re-encode anyway)

Stream copy treats video as a stream of data packets rather than pixels. That abstraction is what makes it so fast and lossless — and understanding it opens up a whole category of FFmpeg operations that operate at the container level rather than the codec level.