2013-03-03 00:01:20 +01:00
|
|
|
#!/bin/sh
|
|
|
|
|
2013-10-24 21:52:34 +02:00
|
|
|
## TODO: handle srt encoding?
|
2013-03-03 00:01:20 +01:00
|
|
|
|
|
|
|
_printhelp()
|
|
|
|
{
|
|
|
|
cat <<EOF
|
2013-03-03 11:19:50 +01:00
|
|
|
Usage: ${1##*/} [OPTIONS] FILES|FOLDERS
|
2013-03-03 00:01:20 +01:00
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
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.
|
2013-03-03 00:01:20 +01:00
|
|
|
|
2013-10-23 20:11:42 +02:00
|
|
|
Options:
|
2013-11-29 00:12:06 +01:00
|
|
|
-a CODEC: Audio codec supported by ffmpeg. Defaults to 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
|
2013-05-05 10:19:01 +02:00
|
|
|
|
2013-10-23 20:11:42 +02:00
|
|
|
User options are read from variable TC_VIDEO_OPT. This can be useful if you
|
2013-10-24 22:03:35 +02:00
|
|
|
often use the same options.
|
2013-10-23 20:11:42 +02:00
|
|
|
|
2013-05-05 10:19:01 +02:00
|
|
|
Examples:
|
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
* Get a preview of changes.
|
|
|
|
|
|
|
|
${1##*/} -p input.video
|
|
|
|
|
|
|
|
* Proceed with default options over folders and files.
|
|
|
|
|
|
|
|
${1##*/} input-folder input.video
|
|
|
|
|
2013-11-29 14:17:49 +01:00
|
|
|
* Convert to .avi container with xvid (quality 3) video and mp3 audio.
|
2013-11-29 00:12:06 +01:00
|
|
|
|
2013-11-29 14:17:49 +01:00
|
|
|
${1##*/} -e avi -v "libxvid -qscale:v 3" -a mp3 input.video
|
2013-11-29 00:12:06 +01:00
|
|
|
|
|
|
|
* Preview black stripes cropping.
|
|
|
|
|
|
|
|
${1##*/} -P input.video
|
|
|
|
|
|
|
|
* Process with default options and remove black stripes.
|
|
|
|
|
|
|
|
${1##*/} -C input.video
|
|
|
|
|
2013-05-14 16:38:27 +02:00
|
|
|
* Exchange stream 1 an 2 by first removing them, then reading them in the
|
2013-05-05 10:19:01 +02:00
|
|
|
desired order.
|
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
${1##*/} -o '-map -0:1 -map -0:2 -map 0:2 -map 0:1' input.video
|
2013-05-05 10:19:01 +02:00
|
|
|
|
|
|
|
* Change audio stream 1 title and remove audio stream 2 title:
|
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
${1##*/} -o '-metadata:s:a:0 title="FR: OGG Stereo" -metadata:s:a:1 title=' input.video
|
2013-05-05 10:19:01 +02:00
|
|
|
|
2013-03-03 00:01:20 +01:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
EXT="mkv"
|
|
|
|
AUDIO_CODEC="libvorbis"
|
2013-10-23 20:11:42 +02:00
|
|
|
VIDEO_PARAM="-c:v libx264 -preset slow -crf 20"
|
|
|
|
AUDIO_PARAM=""
|
2013-11-29 00:12:06 +01:00
|
|
|
|
|
|
|
VIDEO_FILTER=""
|
2013-10-23 20:11:42 +02:00
|
|
|
AUDIO_DEFAULT_RATE=0
|
2013-08-04 11:29:35 +02:00
|
|
|
|
2013-10-22 18:44:50 +02:00
|
|
|
OVERWRITE="-n"
|
2013-11-29 00:12:06 +01:00
|
|
|
SAMPLE=""
|
2013-09-21 18:09:18 +02:00
|
|
|
OPT_OVERWRITE=false
|
2013-10-22 18:44:50 +02:00
|
|
|
OPT_REMOVE_TITLE=false
|
|
|
|
OPT_CROP=false
|
2013-10-24 21:52:34 +02:00
|
|
|
OPT_CROPPREVIEW=false
|
2013-10-23 20:11:42 +02:00
|
|
|
OPT_PREVIEW=false
|
2013-10-27 14:28:25 +01:00
|
|
|
OPT_COPY=false
|
2013-03-03 00:01:20 +01:00
|
|
|
|
2013-11-29 00:12:06 +01:00
|
|
|
while getopts ":a:b:cCe:fho:pPsS:tv:" opt; do
|
2013-03-03 00:01:20 +01:00
|
|
|
case $opt in
|
2013-11-29 00:12:06 +01:00
|
|
|
a)
|
|
|
|
AUDIO_CODEC=$OPTARG ;;
|
2013-10-23 20:11:42 +02:00
|
|
|
b)
|
|
|
|
AUDIO_DEFAULT_RATE=$OPTARG ;;
|
2013-07-14 13:27:25 +02:00
|
|
|
c)
|
2013-10-23 20:11:42 +02:00
|
|
|
VIDEO_PARAM="-c:v copy"
|
2013-10-27 14:28:25 +01:00
|
|
|
AUDIO_PARAM="-c:a copy"
|
|
|
|
OPT_COPY=true;;
|
2013-10-22 18:44:50 +02:00
|
|
|
C)
|
|
|
|
OPT_CROP=true;;
|
2013-11-29 00:12:06 +01:00
|
|
|
e)
|
|
|
|
EXT=$OPTARG;;
|
2013-03-03 00:01:20 +01:00
|
|
|
f)
|
2013-10-22 18:44:50 +02:00
|
|
|
OVERWRITE="-y"
|
2013-09-21 18:09:18 +02:00
|
|
|
OPT_OVERWRITE=true;;
|
2013-03-03 00:01:20 +01:00
|
|
|
h)
|
|
|
|
_printhelp "$0"
|
2013-07-14 13:27:25 +02:00
|
|
|
exit 1 ;;
|
2013-05-05 10:19:01 +02:00
|
|
|
o)
|
2013-10-23 20:11:42 +02:00
|
|
|
TC_VIDEO_OPT="$OPTARG" ;;
|
|
|
|
p)
|
|
|
|
OPT_PREVIEW=true ;;
|
2013-10-24 21:52:34 +02:00
|
|
|
P)
|
|
|
|
OPT_CROP=true
|
|
|
|
OPT_PREVIEW=true
|
|
|
|
OPT_CROPPREVIEW=true ;;
|
2013-03-03 00:01:20 +01:00
|
|
|
s)
|
2013-10-22 18:44:50 +02:00
|
|
|
SAMPLE="-ss 60 -t 360" ;;
|
2013-05-03 23:44:53 +02:00
|
|
|
S)
|
2013-10-22 18:44:50 +02:00
|
|
|
SAMPLE="-ss 60 -t $((60*$OPTARG))" ;;
|
2013-05-14 16:38:27 +02:00
|
|
|
t)
|
2013-10-22 18:44:50 +02:00
|
|
|
OPT_REMOVE_TITLE=true ;;
|
2013-11-29 00:12:06 +01:00
|
|
|
v)
|
|
|
|
VIDEO_PARAM="-c:v $OPTARG" ;;
|
2013-03-03 00:01:20 +01:00
|
|
|
?)
|
|
|
|
_printhelp "$0"
|
2013-07-14 13:27:25 +02:00
|
|
|
exit 1 ;;
|
2013-03-03 00:01:20 +01:00
|
|
|
:)
|
|
|
|
echo "Missing argument."
|
2013-03-03 11:19:50 +01:00
|
|
|
_printhelp "$0"
|
2013-07-14 13:27:25 +02:00
|
|
|
exit 1 ;;
|
2013-03-03 00:01:20 +01:00
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
|
|
|
shift $(($OPTIND - 1))
|
|
|
|
if [ $# -eq 0 ]; then
|
2013-05-05 20:57:20 +02:00
|
|
|
_printhelp "$0"
|
2013-03-03 00:01:20 +01:00
|
|
|
exit
|
|
|
|
fi
|
|
|
|
|
2013-10-27 19:50:23 +01:00
|
|
|
if ! command -v ffmpeg >/dev/null; then
|
2013-10-23 20:11:42 +02:00
|
|
|
echo "ffmpeg required."
|
|
|
|
exit
|
|
|
|
fi
|
2013-03-03 00:01:20 +01:00
|
|
|
|
2013-10-22 18:44:50 +02:00
|
|
|
_duration()
|
|
|
|
{
|
|
|
|
## In seconds.
|
2013-10-24 21:52:34 +02:00
|
|
|
ffmpeg -nostdin -i "$1" 2>&1 | awk '/Duration/ {split($2, time, /:|\./); print time[1]*60*60 + time[2]*60 + time[3]}'
|
2013-10-22 18:44:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_highfreq()
|
|
|
|
{
|
|
|
|
awk 'BEGIN{max=0} /crop=/ {t[$NF]++; if (t[$NF]>max) {max=t[$NF]; val=$NF}} END{print val}'
|
|
|
|
}
|
|
|
|
|
|
|
|
_cropvalue()
|
|
|
|
{
|
|
|
|
## For 5 different timeslices of 1 second at every 1/6th of the video, we
|
2013-11-29 00:12:06 +01:00
|
|
|
## sample the crop values. We keep the values with highest frequency. This
|
|
|
|
## is much faster than encoding in one pass with low framerate.
|
2013-10-22 18:44:50 +02:00
|
|
|
STEP=$(($(_duration "$1")/6))
|
|
|
|
for i in $(seq $STEP $STEP $((5*$STEP))); do
|
2013-10-24 21:52:34 +02:00
|
|
|
ffmpeg -nostdin -ss $i -i "$1" -t 10 -vf "cropdetect=24:2:0" -f null - 2>&1
|
2013-10-22 18:44:50 +02:00
|
|
|
done | _highfreq
|
|
|
|
}
|
|
|
|
|
2013-10-24 21:52:34 +02:00
|
|
|
_croppreview()
|
|
|
|
{
|
|
|
|
STEP=$(($(_duration "$1")/6))
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2013-10-23 19:28:53 +02:00
|
|
|
_audiobitrate()
|
2013-03-03 00:01:20 +01:00
|
|
|
{
|
2013-10-23 19:28:53 +02:00
|
|
|
## As of 2013-10-23, ffmpeg sucks at retrieving audio bitrate. Therefore we
|
|
|
|
## use mediainfo if available, ffmpeg otherwise.
|
|
|
|
|
|
|
|
## In the end, if some stream bitrates are missing, we have two
|
2013-10-24 16:48:45 +02:00
|
|
|
## possibilities: either we copy the stream (default), or we encode it to a
|
|
|
|
## default value.
|
2013-10-23 19:28:53 +02:00
|
|
|
|
2013-10-24 16:48:45 +02:00
|
|
|
## AWK note: in "(bitrate+0 > 500)", the +0 is required to make sure bitrate is numeric.
|
2013-10-27 19:50:23 +01:00
|
|
|
if command -v mediainfo >/dev/null; then
|
2013-11-26 21:21:58 +01:00
|
|
|
## WARNING: mediainfo puts a space every 3 digits, hence the 'for' loop.
|
|
|
|
mediainfo "$1" | awk 'BEGIN {id=0} /^Audio/ { while(getline && ! index($0,"Bit rate ") && NF != 0); for(i=4; i<NF; i++) res=res $i; print id, res; id++}'
|
2013-10-23 19:28:53 +02:00
|
|
|
else
|
2013-10-24 21:52:34 +02:00
|
|
|
ffmpeg -nostdin -i "$1" 2>&1 | awk 'BEGIN {id=0} /^ *Stream.*Audio/ {match($0, / [^ ]+ kb\/s/); res=substr($0, RSTART+1, RLENGTH-6); print id, res; id++}'
|
2013-10-24 17:01:44 +02:00
|
|
|
fi | awk -v codec="$AUDIO_CODEC" -v bitrate="$AUDIO_DEFAULT_RATE" \
|
|
|
|
'{if($2 || bitrate) {if($2) bitrate=$2; if(bitrate+0 > 500) bitrate=500; printf "-c:a:" $1 " " codec " -b:a:" $1 " " bitrate "k "} else printf "-c:a:" $1 " copy "} END {print ""}'
|
2013-10-23 19:28:53 +02:00
|
|
|
}
|
|
|
|
|
2013-10-24 22:03:35 +02:00
|
|
|
_transcode()
|
2013-10-23 19:28:53 +02:00
|
|
|
{
|
|
|
|
echo "==> [$1]"
|
2013-11-29 00:12:06 +01:00
|
|
|
OUTPUT="${1%.*}.$EXT"
|
|
|
|
[ -e "$OUTPUT" ] && OUTPUT="${1%.*}-$(date '+%F-%H%M%S').$EXT"
|
2013-05-05 20:57:20 +02:00
|
|
|
|
2013-05-14 16:38:27 +02:00
|
|
|
STREAM_TITLE=""
|
2013-10-22 18:44:50 +02:00
|
|
|
if $OPT_REMOVE_TITLE; then
|
2013-10-24 21:52:34 +02:00
|
|
|
STREAM_NUM=$(ffmpeg -nostdin -i "$1" 2>&1 | grep -c 'Stream')
|
2013-05-14 16:38:27 +02:00
|
|
|
for i in $(seq 0 $STREAM_NUM); do
|
|
|
|
STREAM_TITLE="${STREAM_TITLE}-metadata:s:$i title= "
|
|
|
|
done
|
|
|
|
fi
|
|
|
|
|
2013-10-22 18:44:50 +02:00
|
|
|
if $OPT_CROP; then
|
2013-10-23 19:28:53 +02:00
|
|
|
echo "Computing crop values... "
|
2013-10-25 16:18:33 +02:00
|
|
|
VIDEO_FILTER="-vf $(_cropvalue "$1")"
|
2013-10-24 21:52:34 +02:00
|
|
|
if $OPT_CROPPREVIEW; then
|
|
|
|
echo "Generating preview... "
|
|
|
|
_croppreview "$1"
|
|
|
|
fi
|
2013-10-22 18:44:50 +02:00
|
|
|
fi
|
|
|
|
|
2013-10-27 14:28:25 +01:00
|
|
|
## 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"
|
2013-10-23 19:28:53 +02:00
|
|
|
|
|
|
|
cat<<EOF
|
|
|
|
================================================================================
|
2013-10-23 20:11:42 +02:00
|
|
|
User options: ${TC_VIDEO_OPT:-none}
|
|
|
|
Sample: ${SAMPLE:-no}
|
|
|
|
Clear tags: $OPT_REMOVE_TITLE
|
|
|
|
In place: $OPT_OVERWRITE
|
2013-10-23 19:28:53 +02:00
|
|
|
Crop: $OPT_CROP
|
2013-11-29 00:12:06 +01:00
|
|
|
Video codec: $VIDEO_PARAM
|
2013-10-23 19:28:53 +02:00
|
|
|
Video filter: $VIDEO_FILTER
|
2013-11-29 00:12:06 +01:00
|
|
|
Audio codec: $AUDIO_PARAM
|
|
|
|
Output file: $OUTPUT
|
2013-10-23 19:28:53 +02:00
|
|
|
================================================================================
|
|
|
|
EOF
|
|
|
|
|
2013-10-27 14:28:25 +01:00
|
|
|
$OPT_PREVIEW && return
|
2013-10-23 12:51:09 +02:00
|
|
|
|
2013-10-23 20:11:42 +02:00
|
|
|
## Zsh compatibility. We need it otherwise word splitting of parameter like
|
|
|
|
## SAMPLE will not work.
|
2014-01-04 10:37:47 +01:00
|
|
|
STATUS="$(set -o | awk '/shwordsplit / {print $2}')"
|
2013-10-23 20:11:42 +02:00
|
|
|
[ "$STATUS" = "off" ] && set -o shwordsplit
|
|
|
|
|
2013-10-24 21:52:34 +02:00
|
|
|
ffmpeg -nostdin $OVERWRITE -i "$1" \
|
2013-10-23 20:11:42 +02:00
|
|
|
$VIDEO_PARAM $VIDEO_FILTER \
|
|
|
|
$AUDIO_PARAM \
|
2013-03-03 00:01:20 +01:00
|
|
|
-c:s copy \
|
2013-05-14 16:38:27 +02:00
|
|
|
-map 0 $STREAM_TITLE \
|
2013-10-24 21:52:34 +02:00
|
|
|
$SAMPLE $TC_VIDEO_OPT "$OUTPUT"
|
2013-10-23 20:11:42 +02:00
|
|
|
|
|
|
|
## Restore Zsh previous options. This will not turn off shwordsplit if it
|
|
|
|
## was on before calling the function.
|
|
|
|
[ "$STATUS" = "off" ] && set +o shwordsplit
|
2013-09-21 18:09:18 +02:00
|
|
|
|
|
|
|
if $OPT_OVERWRITE; then
|
2013-10-24 22:03:35 +02:00
|
|
|
rm "$1"
|
2013-11-29 00:12:06 +01:00
|
|
|
mv -f "$OUTPUT" "${1%.*}.$EXT"
|
2013-09-21 18:09:18 +02:00
|
|
|
fi
|
2013-10-22 18:44:50 +02:00
|
|
|
echo
|
2013-03-03 00:01:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for i in "$@"; do
|
|
|
|
|
|
|
|
## Argument is a folder. We search for all video files in there.
|
|
|
|
if [ -d "$i" ]; then
|
|
|
|
|
2013-10-23 19:28:53 +02:00
|
|
|
## WARNING: ffmpeg continues to read stdin once it has started, so it should
|
2013-10-24 21:52:34 +02:00
|
|
|
## not be called from within a while<<EOF loop without disabling stdin.
|
2013-10-25 13:44:07 +02:00
|
|
|
while IFS= read -r j; do
|
2013-10-24 22:03:35 +02:00
|
|
|
_transcode "$j"
|
2013-10-23 19:28:53 +02:00
|
|
|
done<<EOF
|
|
|
|
$(find "$i" \( \
|
2013-10-26 16:48:43 +02:00
|
|
|
-iname '*.avi' -o \
|
|
|
|
-iname '*.flv' -o \
|
2013-10-22 18:44:50 +02:00
|
|
|
-iname '*.mkv' -o \
|
2013-10-26 16:48:43 +02:00
|
|
|
-iname '*.mov' -o \
|
2013-10-22 18:44:50 +02:00
|
|
|
-iname '*.mp4' -o \
|
2013-10-26 16:48:43 +02:00
|
|
|
-iname '*.mpeg' -o \
|
|
|
|
-iname '*.mpg' -o \
|
2013-10-22 18:44:50 +02:00
|
|
|
-iname '*.webm' -o \
|
2013-10-26 16:48:43 +02:00
|
|
|
-iname '*.wmv' \) )
|
2013-10-23 19:28:53 +02:00
|
|
|
EOF
|
2013-03-03 00:01:20 +01:00
|
|
|
|
|
|
|
else
|
2013-05-05 10:19:01 +02:00
|
|
|
## Argument is a regular file.
|
2013-10-24 22:03:35 +02:00
|
|
|
_transcode "$i"
|
2013-03-03 00:01:20 +01:00
|
|
|
fi
|
|
|
|
|
|
|
|
done
|