#!/bin/sh ## TODO: handle srt encoding? usage () { cat <&2 Usage: ${0##*/} [OPTIONS] FILES|FOLDERS Transcode FILES or files found in FOLDERS to x264 and ogg vorbis in a MKV container. Black stripes are *not* cropped by default since it may be sometimes inaccurate. A time stamp is appended to the output filenames. CRF encoding is performed by default. It targets a desired quality. CRF 20 is usually a good factor, 18 is higher quality, 22 is lower. The 18-22 range is reasonable. The quality loss is usually noticeable above 22 and the size increase is significant below 18 for a barely noticeable quality change. 2-pass encoding can achieve best compression, if the given bitrate is appropriate. A sample in CRF encoding can give a good bitrate estimate for the desired quality. Bitrates for 1080p movies are found within the 1000k-10000k range. You can get a list of supported codecs with $ ffmpeg -codecs User options are read from the TC_VIDEO_OPT variable. This can be useful if you often use the same options. Options: -a CODEC: Audio codec supported by ffmpeg. (Default: libvorbis). -b: Default bitrate for audio stream with unidentifiable bitrate. If 0, copy stream (default). -c: Copy streams (no reencoding). -C: Enable auto-crop (needs video reencoding). -d BR: Perform a 2-pass video encoding at bitrate BR and set '-tune film'. -f: Remove source when done. -h: Display this help. -o OPT: Additional options. -p: Preview changes, do not encode. -P: Generate two sequence of thumbnails, one cropped, the other not. Enable auto-crop, do not encode. -q QUAL: x264 CRF quality (default 20). -s: Sample of 5 minutes. -S MIN: Sample of MIN minutes. -t: Remove all "title" metadata. Examples: * Get a preview of changes. ${0##*/} -p input.video * Proceed with default options over folders and files. ${0##*/} input-folder input.video * Preview black stripes cropping. ${0##*/} -P input.video * Process with default options and remove black stripes. ${0##*/} -C input.video * Exchange streams 1 and 2 by first removing them, then adding them in the desired order. ${0##*/} -o '-map -0:1 -map -0:2 -map 0:2 -map 0:1' input.video * Change audio stream 1 title and remove audio stream 2 title: ${0##*/} -o '-metadata:s:a:0 title="FR: OGG Stereo" -metadata:s:a:1 title=' input.video See , 'ffmpeg -h encoder=libx264' and 'x264 --fullhelp'. EOF } EXT="mkv" AUDIO_CODEC="libvorbis" VIDEO_PARAM="-c:v libx264 -preset slower -crf 20" AUDIO_PARAM="" VIDEO_FILTER="" AUDIO_DEFAULT_RATE=0 OVERWRITE="-n" SAMPLE="" OPT_OVERWRITE=false OPT_REMOVE_TITLE=false OPT_CROP=false OPT_CROPPREVIEW=false OPT_PREVIEW=false OPT_COPY=false OPT_2PASS=false while getopts ":a:b:cCd:fho:pPq:sS:t" opt; do case $opt in a) AUDIO_CODEC=$OPTARG ;; b) AUDIO_DEFAULT_RATE=$OPTARG ;; c) VIDEO_PARAM="-c:v copy" AUDIO_PARAM="-c:a copy" OPT_COPY=true;; C) OPT_CROP=true;; d) VIDEO_PARAM="-c:v libx264 -preset slower -b:v ${OPTARG}k" OPT_2PASS=true;; f) OVERWRITE="-y" OPT_OVERWRITE=true;; h) usage exit 1 ;; o) TC_VIDEO_OPT="$OPTARG" ;; p) OPT_PREVIEW=true ;; P) OPT_CROP=true OPT_PREVIEW=true OPT_CROPPREVIEW=true ;; q) VIDEO_PARAM="-c:v libx264 -preset slower -crf $OPTARG";; s) SAMPLE="-ss 60 -t 300" ;; S) SAMPLE="-ss 60 -t $((60*OPTARG))" ;; t) OPT_REMOVE_TITLE=true ;; \?) usage exit 1 ;; esac done shift $((OPTIND - 1)) if [ $# -eq 0 ]; then usage exit fi if ! command -v ffmpeg >/dev/null; then echo "ffmpeg required." exit fi highfreq () { awk 'BEGIN{max=0} /crop=/ {t[$NF]++; if (t[$NF]>max) {max=t[$NF]; val=$NF}} END{print val}' } ## Usage: cropvalue FILE STEP ## Return the crop values as ffmpeg output them. cropvalue () { local step=$2 for i in $(seq "$step" "$step" $((5*step))); do ffmpeg -nostdin -ss "$i" -i "$1" -t 10 -vf "cropdetect=24:2:0" -f null - 2>&1 done | highfreq } ## Return the audio encoding parameters. For instance ## -c:2 libvorbis -b:2 384k -c:5 copy ## If input codec is $AUDIO_CODEC, we copy. If some stream bitrates are missing ## or 0, we encode it to a default value. If default value is 0, then we copy ## stream. audiobitrate () { local bitrate for i in $(seq 0 $((format_nb_streams-1))); do ## Skip non audio tracks. [ "$(eval echo \$streams_stream_"$i"_codec_type)" != "audio" ] && continue bitrate=$(eval echo \$streams_stream_"$i"_bit_rate) if [ -n "$bitrate" ] && [ "$bitrate" -gt 0 ] 2>/dev/null; then ## If non-empty and a positive number. bitrate=$((bitrate / 1000)) else bitrate="$AUDIO_DEFAULT_RATE" fi if [ "$bitrate" -le 0 ] || \ [ "$AUDIO_CODEC" = "$(eval echo \$streams_stream_"$i"_codec_name)" ] || \ [ "$AUDIO_CODEC" = "lib$(eval echo \$streams_stream_"$i"_codec_name)" ]; then printf -- "-c:%s copy " "$i" else [ "$bitrate" -gt 500 ] && bitrate=500 printf -- "-c:%s %s -b:%s %sk " "$i" "$AUDIO_CODEC" "$i" "$bitrate" fi done } transcode () { echo "==> [$1]" OUTPUT="${1%.*}.$EXT" [ -e "$OUTPUT" ] && OUTPUT="${1%.*}-$(date '+%F-%H%M%S').$EXT" ## Metadata (i.e. tags + technical data). buffer="$(ffprobe -v quiet -print_format flat=s=_ -show_streams -show_format "$1")" if [ $? -ne 0 ]; then echo "File [$1] is unsupported by FFmpeg." return fi ## The following 'eval' defines variables such as format_nb_streams eval "$buffer" unset buffer STREAM_TITLE="" if $OPT_REMOVE_TITLE; then for i in $(seq 0 $((format_nb_streams-1)) ); do STREAM_TITLE="${STREAM_TITLE}-metadata:s:$i title= " done fi if $OPT_CROP; then echo "Computing crop values... " ## For 5 different timeslices of 1 second at every 1/6th of the video, ## we sample the crop values. We keep the values with highest ## frequency. This is much faster than encoding in one pass with low ## framerate. STEP=${format_duration:-$streams_stream_0_duration} STEP="${STEP%%.*}" STEP=$((STEP/6)) VIDEO_FILTER="-vf $(cropvalue "$1" "$STEP")" if $OPT_CROPPREVIEW; then echo "Generating preview... " for i in $(seq $STEP $STEP $((5*STEP))); do ffmpeg -nostdin -v warning -y -ss "$i" -i "$1" \ -f image2 -vframes 1 "$VIDEO_FILTER" "${1%.*}-preview-$i-cropped.png" \ -f image2 -vframes 1 "${1%.*}-preview-$i-uncropped.png" done fi fi ## WARNING: We mix down audio to 2 channels with '-ac 2'. This greatly reduce ## file size and avoid any confusion for playback, which is often the case ## when converting DTS to any other format. (DTS has embedded channel ## description which is not always available in other formats.) ! $OPT_COPY && AUDIO_PARAM="$(audiobitrate "$1") -ac 2" cat <&2 User options: ${TC_VIDEO_OPT:-none} Sample: ${SAMPLE:-no} Clear tags: $OPT_REMOVE_TITLE In place: $OPT_OVERWRITE Crop: $OPT_CROP Video params: $VIDEO_PARAM Video filter: $VIDEO_FILTER Audio params: $AUDIO_PARAM Output file: $OUTPUT EOF $OPT_PREVIEW && return if $OPT_2PASS; then echo ":: FIRST PASS" ffmpeg -nostdin -y "$SAMPLE" -i "$1" \ "$VIDEO_PARAM" -pass 1 -tune film "$VIDEO_FILTER" \ "$AUDIO_PARAM" \ -c:s copy \ -dn \ -map 0 "$STREAM_TITLE" \ "$TC_VIDEO_OPT" -f matroska /dev/null VIDEO_PARAM="$VIDEO_PARAM -pass 2 -tune film" echo echo ":: SECOND PASS" fi ffmpeg -nostdin "$OVERWRITE" -i "$1" \ "$VIDEO_PARAM" "$VIDEO_FILTER" \ "$AUDIO_PARAM" \ -c:s copy \ -dn \ -map 0 "$STREAM_TITLE" \ "$SAMPLE" "$TC_VIDEO_OPT" "$OUTPUT" $OPT_2PASS && rm -v ffmpeg2pass-0.log ffmpeg2pass-0.log.mbtree if $OPT_OVERWRITE; then rm "$1" mv -f "$OUTPUT" "${1%.*}.$EXT" fi echo } for i; do transcode "$i" done