Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- #
- # foma - [f]lac [o]gg [m]p3 [a]ac
- # Copyright (C) 2012 Julian Hughes julianhughes<at>gmailDOTcom
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- # foma decodes using ffmpeg so can decode the audio of any audio or
- # video file that (your installed version of) ffmpeg supports. It then
- # uses lame, oggenc, neroAacEnc/faac, flac to encode mp3, ogg, m4a or
- # flac respectively.
- #
- # Dependencies:
- # bash and usual gnu utils, ffmpeg, lame, mid3v2 or id3v2, flac,
- # vorbis-tools, neroAacEnc and neroAacTag (or faac and mp4tags), mp3gain
- # aacgain. mid3v2 is part of python-mutagen and is preferred over id3v2
- # because it supports unicode and doesn't mangle your metadata. mp4tags
- # is part of mp4v2-utils.
- function usage ()
- {
- printf "\n\t$(basename "$0") [options] {-o out} <file directory file\
- file...>\n
- \t$(basename "$0") accepts a mix of files and directories in the same
- \tcommand.
- \n\tdefault is to encode to ogg vorbis -q 4 in input directory.
- \n\t$(basename "$0") runs 5 parallel decode/encode tasks for great
- \tperformance on dual/multicore CPU systems. To change the\n\tnumber\
- \tof parallel tasks just edit the line \"PROCESSES=5\"
- \treplacing 5 with your preferred value (minimum 2).
- \tTry starting with number of logical cores +1.
- \nOptions:
- \t-o <output directory>\n
- \t-a convert to aac in m4a container - uses FAAC.\n
- \t-A convert to aac in m4a container - uses neroAacEnc.\n
- \t-f convert to flac.\n
- \t-m convert to mp3 - uses LAME.\n
- \t-q # set quality (-q # uses each encoder's native settings).
- \t Defaults to -q 4 (lame/vorbis) -q 0.5 (nero) -q 160 (faac)
- \t and -5 (flac).
- \t For neroaac your setting -q 3 is interpreted as -q 0.3
- \t For faac your setting -q 3 is interpreted as -q 130
- \t For flac your setting -q 3 is interpreted as -3\n
- \t-l convert to ogg mono 22050 Hz 64kbps VBR\n
- If input argument is a directory then $(basename "$0") will create an \
- identically\nnamed directory in the output directory and copy over cov\
- er art and\ncreate an extended m3u playlist. In this case you can also \
- set:\n\n\t-g run replaygain on the output directory.
- \t-M run \"mpc update\" after directory conversion.\n
- If converting a directory with no output specified then the new
- directory will be nested within the original directory.\n
- You can choose to convert multiple files, and multiple directories
- complete with playlists and images, all in the same run.\n
- $(basename "$0") copies metadata from the input files to the output fil\
- es, except\nfor replaygain tags: this is deliberate, because lossy comp\
- ression\nraises levels and the replaygain data is no longer accurate.
- If converting directories you can then use -g to generate accurate
- replaygain data on your new files.\n
- $(basename "$0") doesn't overwrite your audio or image files. If a \
- target file\nalready exists the conversion is skipped and this is logge\
- d to\n~/.foma.log\n\n"
- }
- PROCESSES=5
- #ignore file types which don't contain audio. ignore case.
- function check_input ()
- {
- shopt -s extglob nocasematch
- if [[ "$TRACK" == !(*.flac|*.ape|*.wv|*.wav|*.wma|*.m4a|*.alac|*.ogg|\
- *.oga|*.mp3|*.mp2|*.mp4|*.m4b|*.mpc|*.avi|*.mkv|*.mka|*.mov|*.flv|*.shn\
- |*.ogv|*.asf|*.wmv|*.aac|*.dts|*.ac3|*.ts|*.vob) ]]
- then
- continue
- fi
- }
- #prevent overwriting of audio files; log files skipped. create output
- #dir as necessary.
- function check_output ()
- {
- if [ -f "$ONAME"/"$TRACKOUT""$EXTN" ]; then
- printf "\n$(date +%F-%R) $ONAME/$TRACKOUT$EXTN\nalready exists.\
- Conversion skipped.\n"|tee -a ~/foma.log
- continue
- fi
- if [ ! -d "$ONAME" ] ; then
- mkdir "$ONAME"
- fi
- }
- function gain ()
- {
- if [ -z $gflag ]; then
- GAIN=""
- else
- $GAIN
- fi
- }
- #decoder: decode to wav, dump ffmpeg stderr (includes metadata) to file
- function ffdecode ()
- {
- ffmpeg -i "$TRACK" -vn -acodec pcm_s16le -f wav - 2>"$TAG"
- }
- #encoders for ogg,mp3,aac,flac
- function encvorbis ()
- {
- oggenc --ignorelength -q ${qval:-4} - -o "$OUT"
- }
- #this one for audiobooks, speech etc.
- function encvorbislow ()
- {
- oggenc --ignorelength --downmix --resample 22050 -b 64 - -o "$OUT"
- }
- function encmp3 ()
- {
- lame -V ${qval:-4} - "$OUT"
- }
- #proprietary binary only, doesn't manage true gapless but otherwise
- #produces very high quality even at low bitrates.switches automatically
- #between LC and HE output.
- function encaac ()
- {
- neroAacEnc -ignorelength -lc -q 0.${qval:-5} -if - -of "$OUT"
- }
- #faac is free software. quality at bitrates >128k is ok, but at low
- #bitrates is poor, not supporting HE encoding. But it produces files
- #with perfect gapless playback. Keep quality >150 for very good results.
- function encfaac ()
- {
- faac -s -q 1"${qval:-6}"0 -o "$OUT" -
- }
- function encflac ()
- {
- flac -${qval:-5} -s --ignore-chunk-sizes -o "$OUT" - ;
- }
- #cleantag: awk expr prints ALL fields after delimiter, then
- #sed removes characters that have unwanted effects if used in tags or on
- #some OS or file systems.
- function cleantag ()
- {
- awk -F ": " '{$1="";$0=substr($0,2)}1'|\
- sed 's/[;]/-/g;s/[/]/-/g;s/[<]/-/g;s/[>]/-/g;s/[:]/-/g;s/[|]/-/g'
- }
- #grep file for metadata and clean up names.
- function getmeta ()
- {
- ARTIST=$(grep -m 1 -i -e "\s ARTIST" "$TAG"|cleantag)
- TITLE=$(grep -m 1 -i -e "\s TITLE" "$TAG"|cleantag)
- ALBUM=$(grep -m 1 -i -e "\s ALBUM" "$TAG"|cleantag)
- GENRE=$(grep -m 1 -i -e "\s GENRE" "$TAG"|cleantag)
- TOT=$(grep -m 1 -i -e "\s TRACKTOTAL" "$TAG"|cleantag)
- DATE=$(grep -m 1 -i -e "\s DATE" "$TAG"|cleantag)
- TRN=$(grep -m 1 -i -e "\s TRACK" "$TAG"|cleantag)
- COMPOSER=$(grep -m 1 -i -e "\s COMPOSER" "$TAG"|cleantag)
- COMMENT=$(grep -m 1 -i -e "\s COMMENT" "$TAG"|cleantag)
- DISC=$(grep -m 1 -i -e "\s DISC" "$TAG"|cleantag)
- }
- #for mp3 tagging prefer mid3v2 if in path, else use id3v2
- function checkid3 ()
- {
- type -P mid3v2 >/dev/null 2>&1
- if [ $? -eq 0 ] ; then
- METAMP3=mid3v2
- else
- METAMP3=id3v2
- fi
- }
- #taggers for various formats
- function vorbismeta ()
- {
- vorbiscomment -a -t artist="$ARTIST" -t title="$TITLE" \
- -t album="$ALBUM" -t genre="$GENRE" -t date="$DATE" -t \
- tracknumber="$TRN" -t composer="$COMPOSER" -t comment="$COMMENT"\
- "$OUT"
- }
- function mp3meta ()
- {
- $METAMP3 --artist "$ARTIST" --song "$TITLE" --album "$ALBUM" \
- --genre "$GENRE" --year "$DATE" --track "$TRN"/"$TOT" --TCOM \
- "$COMPOSER" --comment Comment:"$COMMENT" --TPOS "$DISC" "$OUT" ;
- }
- #proprietary binary only:
- function Aacmeta ()
- {
- neroAacTag "$OUT" -meta:artist="$ARTIST" \
- -meta:title="$TITLE" -meta:album="$ALBUM" -meta:genre="$GENRE" \
- -meta:year="$DATE" -meta:track="$TRN" -meta:totaltracks="$TOT" \
- -meta:Composer="$COMPOSER" -meta:comment="$COMMENT" \
- -meta:disc="$DISC"
- }
- #mp4tags is free software:
- function aacmeta ()
- {
- mp4tags -artist "$ARTIST" -song "$TITLE" -album "$ALBUM" \
- -genre "$GENRE" -year "$DATE" -track ${TRN:-0} -T ${TOT:-0} -w \
- "$COMPOSER" -comment "$COMMENT" -d ${DISC:-1} "$OUT"
- }
- function flacmeta ()
- {
- metaflac --set-tag=artist="$ARTIST" --set-tag=title="$TITLE" \
- --set-tag=album="$ALBUM" --set-tag=genre="$GENRE" \
- --set-tag=date="$DATE" --set-tag=tracknumber="$TRN" \
- --set-tag=composer="$COMPOSER" --set-tag=comment="$COMMENT" "$OUT"
- }
- #replaygain for various formats
- function rgainmp3 ()
- {
- mp3gain -s i -a -k "$ONAME"/*.mp3
- }
- function rgainvorbis ()
- {
- vorbisgain -a "$ONAME"/*.ogg
- }
- function rgainaac ()
- {
- aacgain -s i -a -k "$ONAME"/*.m4a
- }
- function rgainflac ()
- {
- metaflac --add-replay-gain "$ONAME"/*.flac
- }
- #extract duration and metadata from new files and write extended m3u.
- #extm3u spec states that stated time MUST be equal to or greater than
- #track duration so FP times are rounded UP to integer i.e. 38.00 secs
- #becomes 38 seconds but 38.10 secs or 38.95 seconds become 39 seconds.
- function roundup () {
- NUM=$@
- bc << EOF
- num = $NUM;
- base = num / 1;
- if (((num - base) * 10) > 0 )
- base += 1;
- print base;
- EOF
- echo ""
- }
- function playlist ()
- {
- PL="$ONAME"/"$INAME".m3u
- printf "#EXTM3U">"$PL"
- REGEX=".*\(m4a\|mp3\|ogg\|flac\)$"
- find "$ONAME" -iregex "$REGEX" |sort |while read i ; do
- BASENAME="${i##*/}"
- EXT="${i##*\.}"
- DATA=/tmp/"${BASENAME%.*}"_tag
- ffprobe -show_format -show_streams "$i">"$DATA"
- PERF=$(grep -m 1 -i ARTIST "$DATA"|awk -F "=" '{$1="";$0=substr($0,2)}1')
- TIT=$(grep -m 1 -i TITLE "$DATA"|awk -F "=" '{$1="";$0=substr($0,2)}1')
- SEC=$(printf %.2f\\n $(grep -m 1 -i -e "^duration" "$DATA"|awk -F "=" '{print $2}'))
- SEC=$(roundup $SEC)
- printf "\n#EXTINF:$SEC,$PERF - $TIT\n$BASENAME">>"$PL"
- rm "$DATA"
- done
- }
- #copy any image file from root of source directory to target directory.
- #don't overwrite.
- function copyart ()
- {
- find "$i" -maxdepth 1 -type f \
- -iregex ".*\(jpg\|jpeg\|png\|bmp\|tif\|tiff\|gif\)$" -exec \
- cp -n "{}" "$ONAME" \;
- }
- function convert_dirs ()
- {
- function convert_dirfiles ()
- {
- check_input
- TRACKOUT=$(basename "$TRACK")
- TRACKOUT="${TRACKOUT%.*}"
- OUT="$ONAME"/"$TRACKOUT""$EXTN"
- check_output
- TAG=/tmp/"${TRACK##*/}"_tag
- ffdecode | $ENCODE
- getmeta
- $SETMETA
- rm "$TAG"
- }
- function finaltasks ()
- {
- #if target directory exists then run replaygain (if set) and
- #copy images (target directory "$ONAME" may not exist if input
- #arg was directory of non-audio files).
- if [ -d "$ONAME" ]; then
- gain
- copyart
- fi
- if [ $Mflag ]; then
- mpc -q update
- fi
- }
- INAME=$(basename "$i")
- OUTDIR=${oval:-$i}
- ONAME="$OUTDIR"/"$INAME"
- for TRACK in "$i"/* ; do
- while [ $(jobs -p | wc -l) -ge $PROCESSES ] ; do sleep 1 ; done
- convert_dirfiles &
- done
- wait
- playlist
- finaltasks
- }
- function convert_files ()
- {
- for TRACK in "$i" ; do
- check_input
- INAME=$(dirname "$TRACK")
- ONAME=${oval:-"$INAME"}
- TRACKOUT="${TRACK##*/}"
- TRACKOUT="${TRACKOUT%.*}"
- OUT="$ONAME"/"$TRACKOUT""$EXTN"
- check_output
- TAG=/tmp/"${TRACKOUT%.*}"_tag
- ffdecode | $ENCODE
- getmeta
- $SETMETA
- rm "$TAG"
- done
- }
- while getopts 'q:o:fmMaAlg' OPTION
- do
- case $OPTION in
- q) qflag=1
- qval=$OPTARG
- ;;
- f) fflag=1
- ;;
- o) oflag=1
- oval=$OPTARG
- ;;
- m) mflag=1
- ;;
- M) Mflag=1
- ;;
- a) aflag=1
- ;;
- A) Aflag=1
- ;;
- l) lflag=1
- ;;
- g) gflag=1
- ;;
- esac
- done
- shift $(($OPTIND-1))
- #set some parameters according to user choice
- if [ $mflag ]; then
- ENCODE=encmp3
- checkid3
- EXTN=.mp3
- SETMETA=mp3meta
- GAIN=rgainmp3
- elif [ $aflag ]; then
- ENCODE=encfaac
- EXTN=.m4a
- SETMETA=aacmeta
- GAIN=rgainaac
- elif [ $Aflag ]; then
- ENCODE=encaac
- EXTN=.m4a
- SETMETA=Aacmeta
- GAIN=rgainaac
- elif [ $fflag ]; then
- ENCODE=encflac
- EXTN=.flac
- SETMETA=flacmeta
- GAIN=rgainflac
- elif [ $lflag ]; then
- ENCODE=encvorbislow
- EXTN=.ogg
- SETMETA=vorbismeta
- GAIN=rgainvorbis
- else
- ENCODE=encvorbis
- EXTN=.ogg
- SETMETA=vorbismeta
- GAIN=rgainvorbis
- fi
- if [ $# -lt 1 ]; then
- usage
- exit 1
- fi
- for i in "$@" ; do
- if [ $qflag ] && [[ ! $qval =~ ^[0-9]$ ]]; then
- printf "\n\n\tYour quality setting -q $qval makes no sense\
- \n\n\trun $(basename $0) with no options or arguments for \
- usage\n\n"
- exit 1
- fi
- if [ -d "$i" ] ; then
- convert_dirs
- else
- while [ $(jobs -p | wc -l) -ge $PROCESSES ] ; do sleep 1 ; done
- convert_files &
- fi
- done
- wait
- exit 0
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement