Last active
June 1, 2021 13:47
-
-
Save fritschy/ea8e944469433ed78fe3f3af98fb1042 to your computer and use it in GitHub Desktop.
Wrapper around my mostly-used-part of 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
#!/usr/bin/env zsh | |
set -e | |
FFMPEG="$HOME/Downloads/ffmpeg-4.2.1-amd64-static/ffmpeg" | |
CRF=20 | |
VCODEC=libx264 | |
PRESET= | |
DEBUG=x | |
FORCE=() | |
BASENAME=0 | |
MINSIZE=0 | |
AC=() | |
AB=512000 | |
TUNE=() | |
CROP="w=in_w:h=in_h" | |
RESAMPLE=() | |
PARAMS=() | |
RATE=() | |
SUSPEND=0 | |
SUSPENDER= | |
VF=() | |
MERGE=() | |
EXTRA=() | |
EXTRANAME=() | |
DESHAKE1=() | |
DESHAKE2=() | |
ALL=(-map 0) | |
CHPST=() | |
ME=() | |
CROPDETECT=0 | |
SIZE="" | |
FMT="" | |
x() { | |
echo "> $*" | |
eval "$@" | |
} | |
suspend_on_user_logon() { | |
W=(who --ips) | |
W=(w -shuoif) | |
U=$(whoami) | |
exec 2>/dev/null | |
echo "Suspending process(es) $* when a user logs on..." | |
pid_exists() { | |
kill -0 $1 2>/dev/null | |
} | |
stopped= | |
while sleep 0.5 | |
do | |
existing=() | |
for i in $* | |
do | |
if pid_exists $i | |
then | |
existing=($i $existing) | |
else # replace program names with PIDs | |
p=$(pidof $i) | |
pid_exists $p && existing=($p $existing) | |
fi | |
done | |
if [ -z "$existing" ] | |
then | |
continue | |
fi | |
if $W | grep -qv "^$U " | |
then | |
if [ -z "$stopped" ] | |
then | |
kill -STOP $existing | |
echo | |
echo "$(date +%F+%T): Suspended $existing, $($W | grep -v "^$U " | cut -d\ -f1 | sort -u | tr '\n' ' ')" | |
stopped=1 | |
fi | |
elif [ -n "$stopped" ] | |
then | |
echo "$(date +%F+%T): Continued $existing" | |
kill -CONT $existing | |
stopped= | |
fi | |
done | |
} | |
do_nice() { | |
nice -0 "$@" | |
} | |
append_vf() { | |
if [ "$VF" ] | |
then | |
VF=$VF,$1 | |
else | |
VF=$1 | |
fi | |
} | |
prepend_vf() { | |
if [ "$VF" ] | |
then | |
VF=$1,$VF | |
else | |
VF=$1 | |
fi | |
} | |
while [ "$1" ] # BEGIN OF ARGUMENT PARSING | |
do | |
case "$1" in | |
-ffmpeg|-ff) #HELP# choose ffmpeg executable # | |
FFMPEG=$2 | |
shift | |
;; | |
-deshake) #HELP# apply deshake filter # | |
DESHAKE1=vidstabdetect=shakiness=10:show=2:mincontrast=0.7 | |
DESHAKE2=vidstabtransform=crop=black:smoothing=180:optzoom=0:interpol=bicubic | |
;; | |
-size) #HELP# set size of output video, keep aspect reatio in mind! # | |
SIZE=$2 | |
shift | |
;; | |
-me) #HELP# show motion estimate # | |
case $2 in | |
-*) | |
ME=mestimate=epzs:mb_size=32:search_param=64 | |
;; | |
*) | |
ME=$2 | |
shift | |
;; | |
esac | |
;; | |
-deshake1) #HELP# fine tune deshake1 # | |
DESHAKE1=$2 | |
shift | |
;; | |
-deshake2) #HELP# find tune deshake2 # | |
DESHAKE2=$2 | |
shift | |
;; | |
-watch|-suspend) #HELP# watch for user login and suspend if anyone comes looking # | |
SUSPEND=1 | |
;; | |
-x|-extra) #HELP# extra arguments to ffmpeg, e.g. -x -r -x 20 (set rate to 20 fps) # | |
EXTRA=($EXTRA $2) | |
shift | |
;; | |
-rate|-r|-fps) #HELP# set fps # | |
RATE=(-r $2) | |
shift | |
;; | |
-params) #HELP# set encode params # | |
PARAMS=$2 | |
shift | |
;; | |
-crop) #HELP# set cropping # | |
prepend_vf $2 | |
shift | |
;; | |
-debug) #HELP# set debug to display what is happening # | |
DEBUG=echo | |
;; | |
-aac) #HELP# re-encode audio to aac, might need -notall # | |
AC=($AC -acodec aac) | |
;; | |
-ac3) #HELP# re-encode audio to ac3, might need -notall # | |
AC=($AC -acodec ac3) | |
;; | |
-merge) #HELP# merge another file into final MKV # | |
MERGE=($MERGE $2) | |
shift | |
;; | |
-resample) #HELP# resample audio to 44.1kHz # | |
RESAMPLE=(-af aresample=resampler=soxr -ar 44100) | |
;; | |
-ab) #HELP# set audio bitrate # | |
AC=($AC -ab $(($2 * 1000))) | |
shift | |
;; | |
-codec|-vcodec) #HELP# set video codec # | |
VCODEC=$2 | |
shift | |
;; | |
-acodec) #HELP# set audio codec # | |
AC=($AC -acodec $2) | |
shift | |
;; | |
-nice) #HELP# run encoder job nice # | |
do_nice() { | |
nice "$@" | |
} | |
;; | |
-crf) #HELP# set crf # | |
CRF=$2 | |
shift | |
;; | |
-x26?) #HELP# use -x264 or -x265 encoder # | |
VCODEC=libx26${1#-x26} | |
;; | |
-basename) #HELP# output filename will be in CWD # | |
BASENAME=1 | |
;; | |
-f|-force|--force) #HELP# overwrite output file # | |
FORCE=-y | |
;; | |
-preset) #HELP# set encode preset # | |
PRESET=$2 | |
shift | |
;; | |
-tune) #HELP# set encoder tune # | |
TUNE=(-tune $2) | |
shift | |
;; | |
-vf) #HELP# set ffmpet video filter # | |
append_vf $2 | |
shift | |
;; | |
-notall) #HELP# do not handle all streams of source file (only e..g first audio stream) # | |
ALL=() | |
;; | |
-10bit) #HELP# encode using 10bits, e.g. with x265 (although x264 can do this as well...) # | |
EXTRA=($EXTRA -pix_fmt yuv420p10le) | |
EXTRANAME=($EXTRANAME 10bit) | |
;; | |
-cropdetect) #HELP# try detecting crop of source video, use with caution, needs to pre-process # | |
CROPDETECT=1 | |
;; | |
-downmix5.1|-stereo) #HELP# downmix to stereo # | |
ALL=() | |
# | |
# with lfe | |
#prepend_af "pan=stereo|FL < 1.0*FL + 0.707*FC + 0.707*BL + 0.5*LFE|FR < 1.0*FR + 0.707*FC + 0.707*BR + 0.5*LFE" | |
# with back and surround | |
#prepend_af "pan=stereo|FL < 1.0*FL + 0.707*FC + 0.707*BL + 0.707*SL|FR < 1.0*FR + 0.707*FC + 0.707*BR + 0.707*SR" | |
# with back, surround and lfe | |
#prepend_af "pan=stereo|FL < 1.0*FL + 0.707*FC + 0.707*BL + 0.707*SL + 0.5*LFE|FR < 1.0*FR + 0.707*FC + 0.707*BR + 0.707*SR + 0.5*LFE" | |
#prepend_af "pan=stereo|FL < 1.414*FL + 0.707*FC + 0.707*SL|FR < 1.414*FR + 0.707*FC + 0.707*SR" | |
#prepend_af "pan=stereo|FL < 1.0*FL + 0.707*FC + 0.707*SL|FR < 1.0*FR + 0.707*FC + 0.707*SR" | |
EXTRA=($EXTRA -ac 2) | |
EXTRANAME=($EXTRANAME stereo) | |
;; | |
-fmt) #HELP# output specific format, i.e. avi, mkv, mp4 | |
FMT="$2" | |
shift | |
;; | |
-psvita) | |
SIZE=960x540 | |
FMT=mp4 | |
# this is not needed, level and aac audio however are vital | |
# PRESET=veryfast | |
VCODEC=libx264 | |
PARAMS=(level=31) | |
AC=(-acodec aac -ab 96k) | |
ALL=() | |
EXTRANAME=($EXTRANAME vita) | |
;; | |
-nosize) | |
SIZE= | |
;; | |
--) #HELP# signal end of options, next argument needs to be source file # | |
shift | |
break | |
;; | |
--help|-h) #HELP# show this help message # | |
echo "Usage: $0 [options] -- <input-file>" | |
echo | |
while read line # END OF ARGUMENT PARSING | |
do | |
case "$line" in | |
*"BEGIN OF ARGUMENT PARSING") | |
docs=1 | |
continue | |
;; | |
*"END OF ARGUMENT PARSING") | |
docs=0 | |
break | |
;; | |
*"#HELP#"*) | |
[ "$docs" != 1 ] && continue | |
opts=${line%%)*} | |
text=${line##*#HELP#} | |
text=${text% #} | |
printf " %-18s\t%s\n" "$opts" "$text" | |
;; | |
esac | |
done < "$0" | sort | |
echo | |
exit 0 | |
;; | |
esac | |
shift | |
done | |
[ -x "$FFMPEG" ] || { | |
echo "ffmpeg '$FFMPEG' does not exist/is not executable, using default 'ffmpeg'" | |
FFMPEG=ffmpeg | |
} | |
set -u | |
if ! [ "$AC" ] | |
then | |
AC=(-acodec copy) | |
fi | |
if [ "$PARAMS" ] | |
then | |
case "$VCODEC" in | |
libx264) | |
PARAMS=(-x264opts $PARAMS) | |
;; | |
libx265) | |
PARAMS=(-x265-params $PARAMS) | |
;; | |
esac | |
fi | |
[ "$1" ] || { | |
echo "Usage: $0 [options] -- <input-file>" 2>&1 | |
exit 1 | |
} | |
# Actual file to encode... | |
i=$1 | |
if [ "$i" != "${i//-crf/}" ] || [ "$i" != "${i//SDR/}" ] | |
then | |
continue | |
fi | |
if ! [ "$FMT" ] | |
then | |
ext=mkv | |
else | |
ext=$FMT | |
fi | |
# BUild output filename | |
local out="${${i//HDR/SDR}:r}-${VCODEC//lib/}$(for i in $EXTRANAME; echo ".$EXTRANAME").$([ "$PRESET" ] && echo "$PRESET" || echo default)$([ "$SIZE" ] && echo ".$SIZE").crf$CRF.$ext" | |
if [ "$BASENAME" = 1 ] | |
then | |
out="${out:t}" | |
fi | |
if [ -e "$out" ] && [ -z "$FORCE" ] | |
then | |
echo "Output file exists but I have no force..." | |
exit | |
fi | |
CMDNAME=$(basename $FFMPEG) | |
if command -v chpst >/dev/null | |
then | |
CMDNAME=docker-node | |
CHPST=(chpst -b $CMDNAME) | |
fi | |
if [ $SUSPEND = 1 ] | |
then | |
suspend_on_user_logon $CMDNAME & | |
trap "kill -9 $!" INT EXIT | |
fi | |
# Do we want ffmpeg to do ME? | |
if [ "$ME" ] | |
then | |
prepend_vf $ME | |
fi | |
if [ $CROPDETECT = 1 ] | |
then | |
echo Detecting crop... | |
prepend_vf $($FFMPEG -i "$i" -vf cropdetect -vcodec rawvideo -y -f matroska -an -ss 0:02:00 -t 1 /dev/null 2>&1 | tail -n5 | head -n1 | sed -e 's/^.* crop=/crop=/g') | |
fi | |
if [ "$DESHAKE1" ] | |
then | |
XVF=$VF # save actual VF | |
append_vf ${DESHAKE1}:result="${out}.trf" | |
# append_vf scale=w=16:h=16 # also rescale to copy less data? | |
$DEBUG do_nice $CHPST $FFMPEG $FORCE $RATE -i "$i" -vf ${VF} -vcodec rawvideo $AC -an $EXTRA -f matroska /dev/null | |
VF=$XVF # restore out actual VF | |
append_vf ${DESHAKE2}:input="${out}.trf" | |
fi | |
if [ "$i" != "${i//HDR/}" ] | |
then | |
echo Tonemapping ITU.2020 to ITU.709 for $i | |
# prepend_vf "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable,zscale=t=bt709:m=bt709:r=tv,format=yuv420p" | |
prepend_vf scale=in_color_matrix=bt2020:out_color_matrix=bt709 | |
fi | |
# Setup $VF for ffmpeg | |
if [ $#VF != 0 ] | |
then | |
VF=(-vf $VF) | |
fi | |
to-option() { | |
set +u | |
[ "$2" ] && echo "$1 $2" | |
set -u | |
} | |
$DEBUG do_nice $CHPST $FFMPEG $FORCE $RATE -i "$i" $VF -vcodec $VCODEC -scodec copy $PARAMS $TUNE $(to-option -preset $PRESET) $(to-option -s $SIZE) -crf $CRF $RESAMPLE $AC $ALL $EXTRA $(to-option -f $FMT) "$out" | |
if [ $#MERGE -ne 0 ] | |
then | |
echo "Merging additional streams into new mkv" | |
$DEBUG $CHPST mkvmerge $out $MERGE -o $(basename $out .$FMT)-merged.mkv | |
fi | |
# zsh encode-video.zsh -f -fps 60 -x264 -crf 20 -vf tmix=frames=5,"select=not(mod(n\,20)),setpts=0.05*PTS" -deshake -ff ffmpeg -preset fast -- MOV_3147.mp4 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment