#!/bin/sh # Written by Juliusz Chroboczek. Do whatever you want with this script. # Recode wav/flac/alac/whatever into flac/mp3/aac/ogg preserving tags. # If the input and output formats are identical, builds a new container # without actually recoding anything (unless -e has been used). This is # useful for converting mp4 videos to audio-only, or for reordering the # chunks in "unstreamable" m4a files. # I usually do something like: # find . -maxdepth 1 -name '*.wav' -print0 | xargs -0 -n 1 -P 2 recode-audio # Known bugs: # - there's basically no error handling; # - uses Nero's proprietary AAC encoder, since neither FAAC nor ffmpeg do # a good job encoding AAC. format=mp3 # Default output format when there's no # second argument while getopts "f:q:let" name; do case $name in f) format="$OPTARG";; # Effective when there's no second argument q) quality="$OPTARG";; # Output-format specific, see defaults below l) lc=true;; # Disable AAC-HE, for iPods e) force_encode=true;; # Disable lossless remuxing for mp4->m4a t) tags_only=true;; # Only copy tags, don't recode *) exit 1;; esac done shift $(($OPTIND - 1)) if [ $# -gt 2 ] ; then echo "Too many arguments" >&2 exit 1 fi lame_opts="--vbr-new -h -V ${quality:-3}" nero_opts="-q ${quality:-0.5} ${lc:+-lc}" ogg_opts="-q ${quality:-5}" flac_opts="" if="$1" of="${2:-${1%.*}."$format"}" format="${of##*.}" if [ "$if" = "$of" ]; then echo "Converting to self?" >&2 exit 1 fi locale | grep -q 'LC_CTYPE.*UTF-8' [ $? -eq 0 ] || echo "Warning: running in weird locale." >&2 tags="title album artist track tracknumber totaltracks disc discnumber totaldiscs genre comment year date copyright isrc label composer author tempo" # Only in mp4: track, disc, year, tempo # Only in Ogg: tracknumber, discnumber, date, performer, composer, author for tag in $tags; do eval unset $tag done # Ogg and Flac are not case-sensitive. Sigh. tolower() { while read line; do echo -n "${line%%=*}" | tr [A-Z] [a-z] echo "=${line#*=}" done } flac_tags () { for tag in $tags; do metaflac --no-filename --show-tag="$tag" "$1" done } parse_tags() { case "$1" in *.mp3|*.mp2|*.mpg) title="$(mp3info -p %t "$1")" album="$(mp3info -p %l "$1")" artist="$(mp3info -p %a "$1")" track="$(mp3info -p %n "$1")" genre="$(mp3info -p %g "$1")" comment="$(mp3info -p %c "$1")" year="$(mp3info -p %y "$1")" copyright="$(mp3info -p %C "$1")" ;; *.ogg) eval "$(vorbiscomment -l "$1" | tolower | \ sed -e "s/'/'\\\\''/" -e "s/=/='/" -e "s/$/'/")" ;; *.flac) eval "$(flac_tags "$1" | tolower | \ sed -e "s/'/'\\\\''/" -e "s/=/='/" -e "s/$/'/")" ;; *.mp4|*.m4a) eval "$(neroAacTag -list-meta "$1" 2>&1 | \ sed -n -e '1,/Metadata list:/!p' | \ sed -n -e '/End of metadata/,$!p' | \ sed -n -e '/=/p' | \ sed -e 's/^[ ]*//' \ -e "s/'/'\\\\''/" \ -e "s/ = /='/" -e "s/$/'/")" ;; # APE? *) echo "Warning: cannot get tags of $1" >&2 ;; esac # Convert mp4 to Ogg syntax [ -z "$tracknumber" ] && tracknumber="$track" [ -z "$discnumber" ] && discnumber="$disc" [ -z "$date" ] && date="$year" # Convert Ogg to mp4 syntax [ -z "$track" ] && track="$tracknumber" [ -z "$disc" ] && disc="$discnumber" [ -z "$year" ] && year="$(echo $date | cut -b 1-4)" } decoder() { case "$1" in *.flac) if [ -z "$2" ] ; then flac -s -dc -- "$1" else flac -s -d -o "$2" -- "$1" fi ;; *.ogg) oggdec -Q -o "${2:--}" -- "$1" ;; *.mp4|*.m4a) neroAacDec -if "$1" -of "${2:-/dev/stdout}" ;; *) ffmpeg -i "$1" -vn -f wav -y "${2:--}" ;; esac } # Second argument specifies whether we use Ogg tags all_tags() { for tag in $tags; do eval value=\$$tag quotedval="$(echo "$value" | sed "s/'/'\\\\''/g")" if $2 ; then case "$tag" in track|disc|year|tempo) value='' ;; esac else case "$tag" in tracknumber|discnumber|date|performer|composer|author) value='' ;; esac fi [ -z "$value" ] || echo $1\'$tag=$quotedval\' done } set_tags() { quoted="'$(echo "$1" | sed "s/'/'\\\\''/g")'" case "$1" in *.mp4|*.m4a) eval neroAacTag $(all_tags -meta: false) "$quoted" ;; *.ogg) eval vorbiscomment $(all_tags "-t " true) "$quoted" ;; *.flac) eval metaflac $(all_tags "--set-tag=" true) "$quoted" ;; *) echo "Warning: cannot set tags of $1" &>2 ;; esac } encoder() { quoted="'$(echo "$1" | sed "s/'/'\\\\''/g")'" case "$1" in *.flac) eval flac $flac_opts -s $(all_tags '-T ' true) -o \'"$1"\' - ;; *.ogg) eval oggenc -Q $(all_tags '--comment ' true) $ogg_opts -o "$quoted" - ;; *.mp3) lame --quiet $lame_opts \ --tt "$title" \ --tn "$track${totaltracks:+/$totaltracks}" \ --tg "$genre" \ --ty "$year" \ --ta "$artist" \ --tl "$album" \ - "$1" ;; *.mp4|*.m4a) neroAacEnc -ignorelength $nero_opts -if - -of "$1" set_tags "$1" ;; *) echo "Unknown output file format" >&2; exit 1 ;; esac } parse_tags "$if" if [ ! -z "$tags_only" ]; then set_tags "$of" exit fi if [ -z "$force_encode" ]; then case "$if-$of" in *.mp4-*.mp4|*.mp4-*.m4a|*.m4a-*.mp4|*.m4a-*.m4a|*.flv-*.mp4|*.flv-*.m4a) f="$(mktemp /tmp/XXXXXX.mp4)" ffmpeg -i "$if" -vn -acodec copy -y "$f" -map_meta_data 0:0 qt-faststart "$f" "$of" rm "$f" exit ;; *.mp3-*.mp3|*.mpg-*.mp3|*.ogg-*.ogg|*.flac-*.flac) exec ffmpeg -i "$if" -vn -acodec copy "$of" -map_meta_data 0:0 ;; esac fi case "$if-$of" in *.wav-*.ogg|*.flac-*.ogg) exec oggenc -Q $ogg_opts -o "$of" "$if" ;; *.wav-*.flac|*.flac-*.flac) exec flac $flac_opts -s -o "$of" "$if" ;; *.*-*.wav) decoder "$if" "$of" ;; *.wav-*.*) encoder "$of" < "$if" ;; *) decoder "$if" | encoder "$of" ;; esac