SHARE
TWEET

foma - [f]lac [o]gg [m]p3 [a]ac

julian_hughes May 25th, 2012 116 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/bash
  2. #
  3. #  foma - [f]lac [o]gg [m]p3 [a]ac
  4. #  Copyright (C) 2012 Julian Hughes julianhughes<at>gmailDOTcom
  5. #
  6. #  This program is free software; you can redistribute it and/or modify
  7. #  it under the terms of the GNU General Public License as published by
  8. #  the Free Software Foundation; either version 3 of the License, or
  9. #  (at your option) any later version.
  10. #
  11. #  This program is distributed in the hope that it will be useful,
  12. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. #  GNU General Public License for more details.
  15. #
  16. #  You should have received a copy of the GNU General Public License
  17. #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. # foma decodes using ffmpeg so can decode the audio of any audio or
  20. # video file that (your installed version of) ffmpeg supports.  It then
  21. # uses lame, oggenc, neroAacEnc/faac, flac to encode mp3, ogg, m4a or
  22. # flac respectively.
  23. #
  24. # Dependencies:
  25. # bash and usual gnu utils, ffmpeg, lame, mid3v2 or id3v2, flac,
  26. # vorbis-tools, neroAacEnc and neroAacTag (or faac and mp4tags), mp3gain
  27. # aacgain. mid3v2 is part of python-mutagen and is preferred over id3v2
  28. # because it supports unicode and doesn't mangle your metadata. mp4tags
  29. # is part of mp4v2-utils.
  30.  
  31. function usage ()
  32. {
  33. printf "\n\t$(basename "$0") [options] {-o out} <file directory file\
  34. file...>\n
  35. \t$(basename "$0") accepts a mix of files and directories in the same
  36. \tcommand.
  37. \n\tdefault is to encode to ogg vorbis -q 4 in input directory.
  38. \nOptions:
  39. \t-o <output directory>\n
  40. \t-a  convert to aac in m4a container - uses FAAC.\n
  41. \t-A  convert to aac in m4a container - uses neroAacEnc.\n
  42. \t-f  convert to flac.\n
  43. \t-m  convert to mp3 - uses LAME.\n
  44. \t-q # set quality (-q # uses each encoder's native settings).
  45. \t   Defaults to -q 4 (lame/vorbis) -q 0.5 (nero) -q 160 (faac)
  46. \t   and -5 (flac).
  47. \t   For neroaac your setting -q 3 is interpreted as -q 0.3
  48. \t   For faac your setting -q 3 is interpreted as -q 130
  49. \t   For flac your setting -q 3 is interpreted as -3\n
  50. \t-l convert to ogg mono 22050 Hz 64kbps VBR\n
  51. If input argument is a directory then $(basename "$0") will create an \
  52. identically\nnamed directory in the output directory and copy over cov\
  53. er art and\ncreate an extended m3u playlist. In this case you can also \
  54. set:\n\n\t-g run replaygain on the output directory.
  55. \t-M run \"mpc update\" after directory conversion.\n
  56. If converting a directory with no output specified then the new
  57. directory will be nested within the original directory.\n
  58. You can choose to convert multiple files, and multiple directories
  59. complete with playlists and images, all in the same run.\n
  60. $(basename "$0") copies metadata from the input files to the output fil\
  61. es, except\nfor replaygain tags: this is deliberate, because lossy comp\
  62. ression\nraises levels and the replaygain data is no longer accurate.
  63. If converting directories you can then use -g to generate accurate
  64. replaygain data on your new files.\n
  65. $(basename "$0") doesn't overwrite your audio or image files.  If a \
  66. target file\nalready exists the conversion is skipped and this is logge\
  67. d to\n~/.foma.log\n\n"
  68. }
  69. function qcheck ()
  70. {
  71.         if [ $qflag ]; then
  72.                 if [[ ! $qval =~ ^[0-9]$ ]]; then
  73.                         printf "\n\n\tYour quality setting -q $qval makes no sense\
  74. \n\n\trun $(basename $0) with no options or arguments for \
  75. usage\n\n"
  76.                         exit 1
  77.                 fi
  78.         fi
  79. }
  80. #ignore file types which don't contain audio. ignore case.
  81. function check_input ()
  82. {
  83. shopt -s extglob nocasematch
  84. if [[ "$TRACK" == !(*.flac|*.ape|*.wv|*.wav|*.wma|*.m4a|*.alac|*.ogg|\
  85. *.oga|*.mp3|*.mp2|*.mp4|*.m4b|*.mpc|*.avi|*.mkv|*.mka|*.mov|*.flv|*.shn\
  86. |*.ogv|*.asf|*.wmv|*.aac|*.dts|*.ac3|*.ts|*.vob) ]]
  87. then
  88. continue
  89. fi
  90. }
  91. #prevent overwriting of audio files; log files skipped. create output
  92. #dir as necessary.
  93. function check_output ()
  94. {
  95.         if [ -f "$ONAME"/"$TRACKOUT""$EXTN" ]; then
  96.         printf "\n$(date +%F-%R) $ONAME/$TRACKOUT$EXTN\nalready exists.\
  97.         Conversion skipped.\n"|tee -a ~/foma.log
  98.         continue
  99.         fi
  100.         if [ ! -d "$ONAME" ] ; then
  101.                 mkdir "$ONAME"
  102.         fi
  103. }
  104.  
  105. function gain ()
  106. {
  107.         if [ -z $gflag ]; then
  108.                 GAIN=""
  109.                 else
  110.                 $GAIN
  111.         fi
  112. }
  113.  
  114. #decoder: decode to wav, dump ffmpeg stderr (includes metadata) to file
  115. function ffdecode ()
  116. {
  117.         ffmpeg -i "$TRACK" -vn -acodec pcm_s16le -f wav - 2>"$TAG"
  118. }
  119.  
  120. #encoders for ogg,mp3,aac,flac
  121. function encvorbis ()
  122. {
  123.         oggenc --ignorelength -q ${qval:-4} - -o "$OUT"
  124. }
  125. #this one for audiobooks, speech etc.
  126. function encvorbislow ()
  127. {
  128.         oggenc --ignorelength --downmix --resample 22050 -b 64 - -o "$OUT"
  129. }
  130. function encmp3 ()
  131. {
  132.         lame -V ${qval:-4} - "$OUT"
  133. }
  134. #proprietary binary only, doesn't manage true gapless but otherwise
  135. #produces very high quality even at low bitrates.switches automatically
  136. #between LC and HE output.
  137. function encaac ()
  138. {
  139.         neroAacEnc -ignorelength -lc -q 0.${qval:-5} -if - -of "$OUT"
  140. }
  141. #faac is free software. quality at bitrates >128k is ok, but at low
  142. #bitrates is poor, not supporting HE encoding. But it produces files
  143. #with perfect gapless playback. Keep quality >150 for very good results.
  144. function encfaac ()
  145. {
  146.         faac -s -q 1"${qval:-6}"0 -o "$OUT" -
  147. }
  148. function encflac ()
  149. {
  150.         flac -${qval:-5} -s --ignore-chunk-sizes -o "$OUT" - ;
  151. }
  152.  
  153. function roundup () {
  154. NUM=$@
  155. bc << EOF
  156. num = $NUM;
  157. base = num / 1;
  158. if (((num - base) * 10) > 0 )
  159.     base += 1;
  160. print base;
  161. EOF
  162. echo ""
  163. }
  164. #cleantag: awk expr prints ALL fields after delimiter, then
  165. #sed removes characters that have unwanted effects if used in tags or on
  166. #some OS or file systems.
  167. function cleantag ()
  168. {
  169.         awk -F ": " '{$1="";$0=substr($0,2)}1'|\
  170.         sed 's/[;]/-/g;s/[/]/-/g;s/[<]/-/g;s/[>]/-/g;s/[:]/-/g;s/[|]/-/g'
  171. }
  172. #grep file for metadata and clean up names. find track duration in secs.
  173. #awk converts ffmpeg's hh:mm:ss.ms time to seconds; printf and bc round
  174. #it UP to integer.
  175. #extm3u spec states time MUST be equal to or greater than track duration
  176. #so times are rounded UP to integer i.e. 138.00 secs becomes 138 seconds
  177. #but 138.10 secs or 138.95 seconds become 139 seconds.
  178. function getmeta ()
  179. {
  180.         ARTIST=$(grep -m 1 -i -e "\s   ARTIST" "$TAG"|cleantag)
  181.         TITLE=$(grep -m 1 -i -e "\s   TITLE" "$TAG"|cleantag)
  182.         ALBUM=$(grep -m 1 -i -e "\s   ALBUM" "$TAG"|cleantag)
  183.         GENRE=$(grep -m 1 -i -e "\s   GENRE" "$TAG"|cleantag)
  184.         TOT=$(grep -m 1 -i -e "\s   TRACKTOTAL" "$TAG"|cleantag)
  185.         DATE=$(grep -m 1 -i -e "\s   DATE" "$TAG"|cleantag)
  186.         TRN=$(grep -m 1 -i -e "\s   TRACK" "$TAG"|cleantag)
  187.         COMPOSER=$(grep -m 1 -i -e "\s   COMPOSER" "$TAG"|cleantag)
  188.         COMMENT=$(grep -m 1 -i -e "\s   COMMENT" "$TAG"|cleantag)
  189.         DISC=$(grep -m 1 -i -e "\s   DISC" "$TAG"|cleantag)
  190.         SEC=$(grep -m 1 -e Duration "$TAG"|awk -F ": " '{print $2}'|\
  191.         awk -F ", " '{print $1}'|\
  192.         awk '{ split($1, A, ":"); print 3600*A[1] + 60*A[2] + A[3] }')
  193.         SEC=$(roundup $SEC)
  194. }
  195.  
  196. #for mp3 tagging prefer mid3v2 if in path, else use id3v2
  197. function checkid3 ()
  198. {
  199.         type -P mid3v2 >/dev/null 2>&1
  200.         if [ $? -eq 0 ] ; then
  201.                 METAMP3=mid3v2
  202.                 else
  203.                 METAMP3=id3v2
  204.         fi
  205. }
  206. #taggers for various formats
  207. function vorbismeta ()
  208. {
  209.         vorbiscomment -a -t artist="$ARTIST" -t title="$TITLE" \
  210.         -t album="$ALBUM" -t genre="$GENRE" -t date="$DATE" -t \
  211.         tracknumber="$TRN" -t composer="$COMPOSER" -t comment="$COMMENT"\
  212.         "$OUT"
  213. }
  214. function mp3meta ()
  215. {
  216.         $METAMP3 --artist "$ARTIST" --song "$TITLE" --album "$ALBUM" \
  217.         --genre "$GENRE" --year "$DATE" --track "$TRN"/"$TOT" --TCOM \
  218.         "$COMPOSER" --comment Comment:"$COMMENT" --TPOS "$DISC" "$OUT" ;
  219. }
  220. #proprietary binary only:
  221. function Aacmeta ()
  222. {
  223.         neroAacTag "$OUT" -meta:artist="$ARTIST" \
  224.         -meta:title="$TITLE" -meta:album="$ALBUM" -meta:genre="$GENRE" \
  225.         -meta:year="$DATE" -meta:track="$TRN" -meta:totaltracks="$TOT" \
  226.         -meta:Composer="$COMPOSER" -meta:comment="$COMMENT" \
  227.         -meta:disc="$DISC"
  228. }
  229. #mp4tags is free software:
  230. function aacmeta ()
  231. {
  232.         mp4tags  -artist "$ARTIST" -song "$TITLE" -album "$ALBUM" \
  233.         -genre "$GENRE" -year "$DATE" -track ${TRN:-0} -T ${TOT:-0} -w \
  234.         "$COMPOSER" -comment "$COMMENT" -d ${DISC:-1} "$OUT"
  235. }
  236. function flacmeta ()
  237. {
  238.         metaflac --set-tag=artist="$ARTIST" --set-tag=title="$TITLE" \
  239.         --set-tag=album="$ALBUM" --set-tag=genre="$GENRE" \
  240.         --set-tag=date="$DATE" --set-tag=tracknumber="$TRN" \
  241.         --set-tag=composer="$COMPOSER" --set-tag=comment="$COMMENT"     "$OUT"
  242. }
  243.  
  244. #replaygain for various formats
  245. function rgainmp3 ()
  246. {
  247.         mp3gain -s i -a -k "$ONAME"/*.mp3
  248. }
  249. function rgainvorbis ()
  250. {
  251.         vorbisgain -a "$ONAME"/*.ogg
  252. }
  253. function rgainaac ()
  254. {
  255.         aacgain -s i -a -k "$ONAME"/*.m4a
  256. }
  257. function rgainflac ()
  258. {
  259.         metaflac --add-replay-gain "$ONAME"/*.flac
  260. }
  261.  
  262. #copy any image file from root of source directory to target directory.
  263. #don't overwrite.
  264. function copyart ()
  265. {
  266.         find "$i" -maxdepth 1 -type f \
  267.         -iregex ".*\(jpg\|jpeg\|png\|bmp\|tif\|tiff\|gif\)$" -exec \
  268.         cp -n "{}" "$ONAME" \;
  269. }
  270.  
  271. function mpcupdate ()
  272. {
  273.         mpc -q update
  274. }
  275.  
  276. function convert_dirs ()
  277. {
  278.         qcheck
  279.         INAME=$(basename "$i")
  280.         OUTDIR=${oval:-$i}
  281.         ONAME="$OUTDIR"/"$INAME"
  282.         PL="$ONAME"/"$INAME".m3u
  283.  
  284.         for TRACK in "$i"/* ; do
  285.  
  286.         check_input
  287.  
  288.         TRACKOUT=$(basename "$TRACK")
  289.         TRACKOUT="${TRACKOUT%.*}"
  290.         OUT="$ONAME"/"$TRACKOUT""$EXTN"
  291.  
  292.         check_output
  293.  
  294.         TAG=/tmp/tag
  295.         ffdecode | $ENCODE
  296.         getmeta
  297.         $SETMETA
  298.         #use metadata to write extm3u playlist
  299.         printf "\n#EXTINF:$SEC,$ARTIST - $TITLE\n$TRACKOUT$EXTN">>"$PL"
  300.         rm "$TAG"
  301.         done
  302.  
  303.         #if playlist "$PL" has been created then insert generic 1st line.
  304.         #("$PL" may not exist if input arg was directory of non-audio files)
  305.         #remove any blank line, remove any duplicate lines.
  306.         if [ -f "$PL" ]; then
  307.         sed -i '1i#EXTM3U' "$PL"
  308.         sed -i '/^$/d;$!N; /^\(.*\)\n\1$/!P; D' "$PL"
  309.         fi
  310.        
  311.         #if target directory exists then run replaygain (if set) and copy
  312.         #images (target directory "$ONAME" may not exist if input arg was
  313.         #directory of non-audio files).
  314.         if [ -d "$ONAME" ]; then
  315.         gain
  316.         copyart
  317.         fi
  318.  
  319.         if [ $Mflag ]; then
  320.         mpcupdate
  321.         fi
  322. }
  323. function convert_files ()
  324. {
  325.         qcheck
  326.         for TRACK in "$i" ; do
  327.        
  328.         check_input
  329.  
  330.         INAME=$(dirname "$TRACK")
  331.         ONAME=${oval:-"$INAME"}
  332.         TRACKOUT="${TRACK##*/}"
  333.         TRACKOUT="${TRACKOUT%.*}"
  334.         OUT="$ONAME"/"$TRACKOUT""$EXTN"
  335.  
  336.         check_output
  337.  
  338.         TAG=/tmp/tag
  339.         ffdecode | $ENCODE
  340.         getmeta
  341.         $SETMETA
  342.         rm "$TAG"
  343.         done
  344. }
  345.  
  346. while getopts 'q:o:fmMaAlg' OPTION
  347. do
  348.         case $OPTION in
  349.         q)      qflag=1
  350.                 qval=$OPTARG
  351.                 ;;
  352.         f)      fflag=1
  353.                 ;;
  354.         o)      oflag=1
  355.                 oval=$OPTARG
  356.                 ;;
  357.         m)      mflag=1
  358.                 ;;
  359.         M)      Mflag=1
  360.                 ;;
  361.         a)      aflag=1
  362.                 ;;
  363.         A)      Aflag=1
  364.                 ;;
  365.         l)      lflag=1
  366.                 ;;
  367.         g)      gflag=1
  368.                 ;;
  369.         esac
  370. done
  371. shift $(($OPTIND-1))
  372.  
  373. #set some parameters according to user choice
  374. if [ $mflag ]; then
  375.         ENCODE=encmp3
  376.         checkid3
  377.         EXTN=.mp3
  378.         SETMETA=mp3meta
  379.         GAIN=rgainmp3
  380. elif [ $aflag ]; then
  381.         ENCODE=encfaac
  382.         EXTN=.m4a
  383.         SETMETA=aacmeta
  384.         GAIN=rgainaac
  385. elif [ $Aflag ]; then
  386.         ENCODE=encaac
  387.         EXTN=.m4a
  388.         SETMETA=Aacmeta
  389.         GAIN=rgainaac
  390. elif [ $fflag ]; then
  391.         ENCODE=encflac
  392.         EXTN=.flac
  393.         SETMETA=flacmeta
  394.         GAIN=rgainflac
  395. elif [ $lflag ]; then
  396.         ENCODE=encvorbislow
  397.         EXTN=.ogg
  398.         SETMETA=vorbismeta
  399.         GAIN=rgainvorbis
  400. else
  401.         ENCODE=encvorbis
  402.         EXTN=.ogg
  403.         SETMETA=vorbismeta
  404.         GAIN=rgainvorbis
  405. fi
  406.  
  407. if [ $# -lt 1 ]; then
  408.         usage
  409.         exit 1
  410.         else
  411.         for i in "$@" ; do
  412.         if [ -d "$i" ] ; then
  413.                 convert_dirs
  414.         else
  415.                 convert_files
  416.         fi
  417.         done
  418. fi
  419. exit 0
RAW Paste Data
Want to get better at Bash?
Learn to code Bash in 2017
Pastebin PRO Summer Special!
Get 40% OFF on Pastebin PRO accounts!
Top