Subtitle problems
Subtitles out of sync: find the offset and fix it
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 rate | One frame | 100 frames | 1000 frames |
|---|---|---|---|
| 24 fps | 0.042 s | 4.2 s | 41.7 s |
| 25 fps | 0.040 s | 4.0 s | 40.0 s |
| 30 fps | 0.033 s | 3.3 s | 33.3 s |
| 60 fps | 0.017 s | 1.7 s | 16.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.
| Format | Extension | What YouTube documents |
|---|---|---|
| SubRip | .srt | Basic versions only, no style markup, plain UTF-8 |
| SubViewer | .sbv, .sub | Basic versions only, no style markup, plain UTF-8 |
| WebVTT | .vtt | Positioning supported, styling limited to b, i, u |
| TTML, DFXP | .ttml | Partial 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 see | What it usually means | Do this |
|---|---|---|
| Captions are late by the same amount throughout | A constant offset between file and cues | Shift every cue by that amount (step 3) |
| Correct at the start, late at the end | The cue file was written for a longer or shorter cut | Re-time against the upload file (step 4) |
| Correct at the end, early in the middle | A scene was removed after the captions were made | Re-time from the current cut (step 4) |
| Drift only on one platform | That platform trimmed or looped the file, or re-timed the track | Check the platform's own timing (step 5) |
| Error grows roughly every N seconds | Frame-rate mismatch between the editor and the player | Re-export and re-time at one frame rate (step 2) |
| Only burned-in captions drift | The burn was done on an earlier version of the file | Re-burn from the current export (step 6) |
Fix it step by step
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.
Check the frame rate of the file you will upload
ffprobe -hide_banner in.mp4prints 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.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.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.
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.
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.
Values worth checking before you shift anything
| Frame rate of the upload file | Read it with ffprobe · must match the rate the cues were timed on |
|---|---|
| Offset type | Constant or growing · decides shift versus re-time |
| One frame at 30 fps | 0.033 s · computed as 1 divided by 30 |
| Shift command | -itsoffset 1.20 before the subtitle input |
| Stream copy | -c copy · re-encodes nothing |
| Validation | Play the result from 0:00 to the end |
A repair order that works
- Confirm the upload file: one file, one frame rate, one render.
- Measure the error at the start, the middle and the end.
- Constant error: shift the whole track by that amount.
- Growing error: re-time the cues against the current file.
- Validate the output from the first cue to the last.
- 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
- FFmpeg Documentation: Command line options (-map, -c copy, -itsoffset) (official, checked 2026-10-07)
- FFmpeg Documentation: Video filters (crop, pad, scale, setsar, subtitles) (official, checked 2026-10-07)
- YouTube Help: Add subtitles & captions (official, checked 2026-10-07)
- YouTube Help: Use automatic captioning (official, checked 2026-10-07)
Reviewed 2026-10-07. Steps not attributed to a source are general video practice or computed above.