#!/bin/sh # Copyright © 2010-2014 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: libav-tools melt mediainfo # # TODO: offer to skip rendering again if an output file exist already # TODO: support --width and --height (resolving the other part from input/forced aspect ratio) set -e PRG=$(basename "$0") showhelp() { cat < EOF } exit1() { response="${1:+Error: }${1:-Internal error!}" echo >&2 "$response" exit 1 } # defaults formats=webm,vp9,mp4 samplestart=0 samplelength=150 # parse cmdline options TEMP="`getopt -s sh -o hp:s:a:r:b:t: -l help,profile:,size:,aspect:,rate:,video:,refbpp:,bitrate:,formats:,audio:,audioprefilter:,loudness,loudness-results:,filter:,stem:,title:,sample,sample-start:,sample-length: -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;; -b|--bitrate) bitrate="$2"; bitrate_fixed=yes; 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-results) loudness=yes; loudness_results="$2"; shift 2;; --filter) filters="${filters:+$filters }-filter $2"; shift 2;; --stem) stem="$2"; shift 2;; -t|--title) title="$2"; shift 2;; --sample) sample=yes; shift;; --sample-start) sample=yes; samplestart="$2"; shift 2;; --sample-length) sample=yes; samplelength="$2"; shift 2;; --) shift; break;; *) exit1 "Internal error resolving options.";; esac done # Resolve if melt is version 0.9.2 or newer # TODO: drop when melt 0.9.2 is stable melt_recent=$(melt -query filter=loudness | grep -qi R128 && echo yes) while [ $# -gt 0 ] ; do case "$1" in *=*) _melt_in="${_melt_in:+$_melt_in }$1"; shift;; *) break;; esac done while [ $# -gt 0 ] ; do case "$1" in *=*) _melt_out="${_melt_out:+$_melt_out }$1"; shift;; *) infiles="${infiles:+$infiles }$1"; shift;; esac done if [ -z "$infiles" ]; then showhelp exit1 "Too few parameters!" fi # input filename (mandatory) infile_first=$(perl -e 'print pop @ARGV' $infiles) [ -e "$infile_first" ] || exit1 "Input file missing!" # resolve stem and title (if not explicitly set) stem=${stem:-$(basename "$infile_first" | perl -pe 's/\.[^.]*//')} title=${title:-$stem} case "$profile" in *@*) while read s r foo; do size="${size:-$s}" framerate="${framerate:-$r}" done << EOF $(echo "$profile" | perl -F@ -anE 'say join " ", @F') EOF ;; *p*) while read s r foo; do size="${size:-${s}p}" framerate="${framerate:-$r}" done << EOF $(echo "$profile" | perl -Fp -anE 'say join " ", @F') EOF ;; *) size="$profile" ;; esac 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 d n foo; do framerate_den="${framerate_den:-$d}" framerate_num="${framerate_num:-$n}" done << EOF $(echo "$framerate" | perl -Fx -anE 'say join " ", @F') EOF ;; ?*) framerate_den=1 framerate_num="$framerate" ;; esac while read w h r foo; do width_in="${width_in:-$w}" height_in="${height_in:-$h}" framerate_in="${framerate_in:-$r}" done << EOF $(mediainfo --Inform="Video;%Width% %Height% %FrameRate%" "$infile_first") EOF 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 ogg) ogg=yes;; mp4) mp4=yes;; webm) webm=yes;; vp9) 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}" webmpreset=360p if [ -n "$_pixels" ] && [ $_pixels -ge $((1024*768)) ]; then webmpreset=720p if [ -n "$_frames" ] && [ $_frames -gt 40 ]; then webmpreset=720p50_60 fi fi # compute average bitrate from reference data and "power of .75" rule bitrate_vp9="$bitrate" if [ -z "$bitrate" ] && [ -n "$_pixels" ] && [ -n "$_frames" ]; then bitrate=$(perl -E '$refsize=640*360;' \ -E "say int( +(($_pixels/\$refsize)**0.75*\$refsize*$_frames*$refbpp) )") bitrate_vp9=$(perl -E "say int( $bitrate/2 )") #" fi # default per-codec-channel bitrates bitrate_vorbis=64 bitrate_opus=64 bitrate_aac=96 channels=-1 maxchannels=2 case "$audio" in music) opusapp=audio ;; hqspeech) channels=1 bitrate_vorbis=48 bitrate_opus=32 bitrate_aac=64 opusapp=voip ;; speech) channels=1 bitrate_vorbis=48 bitrate_opus=32 bitrate_aac=64 compress=yes [ -z "$melt_recent" ] || _melt_loudness="$loudness_results" limit=yes opusapp=voip ;; silence) channels=0 ;; '') : ;; *) exit1 "Unknown audio style \"$audio\".";; esac [ $channels -ge 0 ] || channels=$(avprobe -v warning -show_streams "$infile_first" | perl -ne 's/channels=// and print $_' || echo -1) [ $channels -le $maxchannels ] || channels=$maxchannels [ $channels -gt 0 ] || channels= # TODO: Check and fail if all needed tools are not available # TODO: When verified beneficial, add option real_time=-2 melt="melt -progress" _melt_in="${_melt_in:+$_melt_in }${sample:+in=${samplestart:-0} out=$((${samplestart:-0} + samplelength))}" _melt_video="progressive=1${framerate:+ frame_rate_den="$framerate_den" frame_rate_num="$framerate_num"}${size:+ s=${width:+$width}x${height:+$height}}${aspect:+ aspect=$aspect}" _melt_ogg="$_melt_video f=ogg vcodec=libtheora${bitrate:+ vb=$bitrate}${bitrate_fixed:- qscale=5}" _melt_h264="$_melt_video f=mp4 vcodec=libx264 vpre=medium vprofile=baseline${x264tune:+ tune=$x264tune}${bitrate_fixed:+ vb=$bitrate} threads=0 movflags=+faststart${bitrate_fixed:- crf=23}" _melt_webm="$_melt_video f=webm vcodec=libvpx vpre=libvpx-$webmpreset${bitrate:+ vb=$bitrate}${bitrate_fixed:+ minrate=$bitrate maxrate=$bitrate}{bitrate_fixed:- crf=10} cpu-used=3" _melt_img="$_melt_video f=image2" # CRF ignored with libvpx 1.3 _melt_vp9="$_melt_video f=webm vcodec=libvpx-vp9 vpre=libvpx-$webmpreset${bitrate_vp9:+ vb=$bitrate_vp9}${bitrate_fixed:+ minrate=$bitrate_vp9 maxrate=$bitrate_vp9}${bitrate_fixed:- crf=10} cpu-used=5" _melt_audio="${channels:+ac=$channels}" # limit (i.e. avoid peaks "clipping") _melt_postfilters_audio="${limit:+-filter ladspa.1077}" _melt_vorbis="$_melt_audio acodec=libvorbis${channels:+ ab=$(($channels*$bitrate_vorbis))k}" _melt_opus="$_melt_audio acodec=libopus${channels:+ ab=$(($channels*$bitrate_opus))k}${bitrate_fixed:+ vbr=constrained}${opusapp:+ application=$opusapp}" _melt_aac="$_melt_audio acodec=aac${channels:+ ab=$(($channels*$bitrate_aac))k}" avconv="avconv -threads auto -y -v warning" _avconv_vp8="-c:v libvpx -pre:v libvpx-$webmpreset${bitrate:+ -b:v $bitrate}${bitrate_fixed:+ -minrate $bitrate -maxrate $bitrate}${bitrate_fixed:- -crf 10} -cpu-used 3" _avconv_vp9="-c:v libvpx-vp9 -pre:v libvpx-$webmpreset${bitrate_vp9:+ -b:v $bitrate_vp9}${bitrate_fixed:+ -minrate $bitrate_vp9 -maxrate $bitrate_vp9}${bitrate_fixed:- -crf 10} -cpu-used 5" # resolve EBU R128 audio normalizing # TODO: normalize each infile separately when xml fed as infile keeps sync if [ -n "$loudness" ] && [ -z "$_melt_loudness" ]; then $melt -group $_melt_in $infiles -group $audioprefilters -filter loudness -consumer xml:$stem.xml $_melt_audio video_off=1 all=1 _melt_loudness="$(perl -ne 'm!([^<]+)! and print $1' $stem.xml)" fi if [ -n "$mp4" ] && [ -n "$bitrate_fixed" ]; then $melt -group $_melt_in $infiles -group $filters \ -consumer avformat:/dev/null pass=1 fastfirstpass=1 an=1 audio_off=1 $_melt_h264 $_melt_out fi if [ -n "$webm$vp9" ]; then $melt -group $_melt_in $infiles -group $filters \ -consumer avformat:pipe:1 $_melt_video f=yuv4mpegpipe pix_fmt=yuv420p \ | $avconv -i pipe:0 \ ${webm:+-f rawvideo $_avconv_vp8 -an -pass 1 -passlogfile ${stem}_vp8 /dev/null} \ ${vp9:+-f rawvideo $_avconv_vp9 -an -pass 1 -passlogfile ${stem}_vp9 /dev/null} [ -z "$webm" ] || mv -f ${stem}_vp8-*.log ${stem}_vp8_2pass.log [ -z "$vp9" ] || mv -f ${stem}_vp9-*.log ${stem}_vp9_2pass.log fi $melt -group $_melt_in $infiles \ -group ${channels:+$audioprefilters${_melt_loudness:+ -filter loudness results="$_melt_loudness"}} \ $filters${channels:+ $_melt_postfilters_audio} \ ${ogg:+-consumer avformat:$stem.ogv $_melt_ogg $_melt_vorbis $_melt_out} \ ${mp4:+-consumer avformat:$stem.mp4${bitrate_fixed:+ pass=2} $_melt_h264 $_melt_aac $_melt_out} \ ${webm:+-consumer avformat:$stem.webm $_melt_webm$ pass=2 passlogfile=${stem}_vp8 $_melt_vorbis $_melt_out} \ ${vp9:+-consumer avformat:${stem}_vp9.webm pass=2 passlogfile=${stem}_vp9 $_melt_vp9 $_melt_opus $_melt_out} if [ -n "$mp4" ] && [ -z "$melt_recent" ]; then mv "$stem.mp4" "$stem.mp4"~ qt-faststart "$stem.mp4"~ "$stem.mp4" [ -f "$stem.mp4" ] && rm "$stem.mp4"~ || mv -f "$stem.mp4"~ "$stem.mp4" fi # cleanup audio normalize hinting rm -f $stem.xml ## JPEG preview $melt -group in=0 out=0 $infiles \ -group $filters \ -consumer avformat:$stem.jpg $_melt_img $_melt_out __width="${_width:+ width=\"$_width\"}" __height="${_height:+ height=\"$_height\"}" # Flash object needs extra space for controllers __heightplus=${_height:+ height=\"$(($_height+4))\"} _source_ogg="" _source_webm="" _source_vp9="" _source_mp4="" # TODO: resolve flash player to use [ -z "$flashplayer" ] || flash=yes [ -n "$mp4" ] || [ -z "$flash" ] || error1 "Cannot enable flash when mp4 format is disabled." _object_flash="" _param_name="" _param_flashvars="" __oggfile=${ogg:+open format Ogg} __webmfile=${webm:+open format WebM (VP8)} __vp9file=${vp9:+open format WebM (VP9/Opus)} __mp4file=${mp4:+closed format MPEG-4} cat >"$stem.html" < ${mp4:+$_source_mp4 }${vp9:+$_source_vp9 }${webm:+$_source_webm }${ogg:+$_source_ogg }${flash:+$_object_flash $_param_name $_param_flashvars }$title ${flash:+ }

Download Video:

    ${vp9:+
  • $__vp9file }${webm:+
  • $__webmfile }${ogg:+
  • $__oggfile }${mp4:+
  • $__mp4file }

EOF