ffmpegvideo-editingapi

FFmpeg cuts on keyframes. Trim video by timestamp with an API.

·Javid Jamae·10 min read
FFmpeg cuts on keyframes. Trim video by timestamp with an API.

You have a three-hour recording and a list of timestamps: 4:12 to 6:30, 18:05 to 19:40, and thirty more. One cut with FFmpeg takes ten seconds. Thirty cuts a day, on a box that stays up long enough to finish, is where the job stops being a one-liner.

Quick answer: To trim video by timestamp with FFmpeg, run ffmpeg -ss 00:04:12 -i input.mp4 -t 138 -c copy clip.mp4, and to split one file at several timestamps in a single pass use the segment muxer: ffmpeg -i input.mp4 -c copy -map 0 -f segment -segment_times 252,390,1085 -reset_timestamps 1 out_%03d.mp4. Stream copy can only cut on keyframes, so re-encode the clip when the edges have to be frame accurate. A trim video by timestamp API such as FFmpeg Micro runs the same cut list as one call, with no FFmpeg install, no encoder box, and no long-running job to babysit.

Your cut lands on a keyframe, not on your timestamp

Conventional wisdom says trimming is the easy FFmpeg operation, the one you learn on day one. True for a single clip you eyeballed yourself. It stops being true once the timestamps come from a transcript or a highlight detector, because the cut you ask for and the cut you get are different numbers.

The reason isn't the syntax. It's the GOP structure. With -c copy, FFmpeg isn't decoding anything, so it can only start a new file on a keyframe. A cut requested at 00:01:12 gets rounded back to the nearest keyframe before it. x264's default keyframe interval is 250 frames, about 8.3 seconds of drift at 30 fps. Screen recordings and phone footage are usually tighter, often one or two seconds, but "usually" is not a spec you can build a pipeline on.

You get one of three outcomes: the clip starts early, it opens on a frozen or garbled frame because the decoder began mid-GOP, or the audio leads the video slightly. All three look like a bug in your automation. None of them are.

Trim one clip by timestamp with FFmpeg

Trimming a single range is a choice between fast and exact.

Fast, keyframe-aligned, no quality loss:

ffmpeg -ss 00:04:12 -i input.mp4 -t 138 -c copy clip.mp4

Frame accurate, at the cost of an encode:

ffmpeg -ss 00:04:12 -i input.mp4 -t 138 \
  -c:v libx264 -crf 20 -preset veryfast -c:a aac -b:a 128k clip.mp4

Both put -ss before -i, which makes the seek fast: FFmpeg jumps to the nearest keyframe instead of decoding from the top of the file. On a 2.7 GB one-hour 1080p MP4, a copy-mode trim of a 45-second range finishes in well under a second, while the same range at -preset veryfast takes several seconds on one core. Scale that number by your clip count for your batch budget.

The trap here is -to. With -ss before -i, FFmpeg resets output timestamps to zero, so -to counts from the cut point, not the original timeline. ffmpeg -ss 00:01:00 -i input.mp4 -to 00:02:00 -c copy out.mp4 gives two minutes starting at one minute, not the one-minute range you meant. Add -copyts, or always pass a duration with -t.

Split one video into many clips in a single pass

Splitting a file at a list of timestamps needs no loop. The segment muxer takes the cut list and writes every piece in one read of the source:

ffmpeg -i input.mp4 -c copy -map 0 -f segment \
  -segment_times 252,390,1085,1180 \
  -reset_timestamps 1 -segment_list chunks.csv \
  chunk_%03d.mp4

That produces five files split at 4:12, 6:30, 18:05 and 19:40, plus a CSV listing each output with its real start and end times. -reset_timestamps 1 makes every chunk start at zero so players and downstream FFmpeg steps don't choke on a first PTS of 1085. With -c copy, boundaries still snap to keyframes.

For arbitrary ranges instead of contiguous chunks, give one input several outputs and let FFmpeg decode once:

ffmpeg -i input.mp4 \
  -ss 00:04:12 -t 138 -c:v libx264 -crf 20 -preset veryfast -c:a aac clip_01.mp4 \
  -ss 00:18:05 -t 95  -c:v libx264 -crf 20 -preset veryfast -c:a aac clip_02.mp4

Here -ss sits after -i and applies per output, so each clip is frame accurate and the source is read once. To keep stream copy instead and still hit your marks, find the keyframes first:

ffprobe -v error -select_streams v:0 -skip_frame nokey \
  -show_entries frame=pts_time -of csv=p=0 input.mp4

Snap each cut point to the nearest value in that list and copy-mode trimming is exact by construction. Or re-encode once with -force_key_frames 252,390,1085, after which every cut at those points is free.

Run a cut list against one file

A cut list, an EDL, a chapter table, a highlight array from an LLM: all the same input, start and end pairs that have to become files on disk. Normalize the timestamps first, then cut.

Normalizing matters more than it sounds. Broadcast and camera files carry an SMPTE start timecode, often 01:00:00:00 instead of zero:

ffprobe -v error -show_entries format_tags=timecode -of default=nw=1 input.mov

If that returns a non-zero value and your cut list came from an NLE or a timecode-stamped transcript, every timestamp is an hour off FFmpeg's zero-based clock. Subtract it, or you'll ship an hour of black.

Reassembling clips afterward is the concat demuxer, and its -c copy path needs matching codec, resolution, and frame rate: FFmpeg Concat Different Resolutions.

Where local trimming falls over

Trimming becomes an infrastructure problem once one input produces more than a handful of outputs. Three things break, none of them FFmpeg's fault.

The first is where the bytes live. Reading a 2.7 GB source into a workflow tool to slice it kills the tool. The n8n community has a long-running thread about a roughly 1 GB file where the Read File node takes the instance down, and the answer is always the same: pass URLs, take a callback. More in How to Fix n8n Running Out of Memory on Large Video Files.

The second is timeouts. AWS Lambda caps a single invocation at 15 minutes. Cloud Run deployments of the popular self-hosted media containers are documented by their maintainers as best suited to jobs under five minutes, and a proxied deployment can impose a one-minute synchronous limit without webhooks. Thirty re-encoded clips from a two-hour master blow through all of those.

The third is that the escape hatch closed. n8n disabled the Execute Command node by default in v2.0 because arbitrary shell execution is a security hole in shared environments, so shelling out to FFmpeg isn't a plan on n8n Cloud.

FFmpeg on your own boxFFmpeg Micro
SetupInstall a build, pin the version, match codecsNothing to install
30 clips from one masterYour loop, your disk, your CPUSubmit jobs, get URLs back
Job longer than the timeoutRe-architect around the limitAsync job plus webhook
Cost shapeInstance running whether or not it's cuttingFree tier, then usage-based

If the cutting is solved and the babysitting is what's costing you, send the cut list to FFmpeg Micro. Submit a job, poll it or take a webhook, download the output. The playground runs a trim before you write code, and the docs have the request shape.

Wire the cut list into n8n, Make, or Zapier

A cut list runs fine from a no-code tool as long as the tool never touches the video file. Same pattern in n8n, Make, and Zapier:

  1. Get the cut list into the workflow from Airtable, a Google Sheet, a transcript node, or an LLM step returning start and end pairs as JSON.
  2. Split the list into items so each range becomes its own iteration.
  3. For each range, call the API with the source URL and the timestamps. Never download the source into the workflow.
  4. Take the webhook callback, or poll job status, instead of holding the HTTP request open.
  5. Write the returned output URL back to the row that produced it.

Steps 3 and 4 are the whole trick. A workflow that waits synchronously for a video render dies on a plan timeout, covered in Zapier video processing timeout isn't a plan limit. If the clips also need captions and a vertical reframe, Clip, Caption, and Reformat Video in One n8n Workflow picks it up from there.

Common pitfalls when cutting by timestamp

Mixing -ss placement between the copy path and the re-encode path is the most common bug. Use one convention everywhere, so a clip that lands one keyframe early is a keyframe problem, not a syntax problem.

Trusting -c copy for user-facing clips is the second. Copy mode is right for chunking a long file into work units where the edges don't matter, and wrong for a social clip whose first frame is the thumbnail and may be a mid-GOP smear.

Fractional seconds in a locale-formatted string break quietly. FFmpeg wants 74.5, not 74,5, and a comma in -segment_times reads as a separator, silently giving you extra segments.

A trim inherits every problem the source had. Drifting audio sync or a missing moov atom shows up in all thirty clips, so fix the source first. See FFmpeg Audio Out of Sync Isn't the Codec if the drift is variable frame rate rather than a real offset.

When a trimming API is the wrong call

Cutting video by timestamp through an API is the wrong choice in a few real cases. If you trim one clip a week on your laptop, FFmpeg is already installed and there's no pipeline to build. If a human has to scrub a timeline and find the cut by eye, you need an editor, not an API, because FFmpeg Micro ships no editing UI. And if your worker fleet already stays saturated, the per-job economics may favor hardware you pay for anyway, though that math includes on-call time, which I broke down in The NCA Toolkit is free. Self-hosting it isn't cheap.

FAQ

How do I cut a video at an exact timestamp without losing quality?

An exact cut and an untouched bitrate are in conflict, because exact cuts require decoding and stream copy only cuts on keyframes. Re-encode just the clip at -crf 18 to -crf 20 with libx264, visually transparent for web delivery, or re-encode the master once with -force_key_frames at your cut points, then copy clips out.

Can I trim video by timestamp in n8n without self-hosting FFmpeg?

You can trim video by timestamp in n8n without self-hosting FFmpeg by calling a video API from an HTTP Request node and taking the result on a webhook. The Execute Command node older tutorials use is disabled by default in n8n v2.0, and was never available on n8n Cloud.

How do I handle a cut list with SMPTE timecode?

A cut list in SMPTE timecode needs the source's start timecode subtracted before the values reach FFmpeg, because FFmpeg counts from zero while broadcast files often start at 01:00:00:00. Read it with ffprobe -show_entries format_tags=timecode, convert to seconds, subtract.

Does trimming with -c copy re-encode the audio?

Trimming with -c copy re-encodes nothing, audio included, which is why it's near instant. The side effect is that audio is cut at a packet boundary rather than your exact timestamp, so a copy-mode clip can start with a few milliseconds of audio from the previous frame.

If your cut list is already in a spreadsheet or a transcript and the only missing piece is somewhere to run the cuts, sign up free and send the first batch through the free tier before you provision anything.

About Javid Jamae

Founder & CEO at FFmpeg Micro

Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

Ready to process videos at scale?

Start using FFmpeg Micro's simple API today. No infrastructure required.

Get Started Free