ffmpegsubtitlesvideo-automation

FFmpeg Subtitle Delay Isn't a Slider Drag. Use -itsoffset

·Javid Jamae·10 min read
FFmpeg Subtitle Delay Isn't a Slider Drag. Use -itsoffset

Your captions land two seconds after the person speaks, and every player shows the same lag. The usual advice is to drag a delay slider until it looks right, which is fine for one movie and useless when you have 400 clips in a queue. The scriptable fix is one FFmpeg flag, and the harder question is whether your error is actually a constant.

Quick answer: Fix an FFmpeg subtitle delay by putting -itsoffset before the subtitle input: ffmpeg -i video.mp4 -itsoffset 2.5 -i subs.srt -map 0 -map 1 -c copy -c:s mov_text out.mp4 shifts every cue 2.5 seconds later. Negative values like -itsoffset -1.8 pull captions earlier, and decimals are honored down to the millisecond. A single offset only works if the error is the same size at the start and the end of the file.

Measure the offset before you shift anything

Guessing at the offset costs you three or four re-encodes. Measuring it costs about twenty seconds in a player you already have open.

  1. Open the video with the subtitle track in VLC and press h and g. Each press moves subtitle delay by 50 ms and VLC prints the running total on screen. When the words line up, that number is your offset.
  2. In mpv, z and Shift+Z do the same thing in 0.1 second steps and the OSD shows sub-delay.
  3. Check a second spot near the end of the file. If the same delay still lines up there, the error is constant and -itsoffset will fix it. If the gap grew, skip ahead to the drift section, because no single number will hold.

You can also read the raw cue times without a player. ffprobe -v error -select_streams s:0 -show_entries packet=pts_time -of csv=p=0 subs.srt | head -3 prints the first three cue start times in seconds, which is enough to compare against when the first line of dialogue actually happens.

Shift a subtitle track with -itsoffset when you're muxing

-itsoffset adds its value to every timestamp of the input that follows it. Put it before the -i for the SRT and the whole caption track moves as a block, with no re-encoding of video or audio.

ffmpeg -i video.mkv \
  -itsoffset 2.5 -i subs.srt \
  -map 0 -map 1 -c copy \
  out.mkv

A positive value makes subtitles appear later, which is what you want when captions fire early. A negative value pulls them earlier: -itsoffset -1.8 moves everything 1.8 seconds toward the start of the file.

MP4 is the one container that needs an extra flag. MP4 can't store SubRip, so -c copy alone fails with Could not find tag for codec subrip in stream #2, codec not currently supported in container. Convert the subtitle stream to the MP4-native format and leave everything else copied:

ffmpeg -i video.mp4 \
  -itsoffset 2.5 -i subs.srt \
  -map 0 -map 1 -c copy -c:s mov_text \
  out.mp4

The explicit -map 0 -map 1 matters more than it looks. Without it, FFmpeg's default stream selection picks one stream per type and will happily drop a subtitle track you thought you were carrying through.

How to shift the SRT file itself, and why burn-in ignores -itsoffset

Burned-in captions don't respond to -itsoffset at all. The subtitles filter opens the subtitle file straight off disk through libass, so it never sees the input timestamps that -itsoffset modifies. People lose an afternoon to this one.

The fix is to shift the file first, then burn the shifted copy. FFmpeg will remux SRT to SRT and apply the offset on the way:

ffmpeg -itsoffset 2.5 -i subs.srt -c copy shifted.srt
ffmpeg -i video.mp4 -vf "subtitles=shifted.srt" -c:a copy out.mp4

One caveat on negative shifts: cues that would land before 00:00:00 get clamped or dropped, so if your first caption starts at 0.4 s and you shift by -1.8, that line disappears. Trim the leading cues by hand, or shift the video instead of the subtitles.

Once the timing is right, the styling questions start. That's a separate rabbit hole, and FFmpeg's ass filter is where it leads if you need per-word highlighting rather than plain SubRip blocks.

When one number can't fix it: subtitle drift

If the delay is 1 second at the opening credits and 40 seconds at the end, you don't have a delay. You have a framerate mismatch, and every offset you try will be wrong somewhere in the file.

The classic case is a subtitle file timed against a 25 fps PAL transfer being played against a 23.976 fps source. Those run at a ratio of 1.0427, so the subtitles drift 4.27% fast. Over a 90-minute film that's about 3 minutes 50 seconds of accumulated error. FFmpeg has no clean way to rescale subtitle timings by a ratio, so this is where you stop reaching for -itsoffset.

ffsubsync is the tool most people land on. It runs voice activity detection over the video's audio, correlates the speech regions against the subtitle cue positions, and writes a corrected file:

pip install ffsubsync
ffs video.mp4 -i unsynced.srt -o synced.srt

By default it solves for a constant offset. Add --gss and it also searches framerate ratios, which is the flag that handles PAL drift. You can also pass a known-good subtitle file as the reference instead of the video, which is faster and works when the audio is noisy. alass is the alternative worth knowing: it handles files where the offset changes partway through, which is what you get when the source had ad breaks or a different cut. subaligner takes a deep-learning approach to the same problem.

Common pitfalls when shifting subtitle timing

-itsoffset placed after the -i does nothing. It's an input option, so it applies to the next input file listed, and if there isn't one, FFmpeg prints a quiet "Option itsoffset not used by any stream" warning and produces an unshifted output that looks like a failed fix.

Shifting the wrong input is the second trap. ffmpeg -itsoffset 2.5 -i video.mp4 -i subs.srt moves the video, not the captions, which is the opposite of what almost everyone wants and is easy to miss if you only spot-check the first ten seconds.

Frame-count offsets aren't seconds. If someone hands you a delay of "60 frames," convert it against the actual framerate before typing it in. At 23.976 fps that's 2.503 seconds, not 2.4, and the 100 ms difference is visible on fast dialogue.

Finally, check the offset at both ends of every file, not just the first cue. A pipeline that applies a fixed -itsoffset 2.5 to a batch will produce clips that look correct in the first fifteen seconds of preview and fall apart at minute nine.

Shifting an existing SRT vs generating timestamps at transcode time

Every technique above is repair work. It exists because the subtitle file and the video file were timed against different references, which is a problem you can design out rather than fix repeatedly.

ApproachFixes constant offsetFixes driftPer-file cost
`-itsoffset` on the SRT inputYesNoOne remux, seconds
Pre-shifted SRT plus burn-inYesNoOne re-encode
`mkvmerge -o out.mkv video.mkv --sync 0:2500 subs.srt`YesNoOne remux, MKV only
ffsubsync or alassYesYesAudio analysis per file, roughly a minute
Transcribe from the video's own audioNot neededNot neededOne job

The last row is the one that scales. When the transcript comes out of the audio you're actually shipping, the timestamps are already in that file's timebase and there is nothing to align. That's how captioning works in FFmpeg Micro: you send the video, the transcription and the burn happen in the same job, and you never end up with a second file to keep in sync. It's the same reason a batch clip workflow in n8n should caption after cutting rather than carrying one master SRT across every clip, since each cut shifts the timeline out from under the original cue times.

When to reach for a GUI or a different tool instead

-itsoffset is the wrong tool when the shift isn't uniform across the file or when you need to see what you're doing. Reach for something else in those cases and don't feel bad about it.

Subtitle Edit and Aegisub both handle partial shifts, where you select a range of cues and move only those. That's the fix for a file that's correct until an ad break and off by 12 seconds after it. SubShifter is a browser tool for a one-off shift when you don't want to install anything. MKVToolNix's mkvmerge --sync is often cleaner than FFmpeg for MKV, since it takes the delay in milliseconds per track and doesn't need stream mapping. And if the subtitles are for a video you're about to re-cut anyway, fixing the old file is wasted effort, because the cuts will invalidate the timings again. That's true of any long-form to Shorts pipeline.

FAQ

How do I delay subtitles by 2 seconds in FFmpeg?

Delay subtitles by 2 seconds with ffmpeg -i video.mkv -itsoffset 2 -i subs.srt -map 0 -map 1 -c copy out.mkv. The -itsoffset 2 has to sit before the -i subs.srt it applies to, and for MP4 output you also need -c:s mov_text.

Does -itsoffset accept negative and decimal values?

-itsoffset accepts both. Negative values such as -itsoffset -1.75 move captions earlier, decimals are honored to millisecond precision, and you can also pass a duration string like -itsoffset 00:00:02.500 instead of raw seconds.

Why did -itsoffset have no effect on my subtitles?

The two usual causes are placement and burn-in. -itsoffset written after -i applies to no input at all, and captions rendered with the subtitles filter ignore it entirely because that filter reads the file from disk rather than from an input stream. For burn-in, shift the SRT first with ffmpeg -itsoffset 2.5 -i subs.srt -c copy shifted.srt.

How do I sync subtitles automatically instead of measuring the offset?

ffsubsync syncs subtitles automatically by matching speech in the video's audio against the cue positions: ffs video.mp4 -i unsynced.srt -o synced.srt. Add --gss when the error grows over the file, since that turns on the framerate-ratio search that a fixed offset can't cover.

Can I shift only part of a subtitle file?

FFmpeg can't shift part of a subtitle file, because -itsoffset always moves the entire track by the same amount. Use Subtitle Edit or Aegisub to select a cue range and shift just that range, or use alass, which detects offsets that change partway through.

If the real answer for your pipeline is to stop maintaining a separate SRT at all, captioning from the video's own audio is on the free tier and takes one call to try. Sign up free and run one clip through it before you rewrite your offset script.

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

Skip the command line

The Auto Captions blueprint transcribes your video and burns the captions in. You just review the transcript.

Run it (free)