ambevar-dotfiles/.scripts/tc-video-generic

292 lines
7.3 KiB
Bash
Executable File

#!/bin/sh
## TODO: handle srt encoding?
_usage () {
cat <<EOF
Usage: ${1##*/} [OPTIONS] FILES|FOLDERS
Transcode FILES or files found in FOLDERS to desired format (default to .mkv
with x264 and ogg vorbis). Black stripes are *not* cropped by default since it
may be sometimes inaccurate. By default output filenames are the same as the
original, with time appended.
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).
-e EXT: Set output container by specifying the extension. Defaults to mkv.
-f: Remove source when done.
-h: Display this help.
-o OPT: Additional options.
-p: Preview changes, do not encode.
-P: Generate two thumbnails, one cropped, the other not. Enable
auto-crop, do not encode.
-s: Sample of 5 minutes.
-S MIN: Sample of MIN minutes.
-t: Remove all "title" metadata.
-v CODEC: Video codec supported by ffmpeg. Defaults to libx264.
You can get a list of supported codecs with
$ ffmpeg -codecs
User options are read from variable TC_VIDEO_OPT. This can be useful if you
often use the same options.
Examples:
* Get a preview of changes.
${1##*/} -p input.video
* Proceed with default options over folders and files.
${1##*/} input-folder input.video
* Convert to .avi container with xvid (quality 3) video and mp3 audio.
${1##*/} -e avi -v "libxvid -qscale:v 3" -a mp3 input.video
* Preview black stripes cropping.
${1##*/} -P input.video
* Process with default options and remove black stripes.
${1##*/} -C input.video
* Exchange stream 1 an 2 by first removing them, then reading them in the
desired order.
${1##*/} -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:
${1##*/} -o '-metadata:s:a:0 title="FR: OGG Stereo" -metadata:s:a:1 title=' input.video
EOF
}
EXT="mkv"
AUDIO_CODEC="libvorbis"
VIDEO_PARAM="-c:v libx264 -preset slow -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
while getopts ":a:b:cCe:fho:pPsS:tv:" 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;;
e)
EXT=$OPTARG;;
f)
OVERWRITE="-y"
OPT_OVERWRITE=true;;
h)
_usage "$0"
exit 1 ;;
o)
TC_VIDEO_OPT="$OPTARG" ;;
p)
OPT_PREVIEW=true ;;
P)
OPT_CROP=true
OPT_PREVIEW=true
OPT_CROPPREVIEW=true ;;
s)
SAMPLE="-ss 60 -t 360" ;;
S)
SAMPLE="-ss 60 -t $((60*$OPTARG))" ;;
t)
OPT_REMOVE_TITLE=true ;;
v)
VIDEO_PARAM="-c:v $OPTARG" ;;
\?)
_usage "$0"
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
if [ $# -eq 0 ]; then
_usage "$0"
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
_error "File [$1] is unsupported by FFmpeg."
return
fi
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 because DTS has embedded
## channel description which is not available in these formats.
! $OPT_COPY && AUDIO_PARAM="$(_audiobitrate "$1") -ac 2"
cat<<EOF
================================================================================
User options: ${TC_VIDEO_OPT:-none}
Sample: ${SAMPLE:-no}
Clear tags: $OPT_REMOVE_TITLE
In place: $OPT_OVERWRITE
Crop: $OPT_CROP
Video codec: $VIDEO_PARAM
Video filter: $VIDEO_FILTER
Audio codec: $AUDIO_PARAM
Output file: $OUTPUT
================================================================================
EOF
$OPT_PREVIEW && return
ffmpeg -nostdin $OVERWRITE -i "$1" \
$VIDEO_PARAM $VIDEO_FILTER \
$AUDIO_PARAM \
-c:s copy \
-dn \
-map 0 $STREAM_TITLE \
$SAMPLE $TC_VIDEO_OPT "$OUTPUT"
if $OPT_OVERWRITE; then
rm "$1"
mv -f "$OUTPUT" "${1%.*}.$EXT"
fi
echo
}
for i in "$@"; do
## Argument is a folder. We search for all video files in there.
if [ -d "$i" ]; then
## WARNING: ffmpeg continues to read stdin once it has started, so it should
## not be called from within a while<<EOF loop without disabling stdin.
while IFS= read -r j; do
_transcode "$j"
done<<EOF
$(find "$i" \( \
-iname '*.avi' -o \
-iname '*.flv' -o \
-iname '*.mkv' -o \
-iname '*.mov' -o \
-iname '*.mp4' -o \
-iname '*.mpeg' -o \
-iname '*.mpg' -o \
-iname '*.webm' -o \
-iname '*.wmv' \) )
EOF
else
## Argument is a regular file.
_transcode "$i"
fi
done