The aim here is to take e.g. a 25fps video, change it so that it becomes 24fps using the same frames (so is slightly longer), and also stretch the audio to fit the new length.
To take a 24fps video and make it 30fps (same frames, sped up, shorter duration): (from this stackoverflow
With re-encoding
Note that you have to compute the 1.25 passed to setpts.
ffmpeg -y -i in.mp4 -vf "setpts=1.25*PTS" -r 24 out.mp4
Without re-encoding
This is useful since there is no need to compute the fraction of input-fps/output-fps.
First step - extract video to raw bitstream
ffmpeg -y -i in.mp4 -c copy -f h264 in.h264
Remux with new framerate
ffmpeg -y -r 24 -i in.h264 -c copy out.mp4
Stretching Audio
You could use a DAW like Reaper, or a command line app like rubber band.
Combined script
This is something rough cobbled together. I think it works, but
haven't properly tested it, and it's not robust. The getvdur script
returns the duration of a media file in the format: filename.mp4: 3661 1h1m1s.
Something more robust could be written in Python.
#!/bin/bash
# usage:
# retime OFR file1 [file2...]
# where OFR is TARGET frame rate.
chk() {
for s; do
which "$s" >& /dev/null || { echo "no $s"; exit 1; }
done
}
chk ffprobe ffmpeg rubberband-r3 getvdur
ofrate="$1"
shift
# temporary filenames
wav="tmp_audio.wav"
rwav="tmp_raudio.wav"
h264="tmp_video.h264"
tmp4="tmp_video.mp4"
for ifn; do
if ! rm -f "$wav" "$rwav" "$h264" "$tmp4"; then
echo "Failed to delete temporary files"
exit 1
fi
ofn="rt_$ifn"
if [ -e "$ofn" ]; then
echo "$ofn exists already"
continue
fi
# must h264 to mp4 to get resulting dur for rubber band
ffmpeg -y -i "$ifn" "$wav"
if [ ! -e "$wav" ]; then
echo "Failed to extract audio"
exit 1
fi
ffmpeg -y -i "$ifn" -an -c:v copy -f h264 "$h264"
if [ ! -e "$h264" ]; then
echo "Failed to extract h264 video"
exit 1
fi
VIDCODEC="-c:v h264_nvenc -b:v 8M" # full hd with nvidia gpu
VIDCODEC="-b:v 8M" # full hd using cpu
ffmpeg -y -r "$ofrate" -i "$h264" -an $VIDCODEC "$tmp4"
if [ ! -e "$tmp4" ]; then
echo "Failed to convert frame rate to $tmp4"
exit 1
fi
d="$(getvdur "$tmp4" | cut -f2 -d: | awk '{ print $1 }')"
rubberband-r3 -D$d "$wav" "$rwav"
if [ ! -e "$rwav" ]; then
echo "Failed to stretch audio"
exit 1
fi
ffmpeg -i "$tmp4" -i "$rwav" -c:v copy -map 0:0 -map 1:0 -b:a 160k "$ofn"
if [ ! -e "$ofn" ]; then
echo "Failed to combine to output file"
exit 1
fi
done