SHARE
TWEET

foma multicore

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