730 lines
21 KiB
Bash
730 lines
21 KiB
Bash
## -*- mode:sh -*- #
|
||
################################################################################
|
||
## Shell -- Trancode function set.
|
||
## Date 2013-01-27
|
||
################################################################################
|
||
|
||
## Tool list: ffmpeg, recode, titlecase.awk
|
||
|
||
## Support:
|
||
# ffmpeg -codecs
|
||
# ffmpeg -formats
|
||
|
||
## Useful guides
|
||
## x264: http://ffmpeg.org/trac/ffmpeg/wiki/x264EncodingGuide
|
||
## ID3 details: http://en.wikipedia.org/wiki/ID3
|
||
|
||
################################################################################
|
||
## Text
|
||
|
||
tc_text_scan ()
|
||
{
|
||
local IFS
|
||
IFS="
|
||
"
|
||
|
||
for i in $(find . -type f -size -50M -print); do
|
||
echo -n "# "
|
||
file "$i"
|
||
grep -m1 "\`" "$i"
|
||
grep -m1 "oe" "$i"
|
||
done
|
||
}
|
||
|
||
## Confert all 'bad' encoding to UTF-8/LF.
|
||
##
|
||
## WARNING: It will fail for encoding other than ISO-8859-1 and cp1252.
|
||
tc_text_2utf8 ()
|
||
{
|
||
local CODING
|
||
local IFS
|
||
IFS="
|
||
"
|
||
|
||
for i in $(find . -type f -size -50M -print); do
|
||
CODING=$(file "$i")
|
||
|
||
if [ -n "$(echo $CODING | grep 'ISO-8859')" ]; then
|
||
echo "ISO-8859: [$i]"
|
||
recode latin1..utf-8 "$i"
|
||
|
||
elif [ -n "$(echo $CODING | grep 'Non-ISO extended-ASCII')" ]; then
|
||
echo "cp1252: [$i]"
|
||
recode cp1252..utf-8 "$i"
|
||
|
||
elif [ -n "$(echo $CODING | grep 'UTF-16 Unicode text')" ]; then
|
||
echo "UTF-16: [$i]"
|
||
recode utf-16..utf-8 "$i"
|
||
|
||
elif [ -n "$(echo $CODING | grep 'UTF-8 Unicode (with BOM)')" ]; then
|
||
echo "UTF-8 BOM: [$i]"
|
||
sed -i '1s/^.//' "$i"
|
||
## The following commands work, but may be overkill.
|
||
# dd iflag=skip_bytes skip=3 if=file.srt of=temp.srt
|
||
# dd bs=1 skip=3 if=file.srt of=temp.srt
|
||
# tail -c +32 file.srt > temp.srt
|
||
fi
|
||
|
||
if [ -n "$(echo $CODING | grep 'CRLF')" ]; then
|
||
echo "CRLF: [$i]"
|
||
sed -i 's/\r//g' "$i"
|
||
fi
|
||
|
||
done
|
||
}
|
||
|
||
################################################################################
|
||
## Audio
|
||
|
||
## For the sake of simplicity we convert everything to OGG.
|
||
## You can easily set output folder to current folder with TC_AUDIO_DEST=".".
|
||
TC_AUDIO_DEST="/media/data1/Musics/"
|
||
|
||
## OGG quality ranges from -1 to 10.
|
||
## -q-1 45 kbit/s
|
||
## -q0 64 kbit/s
|
||
## -q1 80 kbit/s
|
||
## -q2 96 kbit/s
|
||
## -q3 112 kbit/s
|
||
## -q4 128 kbit/s
|
||
## -q5 160 kbit/s
|
||
## -q6 192 kbit/s
|
||
## -q7 224 kbit/s
|
||
## -q8 256 kbit/s
|
||
## -q9 320 kbit/s
|
||
## -q10 500 kbit/s
|
||
|
||
## Artists: we use same value for artist and album_artist.
|
||
## Genres: since this is not universal by nature, we do not put a genre in tags.
|
||
## Composer: not universal neither, we prefer ARTIST over COMPOSER, so COMPOSER will be empty.
|
||
|
||
tc_audio_scan()
|
||
{
|
||
OLDIFS=$IFS
|
||
IFS="
|
||
"
|
||
|
||
local BUFFER
|
||
for i in $(find "." \( \
|
||
-iname '*.mp3' -o \
|
||
-iname '*.flac' -o \
|
||
-iname '*.wv' -o \
|
||
-iname '*.aac' -o \
|
||
-iname '*.wav' -o \
|
||
-iname '*.ape' -o \
|
||
-iname '*.mpc' \) ); do
|
||
|
||
BUFFER="$(mediainfo "$i")"
|
||
if [ -n "$(echo "$BUFFER" | grep "Bit rate mode *: Constant")" ]; then
|
||
echo "$i : CBR"
|
||
fi
|
||
|
||
if [ -n "$(echo "$BUFFER" | grep "Cover *: Yes")" ]; then
|
||
echo "$i : Cover"
|
||
fi
|
||
|
||
if [ -n "$(echo "$BUFFER" | grep "Track name/Position *: 0+")" ]; then
|
||
echo "$i : Leading zeros"
|
||
fi
|
||
|
||
done
|
||
|
||
IFS=$OLDIFS
|
||
unset OLDIFS
|
||
}
|
||
|
||
## This will convert all tracks in current folder and subfolders.
|
||
tc_audio_batch()
|
||
{
|
||
|
||
OLDIFS=$IFS
|
||
IFS="
|
||
"
|
||
|
||
for i in $(find "." \( \
|
||
-iname '*.mp3' -o \
|
||
-iname '*.flac' -o \
|
||
-iname '*.wv' -o \
|
||
-iname '*.aac' -o \
|
||
-iname '*.wav' -o \
|
||
-iname '*.ape' -o \
|
||
-iname '*.mpc' \) ); do
|
||
tc_audio_transcode "$@" $i
|
||
done
|
||
|
||
# for i in *.mp3 ; do tc_audio_transcode "$@" $i ; done
|
||
# tc_audio_transcode "$@" "01 James Russo - Winged.mp3"
|
||
|
||
IFS=$OLDIFS
|
||
unset OLDIFS
|
||
}
|
||
|
||
## This function is supposed to work on a per-album basis. Folders will not be
|
||
## accepted as argument. This function will work best if all tracks of a folder
|
||
## belong to the same album. Theres is no way to handle covers raliably, so you
|
||
## should leave the only covers you want to keep in the working folder.
|
||
|
||
## TODO: cover resolution check. Should be > 100x100. Above <1000x1000 should give a warning.
|
||
## TODO: extract cover from tags if checksum does not match any from current folder.
|
||
tc_audio_transcode()
|
||
{
|
||
IFS=${OLDIFS:-$IFS}
|
||
|
||
_printhelp ()
|
||
{
|
||
cat <<EOF
|
||
Usage: $1 [OPTIONS] FILE
|
||
|
||
Options:
|
||
-p : preview (do not change file)
|
||
-s : skip encoding
|
||
|
||
Tags:
|
||
-a : artist
|
||
-b : bitrate
|
||
-d : date
|
||
-g : genre
|
||
-l : album
|
||
-n : track number
|
||
-t : title
|
||
|
||
You can use the following variables to refer to existing tags:
|
||
\$ALBUM
|
||
\$ALBUM_ARTIST
|
||
\$ARTIST
|
||
\$COMPOSER
|
||
\$DATE
|
||
\$TYER
|
||
\$GENRE
|
||
\$TRACK
|
||
|
||
Default output folder:
|
||
OUTPUT="\$TC_AUDIO_DEST/\$OUTPUT_ARTIST/\${OUTPUT_ALBUM:+\${OUTPUT_DATE:+\$OUTPUT_DATE - }\$OUTPUT_ALBUM/}"
|
||
|
||
Default output file:
|
||
OUTPUT_FILE="\$OUTPUT\$OUTPUT_ARTIST - \${OUTPUT_TRACK:+\$OUTPUT_TRACK - }\$OUTPUT_TITLE.\$OUTPUT_FORMAT"
|
||
|
||
Examples:
|
||
|
||
Set the 'artist' tag and reencode:
|
||
$1 -a 'Franz Lizst' file.mp3
|
||
|
||
Set 'artist' to be 'composer', and 'title' to be preceeded by 'artist', do not reencode:
|
||
$1 -s -a '\$COMPOSER' -t '\$ARTIST - \$TITLE' file.ogg
|
||
|
||
IMPORTANT: you *must* use single quotes when using variables.
|
||
|
||
EOF
|
||
}
|
||
|
||
## OPTIONS
|
||
local SKIP
|
||
SKIP=0
|
||
|
||
local PREVIEW
|
||
PREVIEW=0
|
||
|
||
## TAGS
|
||
local OUTPUT_ALBUM
|
||
OUTPUT_ALBUM='$ALBUM'
|
||
local OUTPUT_ARTIST
|
||
OUTPUT_ARTIST='$ARTIST'
|
||
local OUTPUT_DATE
|
||
OUTPUT_DATE='$DATE'
|
||
local OUTPUT_GENRE
|
||
OUTPUT_GENRE='$GENRE'
|
||
local OUTPUT_TITLE
|
||
OUTPUT_TITLE='$TITLE'
|
||
local OUTPUT_TRACK
|
||
OUTPUT_TRACK='$TRACK'
|
||
|
||
## PROPERTIES
|
||
local OUTPUT_BITRATE
|
||
OUTPUT_BITRATE=0
|
||
|
||
## Non-cli-option data. Modifying these imply modifications in code below.
|
||
local OUTPUT_FORMAT
|
||
OUTPUT_FORMAT="ogg"
|
||
local OGG_PARAM
|
||
OGG_PARAM="-c:a libvorbis -b:a ${OUTPUT_BITRATE}k"
|
||
|
||
## These ones are not cli-options either, but this could be easily changed.
|
||
local OUTPUT
|
||
local OUTPUT_FILE
|
||
OUTPUT='$TC_AUDIO_DEST/$OUTPUT_ARTIST/${OUTPUT_ALBUM:+${OUTPUT_DATE:+$OUTPUT_DATE - }$OUTPUT_ALBUM/}'
|
||
OUTPUT_FILE='$OUTPUT$OUTPUT_ARTIST - ${OUTPUT_PADDEDTRACK:+$OUTPUT_PADDEDTRACK - }$OUTPUT_TITLE'
|
||
|
||
while getopts ":a:b:d:g:l:n:t:hps" opt; do
|
||
case $opt in
|
||
|
||
a) OUTPUT_ARTIST=$OPTARG ;;
|
||
b) OUTPUT_BITRATE=$OPTARG ;;
|
||
d) OUTPUT_DATE=$OPTARG ;;
|
||
g) OUTPUT_GENRE=$OPTARG ;;
|
||
l) OUTPUT_ALBUM=$OPTARG ;;
|
||
n) OUTPUT_TRACK=$OPTARG ;;
|
||
t) OUTPUT_TITLE=$OPTARG ;;
|
||
|
||
h)
|
||
_printhelp "$0"
|
||
return 1
|
||
;;
|
||
p)
|
||
PREVIEW=1
|
||
;;
|
||
|
||
s)
|
||
SKIP=1
|
||
;;
|
||
|
||
?)
|
||
_printhelp "$0"
|
||
return 1
|
||
;;
|
||
:)
|
||
echo "Missing argument."
|
||
echo "Use $0 -h for help."
|
||
return 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
shift $(($OPTIND - 1))
|
||
if [ $# -eq 0 ]; then
|
||
_printhelp "$0"
|
||
return
|
||
fi
|
||
|
||
##================================================================================
|
||
## Get metadata.
|
||
local METADATA
|
||
local STREAM
|
||
STREAM=$(ffmpeg -i "$1" 2>&1)
|
||
METADATA=$(echo "$STREAM" | sed -n '/Metadata/ ! d; /Metada/{b cont}; :cont ; {n;p;b cont}')
|
||
STREAM=$(echo "$STREAM" | grep "Stream")
|
||
|
||
if [ -z "$STREAM" ]; then
|
||
echo "Non-audio file."
|
||
return
|
||
fi
|
||
|
||
|
||
## We get format from extension, because codec is not reliable either.
|
||
local FORMAT
|
||
FORMAT="${1##*.}"
|
||
## CODEC is unused for now.
|
||
local CODEC
|
||
CODEC=$(echo "$STREAM" | sed -n '/Audio:/ {s/.*Audio: \([^,]*\),.*/\1/;p}')
|
||
local BITRATE
|
||
BITRATE=$(echo "$STREAM" | sed -n '/kb\/s/ {s/.*, \(.*\) kb\/s/\1/;p}')
|
||
|
||
if [ -z "$FORMAT" ]; then
|
||
echo "ERROR: non-audio file."
|
||
return
|
||
fi
|
||
|
||
_metadata_filter()
|
||
{
|
||
echo "$METADATA" | grep -i "\<$1\>" | sed 's/[^:]* : //g'
|
||
}
|
||
|
||
local TITLE
|
||
TITLE=$(_metadata_filter "title")
|
||
local ARTIST
|
||
ARTIST=$(_metadata_filter "artist")
|
||
local ALBUM
|
||
ALBUM=$(_metadata_filter "album")
|
||
local ALBUM_ARTIST
|
||
ALBUM_ARTIST=$(_metadata_filter "album_artist")
|
||
local COMPOSER
|
||
COMPOSER=$(_metadata_filter "composer")
|
||
local DISC
|
||
DISC=$(_metadata_filter "disc")
|
||
local GENRE
|
||
GENRE=$(_metadata_filter "genre")
|
||
|
||
local TRACK
|
||
TRACK=$(_metadata_filter "track")
|
||
local DATE
|
||
DATE=$(_metadata_filter "date")
|
||
local TYER
|
||
TYER=$(_metadata_filter "TYER")
|
||
|
||
##================================================================================
|
||
## OUTPUT variables.
|
||
|
||
_string_cleanser()
|
||
{
|
||
## We use the AWK script to set title case. The script contains
|
||
## exceptions that can be configured. We fix some chars with sed.
|
||
|
||
# ’ => '
|
||
# : => -
|
||
# / => -
|
||
|
||
awk -f "$HOME/.shell.d/titlecase.awk" \
|
||
<(echo "$@" | sed -n -e "s/’/'/g ; s/ *: */ - /g ; s| */ *| - |g; p; q")
|
||
}
|
||
|
||
OUTPUT_TITLE=$(eval _string_cleanser $OUTPUT_TITLE)
|
||
OUTPUT_TITLE="${OUTPUT_TITLE:-Unknown Title}"
|
||
|
||
## We use album artist if artist is empty.
|
||
OUTPUT_ARTIST=$(eval _string_cleanser $OUTPUT_ARTIST)
|
||
[ -z "$OUTPUT_ARTIST" ] && [ -n "$ALBUM_ARTIST" ] && OUTPUT_ARTIST="$ALBUM_ARTIST"
|
||
OUTPUT_ARTIST="${OUTPUT_ARTIST:-Unknown Artist}"
|
||
|
||
OUTPUT_ALBUM=$(eval _string_cleanser $OUTPUT_ALBUM)
|
||
[ -z "$OUTPUT_ALBUM" ] && echo "${#OUTPUT_ALBUM}${OUTPUT_ALBUM}"
|
||
|
||
## We put genre in lower case and underscore to ease matching. If it
|
||
## matches, we use the Title Case match. If it does not, we set it to empty.
|
||
OUTPUT_GENRE=$(eval _string_cleanser $OUTPUT_GENRE | tr '[:upper:] ' '[:lower:]_')
|
||
case $OUTPUT_GENRE in
|
||
ost) OUTPUT_GENRE="Soundtrack" ;;
|
||
soundtrack) OUTPUT_GENRE="Soundtrack";;
|
||
original_soundtrack) OUTPUT_GENRE="Soundtrack";;
|
||
classical) OUTPUT_GENRE="Classical";;
|
||
classics) OUTPUT_GENRE="Classical";;
|
||
classic) OUTPUT_GENRE="Classical";;
|
||
*) OUTPUT_GENRE="";;
|
||
esac
|
||
|
||
## We remove the track count if any, we suppress leading zeros.
|
||
OUTPUT_TRACK=$(eval _string_cleanser $OUTPUT_TRACK | sed -e 's/^0*//' -e 's|/.*||')
|
||
|
||
## If DATE is not a year, we use TYER if it is a year.
|
||
OUTPUT_DATE=$(eval _string_cleanser $OUTPUT_DATE)
|
||
local TYER_REG=$(echo "$TYER" | sed -n 's/.*\([[:digit:]]\{4\}\).*/\1/p')
|
||
if [ ${#DATE} -ne 4 ] && [ ${#TYER_REG} -eq 4 ]; then
|
||
OUTPUT_DATE="$TYER_REG"
|
||
fi
|
||
|
||
## QUALITY
|
||
## If bitrate argumument is not provided, we use BITRATE value.
|
||
[ $OUTPUT_BITRATE -eq 0 ] && OUTPUT_BITRATE=${BITRATE}
|
||
## TODO: test if table is useful at all.
|
||
# if [ $OUTPUT_BITRATE -eq 0 ]; then
|
||
# [ $BITRATE -le 45 ] && OUTPUT_BITRATE=45k
|
||
# [ $BITRATE -gt 45 ] && OUTPUT_BITRATE=64k
|
||
# [ $BITRATE -gt 64 ] && OUTPUT_BITRATE=80k
|
||
# [ $BITRATE -gt 80 ] && OUTPUT_BITRATE=96k
|
||
# [ $BITRATE -gt 96 ] && OUTPUT_BITRATE=112k
|
||
# [ $BITRATE -gt 112 ] && OUTPUT_BITRATE=128k
|
||
# [ $BITRATE -gt 128 ] && OUTPUT_BITRATE=160k
|
||
# [ $BITRATE -gt 160 ] && OUTPUT_BITRATE=192k
|
||
# [ $BITRATE -gt 192 ] && OUTPUT_BITRATE=224k
|
||
# [ $BITRATE -gt 224 ] && OUTPUT_BITRATE=256k
|
||
# [ $BITRATE -gt 256 ] && OUTPUT_BITRATE=320k
|
||
# [ $BITRATE -gt 320 ] && OUTPUT_BITRATE=500k
|
||
# fi
|
||
|
||
## Only reencode if not in OGG and if SKIP not set.
|
||
OGG_PARAM="-c:a libvorbis -b:a ${OUTPUT_BITRATE}k"
|
||
[ $SKIP -ne 0 ] && OGG_PARAM="-c:a copy" && OUTPUT_FORMAT="$FORMAT"
|
||
[ "$FORMAT" = "ogg" ] && OGG_PARAM="-c:a copy"
|
||
|
||
## Make sure track number has two digits for file name only.
|
||
local OUTPUT_PADDEDTRACK
|
||
OUTPUT_PADDEDTRACK=$OUTPUT_TRACK
|
||
if [ -n "$OUTPUT_PADDEDTRACK" ]; then
|
||
[ ${OUTPUT_PADDEDTRACK} -lt 10 ] && OUTPUT_PADDEDTRACK="0$OUTPUT_PADDEDTRACK"
|
||
fi
|
||
|
||
OUTPUT=$(eval echo $OUTPUT)
|
||
OUTPUT_FILE=$(eval echo $OUTPUT_FILE)
|
||
|
||
## IF file exist, we append a uniq timestamp to the name.
|
||
[ -e "${OUTPUT_FILE}.$OUTPUT_FORMAT" ] && OUTPUT_FILE="$OUTPUT_FILE-$(date '+%F-%H%M%S')"
|
||
|
||
##==============================================================================
|
||
## PREVIEW
|
||
|
||
echo "==> BEFORE"
|
||
echo "ALBUM [$ALBUM]"
|
||
echo "ALBUM_ARTIST [$ALBUM_ARTIST]"
|
||
echo "ARTIST [$ARTIST]"
|
||
echo "BITRATE [$BITRATE]"
|
||
echo "COMPOSER [$COMPOSER]"
|
||
echo "DATE [$DATE]"
|
||
echo "DISC [$DISC]"
|
||
echo "FORMAT [$FORMAT]"
|
||
echo "GENRE [$GENRE]"
|
||
echo "TITLE [$TITLE]"
|
||
echo "TRACK [$TRACK]"
|
||
echo "TYER [$TYER]"
|
||
echo
|
||
echo "==> AFTER"
|
||
echo "ALBUM [$OUTPUT_ALBUM]"
|
||
echo "ARTIST [$OUTPUT_ARTIST]"
|
||
echo "BITRATE [$OUTPUT_BITRATE]"
|
||
echo "DATE [$OUTPUT_DATE]"
|
||
echo "FORMAT [$OUTPUT_FORMAT]"
|
||
echo "GENRE [$OUTPUT_GENRE]"
|
||
echo "TITLE [$OUTPUT_TITLE]"
|
||
echo "TRACK [$OUTPUT_TRACK]"
|
||
echo
|
||
echo "==> OUTPUT"
|
||
echo "[$OUTPUT_FILE.$OUTPUT_FORMAT]"
|
||
# echo "[$OGG_PARAM]"
|
||
echo
|
||
|
||
[ $PREVIEW -ne 0 ] && return
|
||
|
||
##==============================================================================
|
||
## RUN PROCESS
|
||
|
||
## Make sure directory exists.
|
||
mkdir -p "$OUTPUT"
|
||
if [ $? -ne 0 ]; then
|
||
echo "ERROR: could not create output folder"
|
||
return
|
||
fi
|
||
|
||
## COVER
|
||
## We copy cover only if it does not already exist.
|
||
local OUTPUT_COVER
|
||
local COVER_COUNTER
|
||
local OUTPUT_COVERFILE
|
||
OLDIFS=$IFS
|
||
IFS="
|
||
"
|
||
for i in $(find "." \( \
|
||
-iname '*.png' -o \
|
||
-iname '*.jpg' \) ); do
|
||
|
||
OUTPUT_COVER="$OUTPUT/${OUTPUT_ALBUM:+$OUTPUT_ALBUM - }Cover"
|
||
OUTPUT_COVERFILE="$OUTPUT_COVER.${i##*.}"
|
||
COVER_COUNTER=1
|
||
|
||
## Same cover is already in target folder.
|
||
if [ -e "$OUTPUT_COVERFILE" ] && \
|
||
[ "$(sha1sum $OUTPUT_COVERFILE | cut -f1 -d' ')" = "$(sha1sum $i | cut -f1 -d' ')" ]; then
|
||
continue
|
||
fi
|
||
|
||
## Different cover with same name is in target folder. We append a number.
|
||
while [ -e "$OUTPUT_COVERFILE" ] && \
|
||
[ ! "$(sha1sum $OUTPUT_COVERFILE | cut -f1 -d' ')" = "$(sha1sum $i | cut -f1 -d' ')" ]; do
|
||
OUTPUT_COVERFILE="${OUTPUT_COVER} $COVER_COUNTER.${i##*.}"
|
||
COVER_COUNTER=$(($COVER_COUNTER+1))
|
||
done
|
||
|
||
echo "==> COVER"
|
||
cp -nv "$i" "$OUTPUT_COVERFILE"
|
||
echo
|
||
done
|
||
IFS=$OLDIFS
|
||
unset OLDIFS
|
||
|
||
## Zsh compatibility. We need it otherwise word splitting of parameter like
|
||
## OGG_PARAM will not work.
|
||
local STATUS
|
||
STATUS="$(set -o | grep 'shwordsplit' | awk '{print $2}')"
|
||
[ "$STATUS" = "off" ] && set -o shwordsplit
|
||
|
||
## TAG/RECODE
|
||
ffmpeg -i "$1" $OGG_PARAM \
|
||
-metadata title="$OUTPUT_TITLE" \
|
||
-metadata artist="$OUTPUT_ARTIST" \
|
||
-metadata track="$OUTPUT_TRACK" \
|
||
-metadata date="$OUTPUT_DATE" \
|
||
-metadata album="$OUTPUT_ALBUM" \
|
||
-metadata album_artist="$OUTPUT_ARTIST" \
|
||
-metadata genre="$OUTPUT_GENRE" \
|
||
-metadata composer="" \
|
||
-metadata TYER="" \
|
||
-metadata disc="" \
|
||
"$OUTPUT_FILE.$OUTPUT_FORMAT"
|
||
|
||
## Restore Zsh previous options. This will not turn off shwordsplit if it
|
||
## was on before calling the function.
|
||
[ "$STATUS" = "off" ] && set +o shwordsplit
|
||
}
|
||
|
||
|
||
################################################################################
|
||
## Video
|
||
|
||
## TODO: audio quality. Copy if AAC or OGG <320k.
|
||
## TODO: chapters support. Already OK?
|
||
## TODO: crop options.
|
||
## TODO: custom mapping.
|
||
## TODO: handle srt encoding.
|
||
## TODO: option to replace original file.
|
||
## TODO: work with -threads option.
|
||
|
||
## Vars
|
||
TC_VIDEO_AUDIO_QUAL=192k
|
||
|
||
## quality*size ~= 1/speed
|
||
# ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo
|
||
TC_VIDEO_PRESET=slow
|
||
|
||
## Overall quality. Logarithmic scale.
|
||
# 18 is near perfection.
|
||
# 22 is really good compression, while a bit more blurry than the original.
|
||
# 20 is a good compromise.
|
||
TC_VIDEO_QUAL=20
|
||
|
||
## x264 tuning (presets).
|
||
## Possible values: film, animation, grain, ...
|
||
## See x264 --fullhelp.
|
||
## No tuning by default.
|
||
# TC_VIDEO_TUNE="-tune film"
|
||
TC_VIDEO_TUNE=""
|
||
|
||
## x264 options.
|
||
## Possible values: hex, umh...
|
||
## me=umh is default.
|
||
# TC_VIDEO_OPTS="-x264opts me=hex"
|
||
TC_VIDEO_OPTS=""
|
||
|
||
tc_video_demux_audio()
|
||
{
|
||
ffmpeg -i "$1" -vn -sn -c:a copy -map 0:1 "$2"
|
||
}
|
||
|
||
tc_video_audio2ogg()
|
||
{
|
||
echo "================================================================================"
|
||
ffmpeg -threads 4 -i "$1" -vn -c:a libvorbis -b:a $TC_VIDEO_AUDIO_QUAL "${1%.*}-$(date '+%F-%H%M%S').ogg"
|
||
}
|
||
|
||
## TODO: separate functions.
|
||
## tc_video_batch
|
||
## tc_video_transcode
|
||
tc_video_transcode()
|
||
{
|
||
_printhelp()
|
||
{
|
||
cat <<EOF
|
||
Usage: $1 [OPTIONS] FILES|FOLDERS
|
||
|
||
Transcode FILES or files found in FOLDERS to .mkv with x264 and ogg. Output
|
||
files are the same as the original, with time appended. You can customize
|
||
encoding with the TC_* variables.
|
||
|
||
-f: Overwrite existing file if any.
|
||
-h: Display this help.
|
||
-s: Sample of ten minutes.
|
||
EOF
|
||
}
|
||
|
||
## What to do if file exists:
|
||
# -y overwrite
|
||
# -n do not overwrite.
|
||
local TC_OVERWRITE
|
||
TC_OVERWRITE=-n
|
||
|
||
local TC_SAMPLE
|
||
TC_SAMPLE=""
|
||
|
||
while getopts ":fhs" opt; do
|
||
case $opt in
|
||
f)
|
||
TC_OVERWRITE=-y
|
||
;;
|
||
h)
|
||
_printhelp "$0"
|
||
return 1
|
||
;;
|
||
s)
|
||
TC_SAMPLE="-ss 60 -t 600 -i"
|
||
;;
|
||
?)
|
||
_printhelp "$0"
|
||
return 1
|
||
;;
|
||
:)
|
||
echo "Missing argument."
|
||
echo "Use $0 -h for help."
|
||
return 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
shift $(($OPTIND - 1))
|
||
|
||
|
||
if [ $# -eq 0 ]; then
|
||
tc_transcode .
|
||
return
|
||
fi
|
||
|
||
## The IFS trick will let us use an array of file that can have spaces. Real arrays are much better at it, but in this case the trick is not excessive, and works on all sh shells.
|
||
local OLDIFS
|
||
OLDIFS=$IFS
|
||
|
||
## Zsh compatibility. We need it otherwise word splitting of parameter like
|
||
## TC_SAMPLE will not work.
|
||
local STATUS
|
||
STATUS="$(set -o | grep 'shwordsplit' | awk '{print $2}')"
|
||
[ "$STATUS" = "off" ] && set -o shwordsplit
|
||
|
||
_tc_transcode()
|
||
{
|
||
local IFS
|
||
IFS=$OLDIFS
|
||
|
||
echo "================================================================================"
|
||
ffmpeg $TC_OVERWRITE $TC_SAMPLE -i "$1" \
|
||
-c:v libx264 -preset $TC_VIDEO_PRESET -crf $TC_VIDEO_QUAL $TC_VIDEO_TUNE $TC_VIDEO_OPTS \
|
||
-c:a libvorbis -b:a $TC_VIDEO_AUDIO_QUAL \
|
||
-c:s copy \
|
||
-map 0 "${1%.*}-$(date '+%F-%H%M%S').mkv"
|
||
}
|
||
|
||
for i in "$@"; do
|
||
|
||
## Argument is a folder. We search for all video files in there.
|
||
if [ -d "$i" ]; then
|
||
|
||
local IFS
|
||
IFS="
|
||
"
|
||
|
||
## TODO: provide max-depth as option.
|
||
for j in $(find "$i" \( \
|
||
-iname '*.mkv' -o \
|
||
-iname '*.mp4' -o \
|
||
-iname '*.avi' -o \
|
||
-iname '*.webm' -o \
|
||
-iname '*.flv' -o \
|
||
-iname '*.wmv' -o \
|
||
-iname '*.mpg' \) ); do
|
||
_tc_transcode $j
|
||
done
|
||
|
||
## Argument is a regular file.
|
||
else
|
||
_tc_transcode "$i"
|
||
fi
|
||
|
||
done
|
||
|
||
## Restore Zsh previous options. This will not turn off shwordsplit if it
|
||
## was on before calling the function.
|
||
[ "$STATUS" = "off" ] && set +o shwordsplit
|
||
}
|
||
|
||
|
||
################################################################################
|
||
## OLD
|
||
|
||
## High in Handbrake:
|
||
# b-adapt=2:rc-lookahead=50:me=umh
|
||
|
||
## Mine in Handbrake:
|
||
# ref=1:weightp=1:subq=2:rc-lookahead=10:trellis=0:8x8dct=0
|
||
|
||
## Mine with details:
|
||
# cabac=1:ref=1:deblock=1:0:0:analyse=0x1:0x111:me=hex:subme=2:psy=1:psy_rd=1.00:0.00:mixed_ref=0:me_range=16:chroma_me=1:trellis=0:8x8dct=0:cqm=0:deadzone=21,11:fast_pskip=1:chroma_qp_offset=0:threads=6:sliced_threads=0:nr=0:decimate=1:interlaced=0:bluray_compat=0:constrained_intra=0:bframes=3:b_pyramid=2:b_adapt=1:b_bias=0:direct=1:weightb=1:open_gop=0:weightp=1:keyint=240:keyint_min=24:scenecut=40:intra_refresh=0:rc_lookahead=10:rc=crf:mbtree=1:crf=20.0:qcomp=0.60:qpmin=3:qpmax=69:qpstep=4:ip_ratio=1.40:aq=1:1.00
|
||
|
||
## Mine diff with default:
|
||
# ref=1:analyse=0x1:0x111:me=hex:subme=2:trellis=0:8x8dct=0:chroma_qp_offset=0:b_adapt=1:direct=1:weightp=1:keyint=240:rc_lookahead=10
|
||
|
||
|
||
## Best for bad quality movies.
|
||
# me=hex
|