#!/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 ffmpegthumbnailer # # Origins: # http://diveintohtml5.org/video.html # http://camendesign.com/code/video_for_everybody # http://www.streaminglearningcenter.com/articles/so-you-want-to-get-to-know-h264.html # http://www.penguinproducer.com/2012/01/ladspa-noise-removal/ # # Possible flashplayers: # http://www.internetmarketingnotes.com/2010/07/free-embeddable-flash-video-flv-players-for-commercial-use/ # # 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) # TODO: support --formats (comma-separated, to allow e.g. excluding webm even if supported) # TODO: add --speech option, using mono, lower audio rate, and (when solved how) speex set -e PRG=$(basename "$0") showhelp() { cat < EOF } exit1() { response="${1:+Error: }${1:-Internal error!}" echo >&2 "$response" exit 1 } # defaults rate=25 h264profile=baseline # parse cmdline options TEMP="`getopt -s sh -o hp:s:a:r:b:t: -l help,profile:,size:,aspect:,rate:,bitrate:,h264profile:,h264preset:,webmpreset:,audio:,stem:,title:,filter:,sample:: -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) rate="$2"; shift 2;; -b|--bitrate) bitrate="$2"; shift 2;; --h264profile) h264profile="$2"; shift 2;; --h264preset) h264preset="$2"; shift 2;; --webmpreset) webmpreset="$2"; shift 2;; --audio) audio="$2"; shift 2;; --stem) stem="$2"; shift 2;; -t|--title) title="$2"; shift 2;; --filter) filters="${filters:+$filters }-filter $2"; shift 2 while [ $# -gt 0 ] ; do case "$1" in *=*) filters="${filters:+$filters }$1"; shift;; *) break;; esac done ;; --sample) sample="in=${2:-0} out=$((${2:-0} + 150))"; 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 -i R128) 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 320x240|qvga) _melt_in="-profile=quarter_pal${_melt_in:+ $_melt_in}" _melt_video="${size:-width=320 height=240}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 480x360|hvga) _melt_in="-profile=quarter_pal${_melt_in:+ $_melt_in}" _melt_video="${size:-width=480 height=360}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 640x480|vga) _melt_in="-profile=quarter_pal${_melt_in:+ $_melt_in}" _melt_video="${size:-width=640 height=480}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 800x600|svga) _melt_in="-profile=quarter_pal${_melt_in:+ $_melt_in}" _melt_video="${size:-width=800 height=600}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 1024x768|xga) _melt_in="-profile=quarter_pal${_melt_in:+ $_melt_in}" _melt_video="${size:-width=1024 height=768}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-720p}" ;; 240p|432x240|wqvga) _melt_in="-profile=atsc_720p_25${_melt_in:+ $_melt_in}" _melt_video="${size:-width=432 height=240}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 360p|640x360|nhd) _melt_in="-profile=atsc_720p_25${_melt_in:+ $_melt_in}" _melt_video="${size:-width=640 height=360}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 480p|848x480|wvga) _melt_in="-profile=atsc_720p_25${_melt_in:+ $_melt_in}" _melt_video="${size:-width=848 height=480}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 576p|1024x576|wsvga) _melt_in="-profile=atsc_720p_25${_melt_in:+ $_melt_in}" _melt_video="${size:-width=1024 height=576}${_melt_video:+ $_melt_video}" webmpreset="${webmpreset:-360p}" ;; 720p|1280x720|wxga|hd) _melt_in="-profile=atsc_720p_25${_melt_in:+ $_melt_in}" webmpreset="${webmpreset:-720p}" ;; '') _melt_video="progressive=1 frame_rate_den=1${_melt_video:+ $_melt_video}" [ "25" != "$rate" ] || _melt_video="{_melt_video:+$_melt_video }frame_rate_num=$rate" ;; *) exit1 "Unknown profile \"$profile\" - please specify size and aspect directly.";; esac [ "25" = "$rate" ] || _melt_video="{_melt_video:+$_melt_video }frame_rate_num=$rate" case "$h264profile" in baseline|main) _melt_h264="properties=x264-medium-$h264profile ${h264preset:+-vpre=libx264-$h264preset}" ;; high) _melt_h264="properties=x264-medium ${h264preset:+-vpre=libx264-$h264preset}" ;; *) exit1 "Unknown MPEG-4 AVC profile \"$h264profile\".";; esac # default per-codec-channel bitrates bitrate_vorbis=64 bitrate_aac=96 case "$audio" in music) channels=2 # limit (i.e. avoid peaks "clipping") _melt_audio_filters="-filter ladspa.1077" ;; speech) channels=1 bitrate_vorbis=48 bitrate_aac=64 # expand and compress (i.e. lower volume on silent and loud passages) _melt_audio_filters_early="-filter ladspa.1075 -filter ladspa.1073" # limit (i.e. avoid peaks "clipping") _melt_audio_filters="-filter ladspa.1077" ;; silence) channels=0 ;; '') channels=$(avprobe -v warning -show_streams "$infile_first" | perl -ne 's/channels=// and print $_') ;; *) exit1 "Unknown audio style \"$audio\".";; esac [ $channels -le 2 ] || channels=2 [ $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" _melt_video="$_melt_video${bitrate:+ vb=${bitrate}}${size:+ s=$size}${aspect:+ aspect=$aspect}" _melt_ogg="$_melt_video f=ogg vcodec=libtheora${bitrate:- qscale=5}" _melt_h264="$_melt_video $_melt_h264${bitrate:- qscale=5}" _melt_webm="$_melt_video${webmpreset:+- vpre=libvpx-$webmpreset}" _melt_audio="${channels:+ac=$channels}" _melt_vorbis="$_melt_audio acodec=libvorbis ab=$(($channels*$bitrate_vorbis))k" _melt_aac="$_melt_audio acodec=aac ab=$(($channels*$bitrate_aac))k" # resolve EBU R128 audio normalizing # TODO: normalize each infile separately when xml fed as infile keeps sync if [ -n "$melt_recent" ] && [ -n "$channels" ]; then $melt -group $_melt_in $infiles -group $_melt_audio_filters_early -filter loudness -consumer xml:$stem.xml $_melt_audio video_off=1 all=1 _melt_loudness="$(perl -ne 'm!([^<]+)! and print $1' $stem.xml)" rm $stem.xml fi ## Theora/Vorbis/Ogg $melt -group $_melt_in $infiles -group ${channels:+$_melt_audio_filters_early${_melt_loudness:+ -filter loudness results="$_melt_loudness"} $_melt_audio_filters }$filters -consumer avformat:"$stem.ogv" $_melt_ogg $_melt_vorbis $_melt_out ## H.264/AAC/MP4 [ -z "$bitrate" ] || $melt -group $_melt_in $infiles -group $filters -consumer avformat:/dev/null properties=x264-medium-pass1 $_melt_h264 $_melt_out $melt -group $_melt_in $infiles -group ${channels:+$_melt_audio_filters_early${_melt_loudness:+ -filter loudness results="$_melt_loudness"} $_melt_audio_filters }$filters -consumer avformat:"$stem.mp4" ${bitrate:+pass=2} $_melt_h264 $_melt_aac $_melt_out if [ -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 ## VP8/Vorbis/WebM # TODO: use two-pass when supported by melt $melt -group $_melt_in $infiles -group ${channels:+$_melt_audio_filters_early${_melt_loudness:+ -filter loudness results="$_melt_loudness"} $_melt_audio_filters }$filters -consumer avformat:"$stem.webm" properties=webm $_melt_webm $_melt_vorbis $_melt_out ## JPEG preview ffmpegthumbnailer -s0 -i "$stem.mp4" -o "$stem.jpg" # resolve width and height from preview image size=$(jpeginfo "$stem.jpg" | perl -ane 'print "$F[1]x$F[3]"') width=$(echo "$size" | perl -Fx -ane 'print $F[0]') height=$(echo "$size" | perl -Fx -ane 'print $F[1]') heightplus=${height:+$(($height+4))} # TODO: resolve flash player to use [ -z "$flashplayer" ] || flash=yes cat >"$stem.html" <

Download Video: open format WebM, open format Ogg, closed Format MPEG-4.

EOF