#!/bin/sh # Copyright © 2010-2014, 2016 Jonas Smedegaard # Description: Recode a video into web-optimized format(s) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Depends: ffmpeg melt mediainfo # Recommends: moreutils vainfo vpx-tools vorbis-tools opus-tools # # TODO: # * improve workflow # + check initially and fail if all needed tools are not available # + offer to skip rendering again if an output file exist already # + maybe support extracting keyframes to aid in picking cover image # # # + offer to produce a full ladder at once # # # * improve configurability # + support --width and --height, # resolving other dimension from (explicit or source) aspect ratio # + make choice of encoders configurable # + warn if inferior containers are used, e.g. non-WebM for VP8/VP9 # # * tune defaults # + adapt VP8/VP9 quantizer based on frame size # # + maybe pass option real_time=-2 to melt # # + maybe reduce threads to "spend" total available cores only once # # + apply application option when using opusenc # + use joint stereo for low-bitrate Opus speech # + maybe refine audio bandwidth algorithms # # + change VP8/VP9 bandwidth algorithm # # + tune VP8 parameters: # # + maybe use High profile for H.264 # # + compute (or hardcode) html5 codec types, e.g. using mp4v2-utils # # + generalize GOP handling to use "Closed GOPs", # with default "normal" option of 200f@25fps or 240f@30fps (both 8s) # and "low-latency" option of 48f (1.92s at 25fps or 1.6s at 30fps) # # # # # * players # + disable flash player by default # + support HLS player, when HLS is supported # # * normalize each infile separately when xml fed as infile keeps sync # maybe as workaround re-feed audio separately from xml, as done at # # * support DASH # # * support watermark # * support superstable live streaming, e.g. (maybe faked) endless source # + lipsync may require single stream of equally chunked tracks # # * support adaptive streaming # + compute ladder from mathematical model # as in "Optimal design of encoding profiles for ABR streaming" # # * support low-latency streaming a.k.a. "LL", i.e. 3-5s latency # + LL-HLS # # + CMAF, using either fmpeg or (when released) GPAC # # # * support realtime streaming a.k.a. "ULL", i.e. less than 0.5s latency # set -e PRG=$(basename "$0") showhelp() { cat < EOF } exit1() { response="${1:+Error: }${1:-Internal error!}" echo >&2 "$response" exit 1 } # defaults formats=vp8,vp9,avc stillframe=0 samplestart=0 samplelength=150 compression=normal # establish/verify rendering settings for reference codec # # 1) pick reference frame:encoding format: 360p30:h.264 # 2) pick reference codec (incl. normal settings) # 3) render reference frame format with --compression=quality # * check that bpp is near "refbpp" of 0.12 # (otherwise adapt settings or do research and update refbpp) # 4) render misc. frame formats (esp. modulus 16) with --compression=quality # * check that bpp derives from refbpp by "rule of .75" # (otherwise adapt settings and/or do research and update rule) # 5) render misc. frame formats with --compression=normal # * check that bpp derives from refbpp by "rule of .75" # 6) optionally pick dirty/hq settings # * check that --compression=quality has filesize similar as normal # establish/verify rendering settings for non-reference codec # # 1) pick normal settings # 2) render reference frame format with --compression=quality # * check that this "codec-bpp" deviates reasonably from refbpp # 3) render misc. frame formats (esp. modulus 16) with --compression=quality # * check that bpp derives from codec-bpp by "rule of .75" # (otherwise adapt settings and/or extend rule) # 4) render misc. frame formats with --compression=normal # * check that bpp derives from codec-bpp by "rule of .75" # 5) optionally pick dirty/hq settings # * check that --compression=quality has similar filesize as normal # Theora seems to compress ~100% worse than H.264 # (reportedly 76% worse in 2010, and libtheora has not improved since) # factor_theora=200/100 # VP8 is rumored to compress ~20% worse than H.264 factor_vp8=120/100 # VP9 compresses ~30% better than H.264 for practical use-cases # factor_vp9=70/100 # Mimic ReplayGain level when loudness is enabled (EBU R128: -23LUFS) # lufs=-18 # parse cmdline options TEMP="`getopt -s sh -o hp:s:a:r:b:t: -l help,profile:,size:,aspect:,rate:,video:,refbpp:,formats:,audio:,audioprefilter:,loudness,loudness-data:,filter:,stem:,title:,still-frame:,sample,sample-start:,sample-length:,compression:,quality:,gpu:,gpudevice: -n "$PRG" -- "$@"`" || exit1 "Internal getopt error." eval set -- "$TEMP" while true ; do case "$1" in -h|--help) showhelp; exit;; -p|--profile) profile="$2"; shift 2;; -s|--size) size="$2"; shift 2;; -a|--aspect) aspect="$2"; shift 2;; -r|--rate) framerate="$2"; shift 2;; --video) video="$2"; shift 2;; --refbpp) refbpp="$2"; shift 2;; --formats) formats="$2"; shift 2;; --audio) audio="$2"; shift 2;; --audioprefilter) audioprefilters="${audioprefilters:+$audioprefilters }-filter $2"; shift 2;; --loudness) loudness=yes; shift;; --loudness-data) loudness=yes; loudness_data="$2"; shift 2;; --filter) filters="${filters:+$filters }-filter $2"; shift 2;; --stem) stem="$2"; shift 2;; -t|--title) title="$2"; shift 2;; --still-frame) stillframe="$2"; shift 2;; --sample) sample=yes; shift;; --sample-start) sample=yes; samplestart="$2"; shift 2;; --sample-length) sample=yes; samplelength="$2"; shift 2;; --compression) compression="$2"; shift 2;; --quality) quality="$2"; shift 2;; --gpu) gpu="$2"; shift 2;; --gpudevice) gpudevice="$2"; shift 2;; --) shift; break;; *) exit1 "Internal error resolving options.";; esac done [ -n "$sample" ] || notsample=yes # Resolve if system has many CPU cores processors=$(nproc) [ $processors -gt 2 ] || processors= # sanitize infiles infiles=$* infile_first=$(perl -e 'print shift @ARGV' $infiles) [ -e "$infile_first" ] || exit1 "Cannot read first input file \"$infile_first\"!" # resolve stem and title (if not explicitly set) stem=${stem:-$(basename "$infile_first" | perl -pe 's/\.[^.]*//')} title=${title:-$stem} #use_ffmpeg_theora=yes #use_ffmpeg_vp8=yes #use_ffmpeg_vp9=yes #use_ffmpeg_avc=yes #use_ffmpeg2theora=yes # TODO... #use_vpxenc=yes # Avoid discrete audio encoders if possible: may cause sync problems #use_oggenc=yes #use_opusenc=yes # Avoid AAC: Lame arguably better than any free AAC encoder # Argument for AAC: Safari possibly lacks mp3 support in video container #use_mp3=yes [ -n "$use_mp3" ] || use_aac=yes # Avoid MP3 VBR but seems ABR is fine: Set to CBR for strict correctnes #use_lame_cbr=yes [ -n "$use_lame_cbr" ] || use_lame_abr=yes # resolve quality/speed hints qkey_theora=q qkey_vp8=crf qkey_vp9=crf qkey_avc=crf q_theora=6 q_vp8=33 cpu_vp8=2 q_vp9=33 cpu_vp9=2 q_avc=23 preset_avc=medium profile_vp8=0 profile_vp9=0 profile_avc=main case "$compression" in normal) :;; dirty) gpu=${gpu:-auto} cpu_vp8=4 cpu_vp9=4 preset_avc=veryfast ;; hq) multipass=yes no_q_theora=yes no_multipass_avc=yes cpu_vp8=0 cpu_vp9=1 # preset_avc=veryslow # faster than medium unneeded as of June 2020 ;; exact) multipass=yes no_multipass_avc=yes ;; quality) no_bitrate=yes ;; *) exit1 "Unknown compression mode \"$compression\".";; esac case "$quality" in auto|'') :;; none) no_q=yes;; *) q=$quality;; esac gpudevice=${gpudevice:-/dev/dri/renderD128} if [ auto = "$gpu" ]; then vainfo=$(vainfo --display drm --device "$gpudevice" 2>/dev/null | grep -Po 'Driver version:\s*\K.*') case "$vainfo" in '') gpu=;; *) for vformat in theora vp8 vp9 avc; do eval "no_gpu_decoder_$vformat=yes" eval "no_gpu_encoder_$vformat=yes" done case $(vainfo --display drm --device "$gpudevice" 2>/dev/null | grep -Po 'VAProfile\S+\s*:\s*VAEntrypointVLD') in *VAProfileVP8Version0_3*) no_gpu_decoder_vp8=;; *VAProfileVP9Profile0*) no_gpu_decoder_vp9=;; *VAProfileH264Main*) no_gpu_decoder_avc=;; esac case $(vainfo --display drm --device "$gpudevice" 2>/dev/null | grep -Po 'VAProfile\S+\s*:\s*VAEntrypointEncSlice') in *VAProfileVP8Version0_3*) no_gpu_encoder_vp8=;; *VAProfileVP9Profile0*) no_gpu_encoder_vp9=;; *VAProfileH264Main*) no_gpu_encoder_avc=;; esac if [ -n "$no_gpu_decoder_vp8$no_gpu_decoder_vp9$no_gpu_decoder_avc" ]; then case "$vainfo" in # TODO *"Intel iHD driver"*) gpu=qsv;; *) gpu=vaapi;; esac fi esac fi case "$gpu" in none|'') gpu=;; qsv) multipass= preset_vp8=medium preset_vp9=medium preset_avc= qkey_avc=qp ;; vaapi) multipass= preset_avc= qkey_avc=qp case "$compression" in dirty) preset_avc= ;; esac ;; *) exit1 "Unknown GPU type \"$gpu\".";; esac [ -z "$gpu" ] && no_gpu=yes || eval "gpu_$gpu=yes" # parse/resolve size and framerate case "$profile" in '') :;; *@*) while read s r foo; do profilesize="${size:-$s}" framerate="${framerate:-$r}" done << EOF $(echo "$profile" | perl -F@ -anE 'say join " ", @F') EOF ;; *p*) while read s r foo; do profilesize="${size:-${s}p}" framerate="${framerate:-$r}" done << EOF $(echo "$profile" | perl -Fp -anE 'say join " ", @F') EOF ;; *) profilesize="$profile" ;; esac size=${size:-$profilesize} case "$size" in qvga) size=320x240;; hvga) size=480x360;; vga) size=640x480;; svga) size=800x600;; xga) size=1024x768;; 240p|wqvga) size=424x240;; 360p|nhd) size=640x360;; 480p|wvga) size=848x480;; 576p|wsvga) size=1024x576;; 720p|wxga|hd) size=1280x720;; esac if [ -n "$size" ]; then while read w h foo; do width="${width:-$w}" height="${height:-$h}" done << EOF $(echo "$size" | perl -Fx -anE 'say join " ", @F') EOF if [ -z "$width" ] || [ -z "$height" ]; then exit1 "Failed to parse size \"$size\"." fi fi case "$framerate" in */*) while read n d foo; do framerate_num="${framerate_num:-$n}" framerate_den="${framerate_den:-$d}" done << EOF $(echo "$framerate" | perl -F/ -anE 'say join " ", @F') EOF ;; ?*) framerate_num="$framerate" framerate_den=1 ;; esac # resolve input size and framerate (needed for computing bitrate) while read w h r s foo; do width_in="${width_in:-$w}" height_in="${height_in:-$h}" framerate_in="${framerate_in:-$r}" scantype_in="${scantype_in:-$s}" done << EOF $(mediainfo --Inform="Video;%Width% %Height% %FrameRate% %ScanType%" \ "$infile_first") EOF [ Progressive = "$scantype_in" ] || do_deinterlace=yes case "$video" in talkinghead) refbpp="${refbpp:-0.1}" x264tune=film ;; action) refbpp="${refbpp:-0.15}" x264tune=film ;; '') refbpp="${refbpp:-0.12}" ;; *) exit1 "Unknown video style \"$video\".";; esac for format in $(echo "$formats" | sed -e 's/,/ /g'); do case $format in theora|ogg) ogv=yes;; avc|h264|mp4) mp4=yes;; vp8|webm) webm=yes;; vp9) webm_vp9=yes;; *) exit1 "Unknown format \"$format\".";; esac done _width="${width:-$width_in}" _height="${height:-$height_in}" if [ -n "$_width" ] && [ -n "$_height" ]; then _pixels="$((_width*_height))" fi _frames="${framerate:-$framerate_in}" qmin_vp8=0 qmin_vp9=0 qmax_vp8=63 qmax_vp9=63 lag_vp8=16 lag_vp9=16 tokenparts_log2_vp8=0 tokenparts_log2_vp9=0 if [ -n "$_pixels" ] && [ $_pixels -ge $((1024*768)) ]; then qmin_vp8=11 qmin_vp9=11 qmax_vp8=51 qmax_vp9=51 tokenparts_log2_vp8=2 tokenparts_log2_vp9=2 if [ -n "$_frames" ] && [ $_frames -gt 40 ]; then lag_vp8=25 lag_vp9=25 profile_vp8=1 profile_vp9=1 fi fi # compute average bitrate from reference data and "power of .75" rule if [ -n "$_pixels" ] && [ -n "$_frames" ]; then bitrate_avc=$(perl -E '$refsize=640*360;' \ -E "say int( +(($_pixels/\$refsize)**0.75*\$refsize*$_frames*$refbpp) )") bitrate_theora=$(perl -E "say int( +($bitrate_avc*$factor_theora) )") #" bitrate_vp8=$(perl -E "say int( +($bitrate_avc*$factor_vp8) )") #" bitrate_vp9=$(perl -E "say int( +($bitrate_avc*$factor_vp9) )") #" fi # default per-codec-channel bitrates quality_vorbis=3 bitrate_opus=48 quality_lame=6 bitrate_lame=64 bitrate_aac=64 case "$audio" in music) opusapp=audio ;; speech) mono=yes quality_vorbis=2 bitrate_opus=32 quality_lame=7 bitrate_lame=48 opusapp=voip ;; silence) silence=yes ;; '') : ;; *) exit1 "Unknown audio style \"$audio\".";; esac which ffprobe > /dev/null && ffprobe=ffprobe || ffprobe=avprobe channels=$($ffprobe -v warning -show_streams "$infile_first" \ | perl -ne 's/channels=// and print $_' || echo -1) # adapt channel count and flags to reflect actual downmix/silence need if [ -n "$stereo" ]; then if [ $channels -gt 2 ]; then channels=2 else stereo= fi elif [ -n "$mono" ]; then if [ $channels -gt 1 ]; then channels=1 else mono= fi elif [ -n "$silence" ]; then if [ $channels -gt 0 ]; then channels=0 else silence= fi fi [ -z "$silence" ] || channels= if [ -n "$stereo$mono" ]; then # melt cannot downmix with (stereo?) filters applied (bug#763911) if [ -n "$audioprefilters$compress$loudness$limit$filters" ]; then if [ -z "${webm:+$use_oggenc}${webm_vp9:+$use_opusenc}" ]; then downmix_ffmpeg=yes else downmix_oggenc=yes downmix_oggenc=yes fi else downmix_melt=yes fi fi encoder_vp8=libvpx encoder_vp9=libvpx-vp9 encoder_avc=libx264 for vformat in theora vp8 vp9 avc; do [ -z "$no_bitrate" ] || eval "no_bitrate_$vformat=yes" eval "[ -n \"$no_bitrate\$no_bitrate_$vformat\" ] || do_bitrate_$vformat=yes" eval "[ -z \"$no_q\$no_q_$vformat\" ] || q_$vformat=" [ -z "$q" ] || eval "q_$vformat=$q" eval "[ -n \"\$q_$vformat\" ] || no_q_$vformat=yes" [ -z "$gpu" ] || eval "[ -n \"\$no_gpu_encoder_$vformat\" ] || encoder_$vformat=${vformat}_$gpu" [ avc = "$vformat" ] || eval "[ -n \"$use_vpxenc\$use_ffmpeg_$vformat\" ] || use_melt_$vformat=yes" [ -z "$multipass" ] || eval "[ -n \"\$no_multipass_$vformat\" ] || multipass_$vformat=yes" eval "[ -n \"\$multipass_$vformat\" ] || singlepass_$vformat=yes" done if [ -n "$use_ffmpeg_theora$use_ffmpeg_vp8$use_ffmpeg_vp9$use_vpxenc" ]; then [ -n "$use_oggenc" ] || use_ffmpeg_vorbis=yes [ -n "$use_opusenc" ] || use_ffmpeg_opus=yes [ -z "$use_oggenc$use_opusenc" ] || use_wav=yes [ -z "$use_ffmpeg_vorbis$use_ffmpeg_opus" ] || use_matroska=yes fi if [ -n "$use_ffmpeg_avc" ]; then use_matroska=yes else use_melt_avc=yes fi use_melt_any="${ogv:+$use_melt_theora}${webm:+$use_melt_vp8}${webm_vp9:+$use_melt_vp9}${mp4:+$use_melt_avc}" use_ffmpeg_any="${ogv:+$use_ffmpeg_theora}${webm:+$use_ffmpeg_vp8}${webm_vp9:+$use_ffmpeg_vp9}${mp4:+$use_ffmpeg_avc}" use_vpxenc_any="${webm:+$use_vpxenc}${webm_vp9:+$use_vpxenc}" use_opusenc_any="${webm:+$use_oggenc}${webm_vp9:+$use_opusenc}" use_oggenc_any="${ogv:+$use_oggenc}${webm:+$use_oggenc}${webm_vp9:+$use_opusenc}" [ -z "$use_ffmpeg_any$use_vpxenc_any" ] || use_any_nomelt_video=yes [ -z "$gpu" ] || echo >&2 "GPU platform enabled: $gpu" # gpu options _melt_gpu_init=${gpu:+ ${gpu}_device=$gpudevice} _ffmpeg_gpu_vaapi_init="-${gpu}_device $gpudevice" _ffmpeg_gpu_qsv_init="-init_hw_device qsv=hw:$gpudevice -filter_hw_device hw" _ffmpeg_gpu_init="${gpu_vaapi:+ ${_ffmpeg_gpu_vaapi_init}}${gpu_qsv:+ ${_ffmpeg_gpu_qsv_init}}" _ffmpeg_gpu_vaapi_filter="-vf 'format=nv12|$gpu,hwupload'" _ffmpeg_gpu_qsv_filter="-vf 'hwupload=extra_hw_frames=64,format=qsv'" _ffmpeg_gpu_filter="${gpu_vaapi:+ ${_ffmpeg_gpu_vaapi_filter}}${gpu_qsv:+ ${_ffmpeg_gpu_qsv_filter}}" # generic options which ffmpeg > /dev/null && ffmpeg=ffmpeg || ffmpeg=avconv _melt_infiles="${notsample:+$infiles}${sample:+$infile_first in=$((samplestart)) out=$((samplestart + samplelength))}" render() { engine=$1; shift chained= echo= unchained=yes case "$engine" in ffmpeg-chained) engine=ffmpeg; chained=yes;; vpxenc-chained) engine=vpxenc; chained=yes;; ffmpeg-chained-echo) engine=ffmpeg; chained=yes; echo=yes;; vpxenc-chained-echo) engine=vpxenc; chained=yes; echo=yes;; esac [ -z "$chained" ] || unchained= case "$engine" in melt) set -- -progress$_melt_gpu_init "$@" ;; ffmpeg) engine=$ffmpeg set -- -threads auto -y -hide_banner -v ${unchained:+info -stats}${chained:+warning}$_ffmpeg_gpu_init "$@" ;; vpxenc) set -- --quiet ${processors:+-t $((processors-1))} "$@" ;; oggenc) set -- "$@" ;; opusenc) set -- "$@" ;; *) exit1 "Unknown render engine \"$engine\".";; esac ${echo:+echo }"$engine" "$@" } # filter options _melt_video="progressive=1${framerate:+ frame_rate_num="$framerate_num" frame_rate_den="$framerate_den"}${size:+ s=${width:+$width}x${height:+$height}}${aspect:+ aspect=$aspect}" _ffmpeg_video="${no_gpu:+${do_deinterlace:+-filter:v yadif}${framerate:+ -r $framerate_num/$framerate_den}${size:+ -s ${width:+$width}x${height:+$height}}${aspect:+ -aspect $aspect}}" _melt_downmix="${downmix_melt:+ac=$channels}" _ffmpeg_downmix="${downmix_ffmpeg:+-ac $channels}" # FIXME: how to downmix to stereo? _oggenc_downmix="${downmix_oggenc:+${mono:+--downmix}}" _opusenc_downmix="${downmix_opusenc:+${stereo:+--downmix-stereo}${mono:+--downmix-mono}}" # limit (i.e. avoid peaks "clipping") _melt_postfilters_audio="${limit:+-filter ladspa.1077}" # format bitrate options _melt_bitrate_theora="${do_bitrate_theora:+ vb=$bitrate_theora}" _ffmpeg_bitrate_theora="${do_bitrate_theora:+ -b:v $bitrate_theora}" _melt_bitrate_vp8="${do_bitrate_vp8:+ vb=$bitrate_vp8 minrate=$((bitrate_vp8/20)) maxrate=$((bitrate_vp8*12))}" _ffmpeg_bitrate_vp8="${do_bitrate_vp8:+ -b:v $bitrate_vp8 -minrate $((bitrate_vp8/20)) -maxrate $((bitrate_vp8*12))}" _vpxenc_bitrate_vp8="${do_bitrate_vp8:+ --target-bitrate=$((bitrate_vp8/1000))${multipass_vp8:+ --minsection-pct=5 --maxsection-pct=1200}}" _melt_bitrate_vp9="${do_bitrate_vp9:+ vb=$bitrate_vp9 minrate=$((bitrate_vp9/20)) maxrate=$((bitrate_vp9*12))}" _ffmpeg_bitrate_vp9="${do_bitrate_vp9:+ -b:v $bitrate_vp9 -minrate $((bitrate_vp9/20)) -maxrate $((bitrate_vp9*12))}" _vpxenc_bitrate_vp9="${do_bitrate_vp9:+ --target-bitrate=$((bitrate_vp9/1000))${multipass_vp9:+ --minsection-pct=5 --maxsection-pct=1200}}" _melt_bitrate_avc="${do_bitrate_avc:+ maxrate=$bitrate_avc${no_gpu:+ bufsize=$((bitrate_avc*2))}}" _ffmpeg_bitrate_avc="${do_bitrate_avc:+ -maxrate $bitrate_avc${no_gpu:+ -bufsize $((bitrate_avc*2))}}" # format low-level options _melt_misc_vp8=" qmin=$qmin_vp8 qmax=$qmax_vp8 g=120 deadline=good cpu-used=$cpu_vp8${profile_vp8:+ vprofile=$profile_vp8} auto-alt-ref=1 lag-in-frames=$lag_vp8 arnr-maxframes=7 arnr-strength=5 arnr-type=centered" _ffmpeg_misc_vp8=" -qmin $qmin_vp8 -qmax $qmax_vp8 -g 120 -deadline good -cpu-used $cpu_vp8${profile_vp8:+ -profile:v $profile_vp8} -auto-alt-ref 1 -lag-in-frames $lag_vp8 -arnr-maxframes 7 -arnr-strength 5 -arnr-type centered" _vpxenc_misc_vp8=" --passes=${singlepass_vp8:+1}${multipass_vp8:+2} --min-q=$qmin_vp8 --max-q=$qmax_vp8 --kf-max-dist=120 --good --cpu-used=$cpu_vp8${profile_vp8:+ --profile=$profile_vp8} --token-parts=$tokenparts_log2_vp8 --auto-alt-ref=1 --lag-in-frames=$lag_vp8 --arnr-maxframes=7 --arnr-strength=5 --arnr-type=3" _melt_misc_vp9=" qmin=$qmin_vp9 qmax=$qmax_vp9 g=120 deadline=good cpu-used=$cpu_vp9${profile_vp9:+ vprofile=$profile_vp9} auto-alt-ref=1 lag-in-frames=$lag_vp9 arnr-maxframes=7 arnr-strength=5 arnr-type=centered" _ffmpeg_misc_vp9=" -qmin $qmin_vp9 -qmax $qmax_vp9 -g 120 -deadline good -cpu-used $cpu_vp9${profile_vp9:+ -profile:v $profile_vp9} -auto-alt-ref 1 -lag-in-frames $lag_vp9 -arnr-maxframes 7 -arnr-strength 5 -arnr-type centered" _vpxenc_misc_vp9=" --passes=${singlepass_vp9:+1}${multipass_vp9:+2} --min-q=$qmin_vp9 --max-q=$qmax_vp9 --kf-max-dist=120 --good --cpu-used=$cpu_vp9${profile_vp9:+ --profile=$profile_vp9} --tile-columns=$tokenparts_log2_vp9 --tile-rows=$tokenparts_log2_vp9 --auto-alt-ref=1 --lag-in-frames=$lag_vp9 --arnr-maxframes=7 --arnr-strength=5 --arnr-type=3" _melt_misc_avc="${no_gpu:+ movflags=+faststart}" _ffmpeg_misc_avc="${no_gpu:+ -movflags +faststart}" # format options # emulate VP8 Constant Quality using "loose" Constrained Quality _melt_theora="vcodec=libtheora$_melt_bitrate_theora${q_theora:+ $qkey_theora=$q_theora}" _ffmpeg_theora="-c:v libtheora$_ffmpeg_bitrate_theora${q_theora:+ -$qkey_theora:v $q_theora}" _melt_vp8="vcodec=$encoder_vp8${pre_vp8:+ vpreset=$pre_vp8}${preset_vp8:+ preset=$preset_vp8}$_melt_bitrate_vp8${q_vp8:+ ${no_bitrate_vp8:+vb=$((bitrate_vp8*10)) }$qkey_vp8=$q_vp8}$_melt_misc_vp8" _ffmpeg_vp8="-c:v $encoder_vp8${pre_vp8:+ -vpre $pre_vp8}${preset_vp8:+ preset $preset_vp8}$_ffmpeg_bitrate_vp8${q_vp8:+ ${no_bitrate_vp8:+-b:v $((bitrate_vp8*10)) }-$qkey_vp8 $q_vp8}$_ffmpeg_misc_vp8" _vpxenc_vp8="--codec=vp8$_vpxenc_bitrate_vp8 --end-usage=${no_q_vp8:+vbr}${q_vp8:+cq --cq-level=$q_vp8}$_vpxenc_misc_vp8" _melt_vp9="vcodec=$encoder_vp9${pre_vp9:+ vpreset=$pre_vp9}${preset_vp9:+ preset=$preset_vp9}$_melt_bitrate_vp9${q_vp9:+ ${no_bitrate_vp9:+vb=0 }$qkey_vp9=$q_vp9}$_melt_misc_vp9" _ffmpeg_vp9="-c:v $encoder_vp9${pre_vp9:+ -vpre $pre_vp9}${preset_vp9:+ -preset $preset_vp9}$_ffmpeg_bitrate_vp9${q_vp9:+ ${no_bitrate_vp9:+-b:v 0 }-$qkey_vp9 $q_vp9}$_ffmpeg_misc_vp9" _vpxenc_vp9="--codec=vp9$_vpxenc_bitrate_vp9 --end-usage=${no_q_vp9:+vbr}${q_vp9:+cq --cq-level=$q_vp9}$_vpxenc_misc_vp9" _melt_avc="vcodec=$encoder_avc${preset_avc:+ vpreset=$preset_avc}${profile_avc:+ vprofile=$profile_avc}${x264tune:+ tune=$x264tune}$_melt_bitrate_avc${q_avc:+ $qkey_avc=$q_avc} threads=0$_melt_misc_avc" _ffmpeg_avc="-c:v $encoder_avc${preset_avc:+ -preset $preset_avc}${profile_avc:+ -profile:v $profile_avc}${x264tune:+ -tune $x264tune}$_ffmpeg_bitrate_avc${q_avc:+ -$qkey_avc $q_avc}$_ffmpeg_misc_avc" _melt_pcm="$_melt_downmix acodec=pcm_s16le" _melt_vorbis="$_melt_downmix acodec=libvorbis aq=$quality_vorbis" _ffmpeg_vorbis="$_ffmpeg_downmix -c:a libvorbis -q:a $quality_vorbis" _oggenc_vorbis="$_oggenc_downmix -q $quality_vorbis" _melt_opus="$_melt_downmix acodec=libopus ab=$((channels*bitrate_opus))k${opusapp:+ application=$opusapp}" _ffmpeg_opus="$_ffmpeg_downmix -c:a libopus -b:a $((channels*bitrate_opus))k${opusapp:+ -application $opusapp}" _opusenc_opus="$_opusenc_downmix --bitrate $((channels*bitrate_opus))" _melt_mp3="$_melt_downmix acodec=libmp3lame${use_lame_abr:+ aq=$quality_lame}${use_lame_cbr:+ ab=$((channels*bitrate_lame))k}" _ffmpeg_mp3="$_ffmpeg_downmix -c:a libmp3lame${use_lame_abr:+ -q:a $quality_lame}${use_lame_cbr:+ -b:a $((channels*bitrate_lame))k}" _melt_aac="$_melt_downmix acodec=aac ab=$((channels*bitrate_aac))k" _ffmpeg_aac="$_ffmpeg_downmix -c:a aac -b:a $((channels*bitrate_aac))k" # container options _melt_stdout="-consumer avformat:pipe:1 f=yuv4mpegpipe $_melt_video pix_fmt=yuv420p an=1 audio_off=1" _ffmpeg_stdin="-f yuv4mpegpipe -i pipe:0$_ffmpeg_gpu_filter" _ffmpeg_rawvideo="-f rawvideo" _melt_wav="f=wav $_melt_pcm vn=1 video_off=1" _ffmpeg_wav_in="-f wav" _oggenc_wav_in= _opusenc_wav_in= _melt_matroska_pcm="f=matroska $_melt_downmix $_melt_pcm vn=1 video_off=1" _ffmpeg_matroska_pcm_in="-f matroska" _melt_ogv="f=ogg $_melt_video $_melt_theora ${silence:+an=1 audio_off=1}${channels:+$_melt_vorbis}" _ffmpeg_ogv="-f ogg $_ffmpeg_video $_ffmpeg_theora ${silence:+-an}${channels:+ $_ffmpeg_vorbis}" _ffmpeg_ogv_in="-f ogg" _ffmpeg_ogv_keepvideo="-f ogg $_ffmpeg_video -c:v copy ${silence:+-an}${channels:+$_ffmpeg_vorbis}" _ffmpeg_ogg_in="-f ogg" _ffmpeg_ogv_onlyvideo="-f ogg $_ffmpeg_video $_ffmpeg_theora -an" _ffmpeg_ogg_vorbis="-f ogg -vn $_ffmpeg_vorbis" _ffmpeg_ogg_opus="-f ogg -vn $_ffmpeg_opus" _melt_webm="f=webm $_melt_video $_melt_vp8 ${silence:+an=1 audio_off=1}${channels:+$_melt_vorbis}" _ffmpeg_webm="-f webm $_ffmpeg_video $_ffmpeg_vp8 ${silence:+-an}${channels:+ $_ffmpeg_vorbis}" _ffmpeg_webm_in="-f webm" _ffmpeg_webm_onlyvideo="-f webm $_ffmpeg_video $_ffmpeg_vp8 -an" _ffmpeg_webm_keepvideo="-f webm $_ffmpeg_video -c:v copy ${silence:+-an}${channels:+$_ffmpeg_vorbis}" _ffmpeg_webm_keepvideo_opus="-f webm $_ffmpeg_video -c:v copy ${silence:+-an}${channels:+$_ffmpeg_opus}" _melt_webm_vp9="f=webm $_melt_video $_melt_vp9 ${silence:+an=1 audio_off=1}${channels:+$_melt_opus}" _ffmpeg_webm_vp9="-f webm $_ffmpeg_video $_ffmpeg_vp9 ${silence:+-an}${channels:+$_ffmpeg_opus}" _ffmpeg_webm_vp9_onlyvideo="-f webm $_ffmpeg_video $_ffmpeg_vp9 -an" _melt_mp4="f=mp4 $_melt_video $_melt_avc ${silence:+an=1 audio_off=1}${channels:+${use_mp3:+$_melt_mp3}${use_aac:+$_melt_aac}}" _ffmpeg_mp4="-f mp4 $_ffmpeg_video $_ffmpeg_avc ${silence:+-an}${channels:+${use_mp3:+$_ffmpeg_mp3}${use_aac:+$_ffmpeg_aac}}" _ffmpeg_mp4_onlyvideo="-f mp4 $_ffmpeg_video $_ffmpeg_avc -an" _ffmpeg_mp4_keepvideo="-f mp4 $_ffmpeg_video -c:v copy ${silence:+-an}${channels:+$_ffmpeg_mp3}" _ffmpeg_mp4_keepvideo_aac="-f mp4 $_ffmpeg_video -c:v copy ${silence:+-an}${channels:+$_ffmpeg_aac}" _melt_img="f=image2 $_melt_video" _melt_loudness="$loudness_data" if [ -n "$loudness" ] && [ -z "$silence$_melt_loudness" ]; then echo >&2 "Analyzing audio dynamics..." render melt $_melt_infiles \ $audioprefilters -filter loudness $filters \ -consumer xml:$stem.xml video_off=1 all=1 _melt_loudness="$(perl -n \ -e 'm!<(property) name="results">([^<]+)! and print $2' \ $stem.xml)" echo >&2 "Loudness data: $_melt_loudness" fi if [ -n "${ogv:+$multipass_theora}${webm:+$multipass_vp8}${webm_vp9:+$multipass_vp9}${mp4:+$multipass_avc}" ]; then echo >&2 "Analyzing video complexity..." render melt $_melt_infiles $filters \ ${ogv:+${use_melt_theora:+-consumer avformat:$stem.ogv $_melt_ogv \ pass=1 passlogfile=${stem}_theora}} \ ${webm:+${use_melt_vp8:+-consumer avformat:$stem.webm $_melt_webm \ pass=1 passlogfile=${stem}_vp8}} \ ${webm_vp9:+${use_melt_vp9:+-consumer avformat:${stem}_vp9.webm $_melt_webm_vp9 \ pass=1 passlogfile=${stem}_vp9}} \ ${mp4:+${use_melt_avc:+-consumer avformat:$stem.mp4 $_melt_mp4 \ pass=1 passlogfile=${stem}_avc}} \ ${use_any_nomelt_video:+$_melt_stdout} \ | pee \ ${use_ffmpeg_any:+"$(render ffmpeg-chained-echo $_ffmpeg_stdin \ ${ogv:+${use_ffmpeg_theora:+$_ffmpeg_rawvideo $_ffmpeg_ogv -an \ -pass 1 -passlogfile ${stem}_theora /dev/null}} \ ${webm:+${use_ffmpeg_vp8:+$_ffmpeg_rawvideo $_ffmpeg_vp8 -an \ -pass 1 -passlogfile ${stem}_vp8 /dev/null}} \ ${webm_vp9:+${use_ffmpeg_vp9:+$_ffmpeg_rawvideo $_ffmpeg_vp9 -an \ -pass 1 -passlogfile ${stem}_vp9 /dev/null}} \ ${mp4:+${use_ffmpeg_avc:+$_ffmpeg_rawvideo $_ffmpeg_avc -an \ -pass 1 -passlogfile ${stem}_avc /dev/null}})"} \ ${webm:+${use_vpxenc:+"$(render vpxenc-chained-echo - $_vpxenc_vp8 \ --pass=1 --fpf=${stem}_vp8.log -o /dev/null)"}} \ ${webm_vp9:+${use_vpxenc:+"$(render vpxenc-chained-echo - $_vpxenc_vp9 \ --pass=1 --fpf=${stem}_vp9.log -o /dev/null)"}} fi echo >&2 "Encoding video${channels:+ and audio}..." render melt $_melt_infiles \ ${channels:+$audioprefilters \ ${_melt_loudness:+-filter loudness program=$lufs \ results="$_melt_loudness"}} \ $filters${channels:+ $_melt_postfilters_audio} \ ${ogv:+${use_melt_theora:+-consumer avformat:$stem.ogv $_melt_ogv \ ${multipass_theora:+pass=2 passlogfile=${stem}_theora}}} \ ${webm:+${use_melt_vp8:+-consumer avformat:$stem.webm $_melt_webm \ ${multipass_vp8:+pass=2 passlogfile=${stem}_vp8}}} \ ${webm_vp9:+${use_melt_vp9:+-consumer avformat:${stem}_vp9.webm $_melt_webm_vp9 \ ${multipass_vp9:+pass=2 passlogfile=${stem}_vp9}}} \ ${mp4:+${use_melt_avc:+-consumer avformat:$stem.mp4 $_melt_mp4 ${multipass_avc:+pass=2 passlogfile=${stem}_avc}}} \ ${channels:+${use_wav:+-consumer avformat:$stem.wav \ $_melt_wav}} \ ${channels:+${use_matroska:+-consumer avformat:$stem.mkv \ $_melt_matroska_pcm}} \ ${use_any_nomelt_video:+$_melt_stdout} \ | pee \ ${use_ffmpeg_any:+"$(render ffmpeg-chained-echo $_ffmpeg_stdin \ ${ogv:+${use_ffmpeg_theora:+$_ffmpeg_ogv_onlyvideo \ ${multipass_theora:+-pass 2 -passlogfile ${stem}_theora} \ ${stem}${channels:+_silent}.ogv}} \ ${webm:+${use_ffmpeg_vp8:+$_ffmpeg_webm_onlyvideo \ ${multipass_vp8:+-pass 2 -passlogfile ${stem}_vp8} \ ${stem}${channels:+_silent}.webm}} \ ${webm_vp9:+${use_ffmpeg_vp9:+$_ffmpeg_webm_vp9_onlyvideo \ ${multipass_vp9:+-pass 2 -passlogfile ${stem}_vp9} \ ${stem}_vp9${channels:+_silent}.webm}} \ ${mp4:+${use_ffmpeg_avc:+$_ffmpeg_mp4_onlyvideo \ ${multipass_avc:+-pass 2 -passlogfile ${stem}_avc} \ ${stem}${channels:+_silent}.mp4}})"} \ ${webm:+${use_vpxenc:+"$(render vpxenc-chained-echo - $_vpxenc_vp8 \ ${multipass_vp8:+--pass=2 --fpf=${stem}_vp8.log} \ -o ${stem}${channels:+_silent}.webm)"}} \ ${webm_vp9:+${use_vpxenc:+"$(render vpxenc-chained-echo - $_vpxenc_vp9 \ ${multipass_vp9:+--pass=2 --fpf=${stem}_vp9.log} \ -o ${stem}_vp9${channels:+_silent}.webm)"}} if [ -n "${ogv:+$use_oggenc$use_ffmpeg_theora}${webm:+$use_vpxenc$use_oggenc}" ] && [ -n "$channels" ]; then echo >&2 "Encoding Vorbis audio and muxing with video..." # TODO: Encode once, muxing to containers as needed if [ -n "$use_oggenc" ]; then render oggenc $_oggenc_wav_in $_oggenc_vorbis -o - $stem.wav \ | render ffmpeg-chained \ ${ogv:+${use_oggenc:+$_ffmpeg_ogv_in -i ${stem}_silent.ogv \ $_ffmpeg_ogg_in -i pipe:0 \ $_ffmpeg_ogv_keepvideo $stem.ogv}} \ ${webm:+${use_oggenc:+$_ffmpeg_webm_in -i ${stem}_silent.webm \ $_ffmpeg_ogg_in -i pipe:0 \ $_ffmpeg_webm_keepvideo $stem.webm}} fi if [ -n "${ogv:+$use_ffmpeg_theora}" ]; then render ffmpeg $_ffmpeg_ogv_in -i ${stem}_silent.ogv \ $_ffmpeg_matroska_wav_in -i $stem.mkv \ $_ffmpeg_ogv_keepvideo ${stem}.ogv fi if [ -n "${webm:+$use_vpxenc}" ]; then render ffmpeg $_ffmpeg_webm_in -i ${stem}_silent.webm \ $_ffmpeg_matroska_wav_in -i $stem.mkv \ $_ffmpeg_webm_keepvideo ${stem}.webm fi fi if [ -n "${webm_vp9:+$use_vpxenc$use_opusenc}" ] && [ -n "$channels" ]; then echo >&2 "Encoding Opus audio and muxing with VP9 video..." if [ -n "$use_opusenc" ]; then render opusenc $_opusenc_wav_in $_opusenc_opus -o - $stem.wav \ | render ffmpeg-chained \ $_ffmpeg_webm_in -i ${stem}_vp9_silent.webm \ $_ffmpeg_ogg_in -i pipe:0 \ $_ffmpeg_webm_keepvideo_opus ${stem}_vp9.webm else render ffmpeg $_ffmpeg_webm_in -i ${stem}_vp9_silent.webm \ $_ffmpeg_matroska_wav_in -i $stem.mkv \ $_ffmpeg_webm_keepvideo_opus ${stem}_vp9.webm fi fi if [ -n "${mp4:+$use_ffmpeg_avc}" ] && [ -n "$channels" ]; then if [ -n "$use_mp3" ]; then echo "Encoding MP3 audio and muxing with H.264/AVC video..." render ffmpeg $_ffmpeg_mp4_in -i ${stem}_silent.mp4 \ $_ffmpeg_matroska_wav_in -i $stem.mkv \ $_ffmpeg_mp4_keepvideo ${stem}.mp4 else echo "Encoding AAC audio and muxing with H.264/AVC video..." render ffmpeg $_ffmpeg_mp4_in -i ${stem}_silent.mp4 \ $_ffmpeg_matroska_wav_in -i $stem.mkv \ $_ffmpeg_mp4_keepvideo_aac ${stem}.mp4 fi fi # cleanup encoding cruft rm -f $stem.xml $stem.wav $stem.mkv ${stem}_*.log ${stem}_*silent.webm # JPEG preview render melt $infile_first in=$stillframe out=$stillframe \ -group $filters \ -consumer avformat:$stem.jpg $_melt_img __width="${_width:+ width=\"$_width\"}" __height="${_height:+ height=\"$_height\"}" # Flash object needs extra space for controllers __heightplus=${_height:+ height=\"$((_height+4))\"} _source_ogv="" _source_webm="" _source_webm_vp9="" # TODO: add type, with h.264 level inspected after rendering # _source_mp4="" [ -z "$flashplayer" ] || flash=yes [ -n "$mp4" ] || [ -z "$flash" ] || error1 "Cannot enable flash when mp4 format is disabled." _object_flash="" _param_name="" _param_flashvars="" __ogvfile=${ogv:+open format Ogg/Thera} __webmfile=${webm:+open format WebM/VP8} __webm_vp9_file=${vp9:+open format WebM/VP9} __mp4file=${mp4:+closed format MPEG-4/AVC} cat >"$stem.html" < ${mp4:+$_source_mp4 }${webm_vp9:+$_source_webm_vp9 }${webm:+$_source_webm }${ogv:+$_source_ogv }${flash:+$_object_flash $_param_name $_param_flashvars }$title ${flash:+ }

Download Video:

    ${webm_vp9:+
  • $__webm_vp9_file }${webm:+
  • $__webmfile }${ogv:+
  • $__ogvfile }${mp4:+
  • $__mp4file }

EOF