Subtitle problems

Subtitles out of sync: find the offset and fix it

Updated 6 fixes4 sources

Short answer

Drift has two causes and two fixes. A constant offset means the cue times are right but shifted, usually because the video was trimmed at the head or the caption file came from a different export: move every cue by the same amount, with ffmpeg -itsoffset, or a subtitle timing tool. Growing drift means the cue file was written against a different cut of the video: the captions have to be re-timed against the exact file you upload, because no offset can fix a change that happens halfway through.

Key takeaways

  • Measure the error at the start, in the middle and at the end before changing anything.
  • The same error at all three points means one offset fixes it.
  • A growing error means the file changed under the cues; re-time, do not shift.
  • At 30 fps one frame is 0.033 s, so a small frame count is a visible lag.
  • Re-upload the exact file the captions were timed against; a re-encode can change the timing.

Work out the drift before you change anything

Drift has two shapes. Constant offset: the captions are correct but late or early by the same amount from the first word to the last, so one shift fixes it. Growing drift: the error is small at the start and large at the end, which means the file changed under the captions (a different cut, a removed intro, a re-encode that dropped or held frames) and the cues have to be re-timed against the exact file you upload.

Frame rateOne frame100 frames1000 frames
24 fps0.042 s4.2 s41.7 s
25 fps0.040 s4.0 s40.0 s
30 fps0.033 s3.3 s33.3 s
60 fps0.017 s1.7 s16.7 s

Computed: one frame is 1 divided by the frame rate, so at 30 fps a frame is 0.033 s and a cue that is four frames late is 0.13 s late. Measure at the start, in the middle and at the end; if the three numbers are the same, shift; if they grow, re-time.

What a caption file has to look like

YouTube documents what it accepts: SubRip (.srt) is supported in its basic form only, with no style markup recognized, and the file must be plain UTF-8. SubViewer (.sbv or .sub) is also supported. WebVTT (.vtt) is accepted with positioning, but styling is limited to bold, italic and underline. Timestamps are the part every player depends on, so a cue with a broken time code is a cue that never appears.

FormatExtensionWhat YouTube documents
SubRip.srtBasic versions only, no style markup, plain UTF-8
SubViewer.sbv, .subBasic versions only, no style markup, plain UTF-8
WebVTT.vttPositioning supported, styling limited to b, i, u
TTML, DFXP.ttmlPartial implementation

A SubRip cue is a number, a start and stop time, then the text: 1 then 00:00:00,599 --> 00:00:04,160 then the line. The comma before the milliseconds is what makes it SubRip; a dot makes it SubViewer.

Free tools:Check a subtitle fileConvert SRT to VTTConvert VTT to SRT

Find your case

What you seeWhat it usually meansDo this
Captions are late by the same amount throughoutA constant offset between file and cuesShift every cue by that amount (step 3)
Correct at the start, late at the endThe cue file was written for a longer or shorter cutRe-time against the upload file (step 4)
Correct at the end, early in the middleA scene was removed after the captions were madeRe-time from the current cut (step 4)
Drift only on one platformThat platform trimmed or looped the file, or re-timed the trackCheck the platform's own timing (step 5)
Error grows roughly every N secondsFrame-rate mismatch between the editor and the playerRe-export and re-time at one frame rate (step 2)
Only burned-in captions driftThe burn was done on an earlier version of the fileRe-burn from the current export (step 6)

Fix it step by step

  1. Measure the offset in three places

    Play the file and the cues together. Note the error at the first cue, at the middle and at the last cue. Three equal numbers mean a constant offset. A number that grows means the video under the captions is not the video you timed them against.

    Shift subtitle timing →

  2. Check the frame rate of the file you will upload

    ffprobe -hide_banner in.mp4 prints the frame rate among the stream details. Captions timed on a 30 fps timeline and played against a 25 fps render drift by 0.2 s for every 10 s of video, because 10 s is 300 frames at 30 fps and 250 frames at 25 fps: 50 frames of difference is 2 s at 25 fps.

    Check the video file →

  3. Shift a constant offset with ffmpeg

    ffmpeg -i in.mp4 -itsoffset 1.20 -i subs.srt -map 0:v -map 0:a -map 1 -c copy -c:s mov_text out.mp4. FFmpeg documents -itsoffset as an offset added to the input timestamps, so a positive value delays the subtitle stream by exactly that duration, and -c copy re-encodes nothing.

    Source: FFmpeg Documentation: Command line options (-map, -c copy, -itsoffset)

  4. Re-time when the drift grows

    No single offset can fix a cue file written for a different cut. Open the exact file you are going to upload, put every cue where the word is actually said, and export again. If the audio and video streams came from different exports, fix that first: one file, one render, one frame rate.

  5. Check whether the platform retimes the track

    YouTube supports timed caption files and auto-sync, which sets the timing for you, and lists poor sound quality, overlapping speakers and long silences among the reasons its own captions fail. If your file is correct but the platform's track drifts, replace the auto track with your own timed file.

    Source: YouTube Help: Add subtitles & captions

  6. Re-burn from the current export

    If you burn captions in, the text is baked into the picture and cannot drift on its own. It drifts only if the picture was re-encoded or trimmed after the burn: ffmpeg -i current.mp4 -vf "subtitles=current.srt" -c:a copy -c:v libx264 -crf 20 final.mp4.

    Source: FFmpeg Documentation: Video filters (crop, pad, scale, setsar, subtitles) · Burn captions into a video →

Values worth checking before you shift anything

Frame rate of the upload fileRead it with ffprobe · must match the rate the cues were timed on
Offset typeConstant or growing · decides shift versus re-time
One frame at 30 fps0.033 s · computed as 1 divided by 30
Shift command-itsoffset 1.20 before the subtitle input
Stream copy-c copy · re-encodes nothing
ValidationPlay the result from 0:00 to the end

A repair order that works

  1. Confirm the upload file: one file, one frame rate, one render.
  2. Measure the error at the start, the middle and the end.
  3. Constant error: shift the whole track by that amount.
  4. Growing error: re-time the cues against the current file.
  5. Validate the output from the first cue to the last.
  6. Upload that exact file, not a copy that was re-encoded on the way.

Every step here is general video practice: no platform publishes a timing rule you can lean on, so the file and the cues have to agree with each other.

Frequently asked questions

How do I fix subtitles that are out of sync?

If the error is the same at the start and the end, shift every cue by that amount, for example with -itsoffset 1.20 before the subtitle input. If the error grows, the cue file was written for a different cut and has to be re-timed against the file you upload.

Why are my subtitles late by a second?

A constant offset, usually from a trim at the head of the video or a caption file exported against a different cut. One shift fixes the whole track.

Can a frame rate change make captions drift?

Yes. Captions timed on a 30 fps timeline and played against a 25 fps render gain 50 frames of error for every 10 seconds of video, which is 2 s at 25 fps.

Do burned-in captions ever drift?

Not on their own: the text is part of the picture. They only look wrong if the picture was trimmed or re-encoded after the burn.

Sources

Reviewed 2026-10-07. Steps not attributed to a source are general video practice or computed above.