-
-
Save majal/1e63a511b41faeae188083777f8e4b46 to your computer and use it in GitHub Desktop.
Multi-threaded minterpolate in ffmpeg
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Multicore minterpolate in ffmpeg | |
# Just slice & process & concat | |
# NB: The concat points between slices may be weird | |
# Default arguments | |
ff=/usr/bin/ffmpeg | |
fps=60 | |
dur=00:00:10 | |
enc=libx264 | |
[ "${OSTYPE}" == "darwin"* ] && task=$(( $(sysctl -n hw.logicalcpu) - 2 )) || task=$(( $(nproc --all) - 2 )) | |
# Reference: https://ffmpeg.org/ffmpeg-filters.html#minterpolate | |
m_args=":mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1" | |
ff_args=( -hide_banner -loglevel level+warning -nostats -y ) | |
ff_args_enc=( -crf 20 -preset slow ) | |
################################################################################ | |
# Usage notes | |
err_args () { | |
printf "\nUsage: %s -i INPUT_FILE [ARGUMENTS] OUTPUT_FILENAME\n" "$(basename "${0}")" | |
printf "\n" | |
printf "Arguments:\t\t\t\t\t[default values]\n" | |
echo | |
printf "\t-b str\tffmpeg binary path\t\t[%s]\n" "${ff}" | |
printf "\t-d hms\tSlices duration [hour:min:sec]\t[%s]\n" "${dur}" | |
printf "\t-e str\tEncoder name\t\t\t[%s]\n" "${enc}" | |
printf "\t-f str\tffmpeg encoding arguments\t[%s]\n" "'${ff_args_enc[*]}'" | |
printf "\t-p int\tProcess/thread count. 0 is max\t[%s]\n" "${task}" | |
printf "\t-r int\tTarget FPS\t\t\t[%s]\n" "${fps}" | |
printf "\t-m str\tminterpolate arguments\t\t[%s]\n" "'${m_args}'" | |
printf "\t-s hms\tStart point of source video\t[00:00:00]\n" | |
printf "\t-t hms\tEnd point of source video\t[until end of the video]\n" | |
echo | |
exit 1 | |
} | |
# Clean slices and temporary files | |
clean () { | |
rm -f _cut.mp4 | |
rm -f _s_?????.mp4 | |
rm -f _m__s_?????.mp4 | |
rm -f _list.txt | |
} | |
while getopts 'i:r:s:t:e:p:d:b:m:f' OPTION; do | |
case $OPTION in | |
i) | |
in=$OPTARG # input file | |
;; | |
r) | |
fps=$OPTARG # target fps, fraction is also OK | |
;; | |
s) | |
ss=$OPTARG # begin time of input | |
;; | |
t) | |
to=$OPTARG # end time of input | |
;; | |
e) | |
enc=$OPTARG # encoder | |
;; | |
p) | |
task=$((OPTARG)) # process count, xargs use max when it is 0 | |
;; | |
d) | |
dur=$OPTARG # parallel block length | |
;; | |
b) | |
ff=$OPTARG # ffmpeg binary | |
;; | |
m) | |
m_args=$OPTARG # minterpolate arguments | |
;; | |
f) | |
ff_args_enc=$OPTARG # ffmpeg encoding arguments | |
;; | |
*) | |
err_args | |
;; | |
esac | |
done | |
shift $((OPTIND - 1)) | |
# Set output file | |
out="${1}" | |
# Select input file | |
temp="${in}" | |
# Check arguments and files | |
if [ -z "${in}" ]; then | |
echo | |
echo "$(basename ${0}): missing input file" | |
err_args | |
fi | |
if [ -z "${out}" ]; then | |
echo | |
echo "$(basename ${0}): missing output filename" | |
err_args | |
fi | |
# Set start and end duration for minterpolation | |
duration=() | |
if [ -n "${ss}" ]; then | |
duration+=(-ss) | |
duration+=("${ss}") | |
temp=_cut.mp4 | |
fi | |
if [ -n "${to}" ]; then | |
duration+=(-to) | |
duration+=("${to}") | |
temp=_cut.mp4 | |
fi | |
################################################################################ | |
clean | |
echo | |
echo "Processing ${in} ..." | |
echo | |
# Use origin file or cut a part from origin file | |
if [ "${temp}" == _cut.mp4 ]; then | |
"${ff}" "${ff_args[@]}" "${duration[@]}" -i "${in}" -c copy "${temp}" | |
fi | |
# Make slices for parallel use | |
"${ff}" "${ff_args[@]}" -i "${temp}" -c copy -f segment -segment_time "${dur}" '_s_%05d.mp4' | |
xargs_cmd="/usr/bin/nice -n 19 ${ff} ${ff_args[@]} -i _fname -vf minterpolate=fps=${fps}${m_args} -c:a copy -c:v ${enc} ${ff_args_enc[@]} _m__fname" | |
# Minterpolate the slices. Get coffee, go out and exercise, or sleep. This will a while. | |
find . -name '_s_?????.mp4' | sed 's@^.*/@@g' | xargs -I "_fname" -t -P ${task} -- ${xargs_cmd} && echo "Processed slice: $(ls _m__s_?????.mp4 | wc -l) of $(ls _s_?????.mp4 | wc -l) ..." | |
# Make a list of minterpolated slices | |
printf "file '%s'\n" ./_m__s_?????.mp4 > _list.txt | |
# Concatenate the result | |
"${ff}" "${ff_args[@]}" -f concat -safe 0 -i _list.txt -c copy "${out}" | |
echo | |
echo "Processing of ${in} complete. Created ${out}." | |
echo | |
echo "################################################################################" | |
echo | |
clean |
Have you been satisfied with script?
How good is the segmentation cut and concatenate, does it need manual work to make it smooth?
I would ditch the dur parameter and use videolength / nproc, or is there complications?
Hi @anttiryt! Satisfied? Honestly I'm now not using the script because I don't like those missed single frames during concatenate. I still have no idea on how to fix those frames on the concatenate points.
You know what? I think the videolength / nproc is a much better idea.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The script will not work with
ffplay
. Becauseffplay
will read the video from start to finish, while the script cuts the video into pieces and runs minterpolate on each slice in parallel.