32

I have a mkv (h264) video that is 23.976 fps (24000/1001) but I want to convert it to 25fps without reencoding and loosing quality. I know mkvmerge can do it ( with option --default-duration '0:25fps') but I'd like to do it directly from ffmpeg if possible According to the docs this should work:

ffmpeg -i input.mkv -r 25 -vcodec copy output.mkv

but when I execute it I only get the same video fps. What is the correct method to do it (if exists) in ffmpeg?

6
  • 1
    I believe that is not possible with FFmpeg at the moment. -r is not compatible with stream copy, and there are no bitstream filters to change the framerate.
    – Ely
    Jun 13, 2016 at 14:36
  • 1
    too bad. I'll have to use mkvmerge every time. thanks
    – phate89
    Jun 13, 2016 at 14:42
  • 1
    There is a convoluted way to do this with regular ffmpeg and a direct way to do this with an old modded version of ffmpeg. If you're interested, I'll write it up as an answer.
    – Gyan
    Jun 13, 2016 at 17:34
  • 1
    Yes thanks..I'd like to do it without extra tools (I already need ffmpeg)
    – phate89
    Jun 13, 2016 at 18:15
  • 1
    @Mulvya you're talking about this right ? I hesitated to link it, but it's old now.. Interested in the convoluted way to do it with regular FFmpeg though.
    – Ely
    Jun 17, 2016 at 10:42

5 Answers 5

20

Here's the method using current versions of FFmpeg. It relies on the concat demuxer not rescaling the PTS of inputs after the first file, but simply applying a fixed offset. Let's say you have a 30 fps stream with a timescale of 15360 (typical of FFmpeg output). That means frame 0 has PTS 0 and frame 30 has PTS 15360. This would become a 45 fps stream if we could change the timescale to 23040 without affecting the PTS values.

Essentially, that's what the method below does.

1. Identify the source properties.

Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1171 kb/s,
       30 fps, 30 tbr, 15360 tbn (default)

You want to note the source properties, especially resolution and tbn.


2a. (Optional) Change the timescale to something convenient, to make calculations simpler.

ffmpeg -i in.mp4 -c copy -an -video_track_timescale 30 in-v30.mp4

This gets us

Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1171 kb/s, \
       30 fps, 30 tbr, 30 tbn (default

If you do this step, the new timescale should be equal or an integral multiple of the original framerate.

2b. Calculate the timescale needed, so that for target framerate x, PTS of frame # x in the source should have the same value as the new tbn. If you carried out step 2a, this is very easy and it's simply the new framerate. So, for target fps 45, new tbn should be 45.


3. Generate dummy video.

ffmpeg -f lavfi -i color=s=1280x720:r=45:d=1 -profile:v main -video_track_timescale 45 0.mp4

All properties should be same like resolution, H.264 profile, pixel format, refs count..etc for best results.


4 Concat the videos.

First make a text file

file '0.mp4'
file 'in-v30.mp4'

Then, the concat

ffmpeg -f concat -i list.txt -c copy -video_track_timescale 45 45fps.mp4

The output file will have the 2nd video playing at 45 fps.

5. Now, cleave off the dummy preroll

ffmpeg -ss 1.1 -i 45fps.mp4 -c copy -avoid_negative_ts make_zero in45.mp4

and you have

Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 1757 kb/s, \
       45 fps, 45 tbr, 11520 tbn (default)

I did say this was convoluted!

6
  • 4
    very clever, good answer. Dec 25, 2017 at 0:14
  • What about the audio?
    – Freedo
    Jun 15, 2020 at 6:12
  • 1
    ulatekh's more recent answer is simpler, and also handles audio. Jul 20, 2020 at 18:56
  • 1
    Extensive testing with this, a completely effective solution, has convinced me that as a method it is misconceived. It can be adapted very easily to be a far better solution to the problem of changing video from 25fps PAL to 24fps Film, or vice versa. But to use this approach with a 30fps video is a bad idea: conversion between 30fps NTSC and either 25fps PAL or 24fps Film rates definitely needs a different approach. To convert NTSC to PAL you must delete 5 frames a second, by dumping every 6th frame, to retain the video stream's original duration, without which you quickly lose audio sync.
    – Ed999
    Jan 13, 2021 at 17:43
  • 1
    The goal was/is to re-time frames without re-encoding. Considerations of sync or maintaining original duration are beyond the remit.
    – Gyan
    Jan 14, 2021 at 6:01
13
ffmpeg -itsscale 1.0427083 -i input.mp4 -codec copy output.mp4

This correctly slows down a 25 fps mp4 created by Handbrake from a PAL DVD source to 23.974 fps. The original show is NTSC. The audio stays in sync all the way through the now 47 minutes running time. It is very fast as no decoding/encoding is being done. However, there are audio glitches (dropouts) at roughly 3-second intervals throughout. Same result with vcodec substituted for codec, except that while video is not re-encoded, the audio is re-encoded at half the original bitrate and still has the dropout glitches.

ffmpeg -itsscale 1.0427083 -i input.mp4 -vcodec copy -filter:a "atempo=0.959041" output.mp4

This eliminates the audio dropouts, but does re-encode audio. That's much faster than re-encoding video. The remaining drawback is that it defaults to half the original audio bit rate. Need to figure out how to set the audio bit rate for the re-encode.

2
  • Thank you for this amazing answer! It's exactly what I needed to fix some PAL video that's really from an NTSC source.
    – Chris
    Sep 20, 2021 at 23:21
  • to set audio bit rate: -b:a like this: -b:a 256k
    – Ali Akbar
    Dec 22, 2022 at 0:07
8

Use -itsscale on the input video to achieve an effective framerate change. It works fine with -vcodec copy.

3
  • This only changes timestamps, not fps, so to achieve the same fps, frames may be dropped.
    – Minty
    Aug 12, 2021 at 7:59
  • 2
    @Barbara: No, it'll be fine. The stored FPS value is mostly informational; the presentation-time-stamps are what's really important.
    – ulatekh
    Aug 12, 2021 at 21:13
  • This depends on the player, apparently. I've accelerated a sample in several different ways, and this one caused frames to be dropped upon playback, very visibly at 24000/1001–60 acceleration.
    – Minty
    Aug 13, 2021 at 22:08
3

Building on @Eric's answer:

If you aren't doing these frame rates, the itsscale value might not be the same for you. To figure out your values, some simple math is needed.

In my case, my desired frame rate was 23.98, just slightly different than theirs. If your input frame rate is different than ffmpeg's default of 25, use that input frame rate instead of 25.

input frame rate / desired frame rate

In my case, the input frame rate was the default frame rate of ffmpeg (25 fps), so my calculation looked like this:

25 / 23.98 = ~ 1.0425354462051709758131.

I was using my desktop's calculator, and since this is likely a repeating number, some precision will be lost. It likely won't matter for most normal lengths of video files, but if you have some piece of video that's SUPER long, you might lose sync further into the video

Then you need to do the same for your atempo filter (using your values, of course).

desired frame rate / input frame rate

In my case: 23.98 / 25 = .9592

1

You can do this using the bitstream filter setts. This also avoids the hassle of writing a raw stream out to a file, then remuxing it. This works because while you can't use normal filters with codec copy (-c:v copy) since they work on the decoded video stream, you can use bitstream filters which work on the encoded stream.

For example to change frame rate to 60 fps, insert

-bsf:v setts=ts=STARTPTS+N/TB_OUT/60

This should set both pts and dts without decoding the stream. If you have a variable frame rate video you may need something like this instead

-bsf:v setts=ts='if(PREV_OUTPTS+9223372036854775808\,PREV_OUTPTS\,STARTPTS)+PREV_OUTDURATION*2'

This modifies the durations of the frame instead of assuming each frame has a constant duration. The odd looking if expression is to ensure the first frame is offset correctly, since PREV_OUTPTS is set to -9223372036854775808 (min value of 64-bit int) during the first frame, and we need to replace it with zero.

If you have B-frames, this may mess up decoding since dts may need to be smaller than pts, but it seems to work fine for me anyways. You can try replace setts=ts with setts=pts if you run into trouble. See https://ffmpeg.org/ffmpeg-bitstream-filters.html#setts for details

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .