Any beat detection software for Linux? [closed]












28















Amarok 2 can search through music collection using ID3v2 tag's 'bpm' field. That would be very nice to retag the entire music collection so I can find the 'mood' of the track I like.



However I've not found any beat-detection software that could have helped me. Have you ever used one? CLI, preferably. Also I'm interested if there's anything alike for tagging FLACs with the same 'bpm' field.



Thanks! :)



P.S. I'm aware there's a nice moodbar feature, however it's useless for searching.










share|improve this question













closed as off-topic by Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb Dec 19 '18 at 19:39


This question appears to be off-topic. The users who voted to close gave this specific reason:


  • "Questions seeking product, service, or learning material recommendations are off-topic because they become outdated quickly and attract opinion-based answers. Instead, describe your situation and the specific problem you're trying to solve. Share your research. Here are a few suggestions on how to properly ask this type of question." – Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb

If this question can be reworded to fit the rules in the help center, please edit the question.









  • 3





    have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

    – DaveParillo
    Apr 9 '10 at 4:24











  • @DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

    – Justin Smith
    Apr 11 '10 at 7:48











  • @Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

    – kolypto
    Apr 11 '10 at 11:50











  • @Justin - sorry - twitchy trigger finger, I guess.

    – DaveParillo
    Apr 12 '10 at 4:28
















28















Amarok 2 can search through music collection using ID3v2 tag's 'bpm' field. That would be very nice to retag the entire music collection so I can find the 'mood' of the track I like.



However I've not found any beat-detection software that could have helped me. Have you ever used one? CLI, preferably. Also I'm interested if there's anything alike for tagging FLACs with the same 'bpm' field.



Thanks! :)



P.S. I'm aware there's a nice moodbar feature, however it's useless for searching.










share|improve this question













closed as off-topic by Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb Dec 19 '18 at 19:39


This question appears to be off-topic. The users who voted to close gave this specific reason:


  • "Questions seeking product, service, or learning material recommendations are off-topic because they become outdated quickly and attract opinion-based answers. Instead, describe your situation and the specific problem you're trying to solve. Share your research. Here are a few suggestions on how to properly ask this type of question." – Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb

If this question can be reworded to fit the rules in the help center, please edit the question.









  • 3





    have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

    – DaveParillo
    Apr 9 '10 at 4:24











  • @DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

    – Justin Smith
    Apr 11 '10 at 7:48











  • @Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

    – kolypto
    Apr 11 '10 at 11:50











  • @Justin - sorry - twitchy trigger finger, I guess.

    – DaveParillo
    Apr 12 '10 at 4:28














28












28








28


17






Amarok 2 can search through music collection using ID3v2 tag's 'bpm' field. That would be very nice to retag the entire music collection so I can find the 'mood' of the track I like.



However I've not found any beat-detection software that could have helped me. Have you ever used one? CLI, preferably. Also I'm interested if there's anything alike for tagging FLACs with the same 'bpm' field.



Thanks! :)



P.S. I'm aware there's a nice moodbar feature, however it's useless for searching.










share|improve this question














Amarok 2 can search through music collection using ID3v2 tag's 'bpm' field. That would be very nice to retag the entire music collection so I can find the 'mood' of the track I like.



However I've not found any beat-detection software that could have helped me. Have you ever used one? CLI, preferably. Also I'm interested if there's anything alike for tagging FLACs with the same 'bpm' field.



Thanks! :)



P.S. I'm aware there's a nice moodbar feature, however it's useless for searching.







linux mp3 music beat-detection bpm






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Apr 9 '10 at 3:01









kolyptokolypto

1,98852234




1,98852234




closed as off-topic by Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb Dec 19 '18 at 19:39


This question appears to be off-topic. The users who voted to close gave this specific reason:


  • "Questions seeking product, service, or learning material recommendations are off-topic because they become outdated quickly and attract opinion-based answers. Instead, describe your situation and the specific problem you're trying to solve. Share your research. Here are a few suggestions on how to properly ask this type of question." – Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb

If this question can be reworded to fit the rules in the help center, please edit the question.




closed as off-topic by Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb Dec 19 '18 at 19:39


This question appears to be off-topic. The users who voted to close gave this specific reason:


  • "Questions seeking product, service, or learning material recommendations are off-topic because they become outdated quickly and attract opinion-based answers. Instead, describe your situation and the specific problem you're trying to solve. Share your research. Here are a few suggestions on how to properly ask this type of question." – Kamil Maciorowski, fixer1234, Twisty Impersonator, VL-80, bertieb

If this question can be reworded to fit the rules in the help center, please edit the question.








  • 3





    have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

    – DaveParillo
    Apr 9 '10 at 4:24











  • @DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

    – Justin Smith
    Apr 11 '10 at 7:48











  • @Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

    – kolypto
    Apr 11 '10 at 11:50











  • @Justin - sorry - twitchy trigger finger, I guess.

    – DaveParillo
    Apr 12 '10 at 4:28














  • 3





    have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

    – DaveParillo
    Apr 9 '10 at 4:24











  • @DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

    – Justin Smith
    Apr 11 '10 at 7:48











  • @Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

    – kolypto
    Apr 11 '10 at 11:50











  • @Justin - sorry - twitchy trigger finger, I guess.

    – DaveParillo
    Apr 12 '10 at 4:28








3




3





have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

– DaveParillo
Apr 9 '10 at 4:24





have you seen this page? mmartins.com/mmartins/bpmdetection/bpmdetection.asp Seems exactly what you are looking for.

– DaveParillo
Apr 9 '10 at 4:24













@DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

– Justin Smith
Apr 11 '10 at 7:48





@DaveParillo that "mood of a track" link is a link to your hard disk, and as such useless to anyone but you

– Justin Smith
Apr 11 '10 at 7:48













@Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

– kolypto
Apr 11 '10 at 11:50





@Justin Smith, he meant a file in BpmDj docs :) Here's the online version: bpmdj.yellowcouch.org/clustering.html

– kolypto
Apr 11 '10 at 11:50













@Justin - sorry - twitchy trigger finger, I guess.

– DaveParillo
Apr 12 '10 at 4:28





@Justin - sorry - twitchy trigger finger, I guess.

– DaveParillo
Apr 12 '10 at 4:28










9 Answers
9






active

oldest

votes


















15














At the site DaveParillo suggested I've found BpmDj project. It has a bpmcount executable that calculates the bpm very nice: it handles mp3 as well as flac:



161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
63.5645 Doom3.mp3


The only thing that's left is to retag the collection. I'll update this answer whenever I succeed.
Thanks! :)





Step 1



Run bpmcount against the entire collection and store the results into a textfile.
The problem is that bpmcount crashes from time to time and tries to eat up to 2GB of memory when it processes several files so we should feed it with filenames one by one. Like this:



musicdir='/home/ootync/music'
find "$musicdir" -iregex ".*.(mp3|ogg|flac|ape)" -exec bpmcount {} ;
| fgrep "$musicdir" > "$musicdir/BPMs.txt"




Step 2



We'll need some additional packages: apt-get install vorbis-tools flac python-mutagen.
Now have a look at how the 'bpm' tag can be added:



mid3v2 --TBPM 100 doom3.mp3
vorbiscomment -a -t "BPM=100" mother.ogg
metaflac --set-tag="BPM=100" metallica.flac


Alas, I have no *.ape tracks



Now we have the BPMs and the entire collection should be retagged. Here's the script:



cat "$musicdir/BPMs.txt" | while read bpm file ; do
bpm=`printf "%.0f" "$bpm"` ;
case "$file" in
*.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
*.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
*.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
esac
done




Step 2.1 Revisited
Here's a script that will add BPM tags to your collection.



It runs one process per CPU Core to make the process faster. Additionally, it uses no temporary files and it capable of detecting whether a file is already tagged.



Additionally, I've discovered that FLAC sometimes has both ID3 and VorbisComment inside. This script updates both.



#!/bin/bash

function display_help() {
cat <<-HELP
Recursive BPM-writer for multicore CPUs.
It analyzes BPMs of every media file and writes a correct tag there.
Usage: $(basename "$0") path [...]
HELP
exit 0
}

[ $# -lt 1 ] && display_help

#=== Requirements
requires="bpmcount mid3v2 vorbiscomment metaflac"
which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }

#=== Functions

function bpm_read(){
local file="$1"
local ext="${file##*.}"
declare -l ext
# Detect
{ case "$ext" in
'mp3') mid3v2 -l "$file" ;;
'ogg') vorbiscomment -l "$file" ;;
'flac') metaflac --export-tags-to=- "$file" ;;
esac ; } | fgrep 'BPM=' | cut -d'=' -f2
}
function bpm_write(){
local file="$1"
local bpm="${2%%.*}"
local ext="${file##*.}"
declare -l ext
echo "BPM=$bpm @$file"
# Write
case "$ext" in
'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
'flac') metaflac --set-tag="BPM=$bpm" "$file"
mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
;;
esac
}

#=== Process
function oneThread(){
local file="$1"
#=== Check whether there's an existing BPM
local bpm=$(bpm_read "$file")
[ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
#=== Detect a new BPM
# Detect a new bpm
local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
[ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
# Write it
bpm_write "$file" "${bpm%%.*}" >/dev/null
}

NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
find $@ -type f -regextype posix-awk -iregex '.*.(mp3|ogg|flac)'
| while read file ; do
[ `jobs -p | wc -l` -ge $NUMCPU ] && wait
echo "$file"
oneThread "$file" &
done


Enjoy! :)






share|improve this answer


























  • Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

    – DaveParillo
    Apr 9 '10 at 16:24






  • 1





    Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

    – kolypto
    Apr 9 '10 at 19:51






  • 1





    Nice work on step #2. Wish I could upvote twice!

    – DaveParillo
    Apr 12 '10 at 4:31






  • 1





    Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

    – kolypto
    Apr 12 '10 at 15:36













  • How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

    – pedrosaurio
    Jul 25 '12 at 18:56



















8














This is a command-line tool to detect the BPM and put it in the FLAC file tags:



http://www.pogo.org.uk/~mark/bpm-tools/






share|improve this answer
























  • The latest version also handles mp3s and ogg vorbis.

    – encoded
    Jan 10 '13 at 20:51











  • Ubuntu has bpm-tools packages available in saucy.

    – naught101
    Apr 5 '14 at 6:44



















5














I used kolypto's original script using bpmcount and rewrote it for bpm-tag (utility of bpm-tools) which I had better luck with installing. I also made some improvements of my own.



You can find it on GitHub https://github.com/meridius/bpmwrap






share|improve this answer
























  • This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

    – Adrian
    Nov 17 '16 at 23:14



















2














I don't know of a tool that does exactly what you are looking for, but I have played around with MusicIP.



Used the linux / java version - it takes a long time to completely analyze a music library, but it really does work. You can find songs that are similar to other songs. You can right click on the playlist generated and select option to select more or fewer songs like the one selected. You can also choose to eliminate certain genre's. It's kind of cool, but after the wow factor wore off, I stopped using it.



The free version exports playlists up to 75 songs in (at least) m3u format.



It's currently unsupported, but I think they have tried to take it commercial as Predexis.






share|improve this answer































    1














    While it is not just a tool like you say you are looking for, Banshee media player can detect bpm.



    I use Banshee for all my music playing, organisation and synchronizing to portable players.
    I'm not affiliated, but I like the program the best of all that I've tried.
    It can also generate "smart playlists" based on all sorts of properties of the tracks, including bpm.



    There is an extension which analyses all sorts of things about the song, and will find similar songs to the one you're playing. It's called Mirage, and I used it for a while, but I don't any more, as I've created a number of playlists of ones that suit various moods (not necessarily similar according to Mirage).



    I don't know if Banshee will save the bpm it detected back into the ID3v2 "bpm" tag of the file. If anyone knows how to easily check the bpm tag from outside the program I'll check.






    share|improve this answer































      1














      It's not Linux but may well work in Wine - I use MixMeister BPM Analyzer






      share|improve this answer































        0














        I found another tool for tagging MP3 files with the correct BPM value.



        It's called BPMDetect. Open-source. QT libs so works fine under Gnome. Comes with a GUI but can be compiled as a console only version (run "scons console=1" as stated in the readme.txt).



        Otherwise, in the end, i've too used the "bpmcount" from BpmDJ as i had difficulties to compile BPMDetect on a 64 bits Ubuntu host (due to the fmodex dependency). So i took the (very cool and well-written) shell script above (see below), the "bpmcount" binary extracted from the [x64 .rpm][3] available on the BpmDJ website (i've just extract the .rpm with



        pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv


        and it worked like a charm. I just had to modify the above script as, out of the box, it weren't working on my side (problem with stdout / stderr of the bpmcount binary). My modification is about file redirection :



        local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)





        share|improve this answer































          0














          There is another tool recommended in this question on stackoverflow: aubio, which comes along with python modules.



          I haven't tried it because I was kinda busy taking care of compiling BpmDj. Just in case anybody else finds themselves struggling similar troubles while trying, I'd like to strongly recommend to make absolutely sure:




          1. having downloaded the latest release of the BpmDj sources

          2. having the appropriate boost libraries installed


          With the latest g++ compiler upgrades, some issues seem to have arisen especially concerning recent debian and ubuntu releases. As soon as he became aware of these problems, the author had the kindness to fix the emerged incompatibilities and put together a new release which now compiles like a charm. So anybody who have been close to falling into despair over relentless compile errors lately: you are save now.



          @mmx, your tools look good too, but they rely on SoX, which by default has no mp3 features. So they require compiling SoX with Lame/MAD support first, which unfortunately is too much effort for people as lazy as me.






          share|improve this answer

































            0














            To get @meridius' solution working on my Mac I had to do a bit of extra legwork and modify the script a bit:



            # Let's install bpm-tools
            git clone http://www.pogo.org.uk/~mark/bpm-tools.git
            cd bpm-tools
            make && make install
            # There will be errors, but they did not affect the result

            # The following three lines could be replaced by including this directory in your $PATH
            ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
            ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
            ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
            cd ..

            # Time to install a bunch of GNU tools
            # Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
            brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
            brew tap homebrew/dupes; brew install grep

            # Now for Mutagen (contains mid3v2)
            git clone https://github.com/nex3/mutagen.git
            cd mutagen
            ./setup.py build
            sudo ./setup.py install
            # There will be errors, but they did not affect the result
            cd ..


            Then I had to modify the script to point to the GNU versions of everything, and a few other tweaks:



            #!/usr/local/bin/bash

            # ================================= FUNCTIONS =================================

            function help() {
            less <<< 'BPMWRAP

            Description:
            This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
            audio tagging utilities. The purpose is to make BPM (beats per minute)
            tagging as easy as possible.
            Default behaviour is to look through working directory for *.mp3 files
            and compute and print their BPM in the following manner:
            [current (if any)] [computed] [filename]

            Usage:
            bpmwrap [options] [directory or filenames]

            Options:
            You can specify files to process by one of these ways:
            1) state files and/or directories containing them after options
            2) specify --import file
            3) specify --input file
            With either way you still can filter the resulting list using --type option(s).
            Remember that the script will process only mp3 files by default, unless
            specified otherwise!

            -i, --import file
            Use this option to set BPM tag for all files in given file instead of
            computing it. Expected format of every row is BPM number and absolute path
            to filename separated by semicolon like so:
            145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
            Remember to use --write option too.
            -n, --input file
            Use this option to give the script list of FILES to process INSTEAD of paths
            where to look for them. Each row whould have one absolute path.
            This will bypass the searching part and is that way useful when you want
            to process large number of files several times. Like when you are not yet
            sure what BPM limits to set. Extension filtering will still work.
            -o, --output file
            Save output also to a file.
            -l, --list-save file
            Save list of files about to get processed. You can use this list later
            as a file for --input option.
            -t, --type filetype
            Extension of file type to work with. Defaults to mp3. Can be specified
            multiple times for more filetypes. Currently supported are mp3 ogg flac.
            -e, --existing-only
            Only show BPM for files that have it. Do NOT compute new one.
            -w, --write
            Write computed BPM to audio file but do NOT overwrite existing value.
            -f, --force
            Write computed BPM to audio file even if it already has one. Aplicable only
            with --write option.
            -m, --min minbpm
            Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
            -x, --max maxbpm
            Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
            -v, --verbose
            Show "progress" messages.
            -c, --csv-friendly
            Use semicolon (;) instead of space to separate output columns.
            -h, --help
            Show this help.

            Note:
            Program bpm-tag (on whis is this script based) is looking only for lowercase
            file extensions. If you get 0 (zero) BPM, this should be the case. So just
            rename the file.

            License:
            GPL V2

            Links:
            bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)

            Dependencies:
            bpm-tag mid3v2 vorbiscomment metaflac

            Author:
            Martin Lukeš (martin.meridius@gmail.com)
            Based on work of kolypto (http://superuser.com/a/129157/137326)
            '
            }

            # Usage: result=$(inArray $needle haystack[@])
            # @param string needle
            # @param array haystack
            # @returns int (1 = NOT / 0 = IS) in array
            function inArray() {
            needle="$1"
            haystack=("${!2}")
            out=1
            for e in "${haystack[@]}" ; do
            if [[ "$e" = "$needle" ]] ; then
            out=0
            break
            fi
            done
            echo $out
            }

            # Usage: result=$(implode $separator array[@])
            # @param char separator
            # @param array array to implode
            # @returns string separated array elements
            function implode() {
            separator="$1"
            array=("${!2}")
            IFSORIG=$IFS
            IFS="$separator"
            echo "${array[*]}"
            IFS=$IFSORIG
            }

            # @param string file
            # @returns int BPM value
            function getBpm() {
            local file="$1"
            local ext="${file##*.}"
            declare -l ext # convert to lowercase
            { case "$ext" in
            'mp3') mid3v2 -l "$file" ;;
            'ogg') vorbiscomment -l "$file" ;;
            'flac') metaflac --export-tags-to=- "$file" ;;
            esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
            }

            # @param string file
            # @param int BPM value
            function setBpm() {
            local file="$1"
            local bpm="${2%%.*}"
            local ext="${file##*.}"
            declare -l ext # convert to lowercase
            case "$ext" in
            'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
            'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
            'flac') metaflac --set-tag="BPM=$bpm" "$file"
            mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
            ;;
            esac
            }

            # # @param string file
            # # @returns int BPM value
            function computeBpm() {
            local file="$1"
            local m_opt=""
            [ ! -z "$m" ] && m_opt="-m $m"
            local x_opt=""
            [ ! -z "$x" ] && x_opt="-x $x"
            local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
            echo $(echo "$row"
            | gsed -r 's/.+ ([0-9]+.[0-9]{3}) BPM/1/'
            | gawk '{printf("%.0fn", $1)}')
            }

            # @param string file
            # @param int file number
            # @param int BPM from file list given by --import option
            function oneThread() {
            local file="$1"
            local filenumber="$2"
            local bpm_hard="$3"
            local bpm_old=$(getBpm "$file")
            [ -z "$bpm_old" ] && bpm_old="NONE"
            if [ "$e" ] ; then # only show existing
            myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
            else # compute new one
            if [ "$bpm_hard" ] ; then
            local bpm_new="$bpm_hard"
            else
            local bpm_new=$(computeBpm "$file")
            fi
            [ "$w" ] && { # write new one
            if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
            setBpm "$file" "$bpm_new"
            else
            [ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
            fi
            }
            myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
            fi
            }

            function myEcho() {
            [ "$o" ] && echo -e "$1" >> "$o"
            echo -e "$1"
            }


            # ================================== OPTIONS ==================================

            eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch"
            -l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")

            declare i n o l t e w f m x v c h
            declare -a INPUTFILES
            declare -a INPUTTYPES
            while [ $# -gt 0 ] ; do
            case "$1" in
            -i|--import) shift ; i="$1" ; shift ;;
            -n|--input) shift ; n="$1" ; shift ;;
            -o|--output) shift ; o="$1" ; shift ;;
            -l|--list-save) shift ; l="$1" ; shift ;;
            -t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
            -e|--existing-only) e=1 ; shift ;;
            -w|--write) w=1 ; shift ;;
            -f|--force) f=1 ; shift ;;
            -m|--min) shift ; m="$1" ; shift ;;
            -x|--max) shift ; x="$1" ; shift ;;
            -v|--verbose) v=1 ; shift ;;
            -c|--csv-friendly) c=1 ; shift ;;
            -h|--help) h=1 ; shift ;;
            --) shift ;;
            -*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
            *) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
            esac
            done


            # ================================= DEFAULTS ==================================

            #NOTE Remove what requisities you don't need but don't try to use them after!
            # always mp3/flac ogg flac
            REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
            which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }

            [ "$h" ] && {
            help
            exit 0
            }

            [[ $m && $x && ( $m -ge $x ) ]] && {
            myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
            exit 1
            }
            [[ "$i" && "$n" ]] && {
            echo "You cannot specify both -i and -n options!"
            exit 1
            }
            [[ "$i" && ( "$m" || "$x" ) ]] && {
            echo "You cannot use -m nor -x option with -i option!"
            exit 1
            }
            [ "$e" ] && {
            [[ "$w" || "$f" ]] && {
            echo "With -e option you don't have any value to write!"
            exit 1
            }
            [[ "$m" || "$x" ]] && {
            echo "With -e option you don't have any value to count!"
            exit 1
            }
            }

            for file in "$o" "$l" ; do
            if [ -f "$file" ] ; then
            while true ; do
            read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
            case "$key" in
            y|Y|"") echo "" > "$file" ; break ;;
            n|N) exit 0 ;;
            esac
            echo ""
            done
            echo ""
            fi
            done

            [ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")

            # NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
            NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/2/')"
            LASTPID=0
            TYPESALLOWED=("mp3" "ogg" "flac")
            # declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
            declare -A BPMIMPORT # array of BPMs from --import file, keys are file names

            for type in "${INPUTTYPES[@]}" ; do
            [[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
            myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
            exit 1
            }
            done

            ### here are three ways how to pass files to the script...
            if [ "$i" ] ; then # just parse given file list and set BPM to listed files
            if [ -f "$i" ] ; then
            # myEcho "Setting BPM tags from given file ..."
            while read row ; do
            bpm="${row%%;*}"
            file="${row#*;}"
            ext="${file##*.}"
            ext="${ext,,}" # convert to lowercase
            if [ -f "$file" ] ; then
            if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
            FILES=("${FILES[@]}" "$file")
            BPMIMPORT["$file"]="$bpm"
            else
            myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
            fi
            else
            myEcho "Skipping non-existing file $file"
            fi
            done < "$i"
            else
            myEcho "Given import file does not exists!"
            exit 1
            fi
            elif [ "$n" ] ; then # get files from file list
            if [ -f "$n" ] ; then
            rownumber=1
            while read file ; do
            if [ -f "$file" ] ; then
            ext="${file##*.}"
            ext="${ext,,}" # convert to lowercase
            if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
            FILES=("${FILES[@]}" "$file")
            else
            myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
            fi
            else
            myEcho "Skipping file on row $rownumber (non-existing) ... $file"
            fi
            let rownumber++
            done < "$n"
            unset rownumber
            else
            myEcho "Given input file $n does not exists!"
            exit 1
            fi
            else # get files from given parameters
            [ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
            for file in "${INPUTFILES[@]}" ; do
            [ ! -e "$file" ] && {
            myEcho "File or directory $file does not exist!"
            exit 1
            }
            done
            impl_types=`implode "|" INPUTTYPES[@]`
            while read file ; do
            echo -ne "Creating list of files ... (${#FILES[@]}) ${file}33[0K"\r
            FILES=("${FILES[@]}" "$file")
            done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*.($impl_types)")
            echo -e "Counted ${#FILES[@]} files33[0K"\r
            fi

            [ "$l" ] && printf '%sn' "${FILES[@]}" > "$l"

            NUMFILES=${#FILES[@]}
            FILENUMBER=1

            [ $NUMFILES -eq 0 ] && {
            myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
            exit 1
            }

            declare SEP=" "
            [ "$c" ] && SEP=";"


            # =============================== MAIN SECTION ================================

            if [ "$e" ] ; then # what heading to show
            myEcho "num${SEP}old${SEP}filename"
            else
            myEcho "num${SEP}old${SEP}new${SEP}filename"
            fi

            for file in "${FILES[@]}" ; do
            [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
            [ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})t$file ..."
            oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
            LASTPID="$!"
            let FILENUMBER++
            done

            [ "$v" ] && myEcho "Waiting for last process ..."
            wait $LASTPID
            [ "$v" ] && myEcho \n"DONE"


            Thank you for your hard work @kolypto and @meridius.



            ...the pain I go through to maintain a CLI workflow and pay no money for music tools...






            share|improve this answer






























              9 Answers
              9






              active

              oldest

              votes








              9 Answers
              9






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              15














              At the site DaveParillo suggested I've found BpmDj project. It has a bpmcount executable that calculates the bpm very nice: it handles mp3 as well as flac:



              161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
              63.5645 Doom3.mp3


              The only thing that's left is to retag the collection. I'll update this answer whenever I succeed.
              Thanks! :)





              Step 1



              Run bpmcount against the entire collection and store the results into a textfile.
              The problem is that bpmcount crashes from time to time and tries to eat up to 2GB of memory when it processes several files so we should feed it with filenames one by one. Like this:



              musicdir='/home/ootync/music'
              find "$musicdir" -iregex ".*.(mp3|ogg|flac|ape)" -exec bpmcount {} ;
              | fgrep "$musicdir" > "$musicdir/BPMs.txt"




              Step 2



              We'll need some additional packages: apt-get install vorbis-tools flac python-mutagen.
              Now have a look at how the 'bpm' tag can be added:



              mid3v2 --TBPM 100 doom3.mp3
              vorbiscomment -a -t "BPM=100" mother.ogg
              metaflac --set-tag="BPM=100" metallica.flac


              Alas, I have no *.ape tracks



              Now we have the BPMs and the entire collection should be retagged. Here's the script:



              cat "$musicdir/BPMs.txt" | while read bpm file ; do
              bpm=`printf "%.0f" "$bpm"` ;
              case "$file" in
              *.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
              *.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              *.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
              esac
              done




              Step 2.1 Revisited
              Here's a script that will add BPM tags to your collection.



              It runs one process per CPU Core to make the process faster. Additionally, it uses no temporary files and it capable of detecting whether a file is already tagged.



              Additionally, I've discovered that FLAC sometimes has both ID3 and VorbisComment inside. This script updates both.



              #!/bin/bash

              function display_help() {
              cat <<-HELP
              Recursive BPM-writer for multicore CPUs.
              It analyzes BPMs of every media file and writes a correct tag there.
              Usage: $(basename "$0") path [...]
              HELP
              exit 0
              }

              [ $# -lt 1 ] && display_help

              #=== Requirements
              requires="bpmcount mid3v2 vorbiscomment metaflac"
              which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }

              #=== Functions

              function bpm_read(){
              local file="$1"
              local ext="${file##*.}"
              declare -l ext
              # Detect
              { case "$ext" in
              'mp3') mid3v2 -l "$file" ;;
              'ogg') vorbiscomment -l "$file" ;;
              'flac') metaflac --export-tags-to=- "$file" ;;
              esac ; } | fgrep 'BPM=' | cut -d'=' -f2
              }
              function bpm_write(){
              local file="$1"
              local bpm="${2%%.*}"
              local ext="${file##*.}"
              declare -l ext
              echo "BPM=$bpm @$file"
              # Write
              case "$ext" in
              'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
              'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              'flac') metaflac --set-tag="BPM=$bpm" "$file"
              mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
              ;;
              esac
              }

              #=== Process
              function oneThread(){
              local file="$1"
              #=== Check whether there's an existing BPM
              local bpm=$(bpm_read "$file")
              [ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
              #=== Detect a new BPM
              # Detect a new bpm
              local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
              [ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
              # Write it
              bpm_write "$file" "${bpm%%.*}" >/dev/null
              }

              NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
              find $@ -type f -regextype posix-awk -iregex '.*.(mp3|ogg|flac)'
              | while read file ; do
              [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
              echo "$file"
              oneThread "$file" &
              done


              Enjoy! :)






              share|improve this answer


























              • Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

                – DaveParillo
                Apr 9 '10 at 16:24






              • 1





                Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

                – kolypto
                Apr 9 '10 at 19:51






              • 1





                Nice work on step #2. Wish I could upvote twice!

                – DaveParillo
                Apr 12 '10 at 4:31






              • 1





                Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

                – kolypto
                Apr 12 '10 at 15:36













              • How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

                – pedrosaurio
                Jul 25 '12 at 18:56
















              15














              At the site DaveParillo suggested I've found BpmDj project. It has a bpmcount executable that calculates the bpm very nice: it handles mp3 as well as flac:



              161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
              63.5645 Doom3.mp3


              The only thing that's left is to retag the collection. I'll update this answer whenever I succeed.
              Thanks! :)





              Step 1



              Run bpmcount against the entire collection and store the results into a textfile.
              The problem is that bpmcount crashes from time to time and tries to eat up to 2GB of memory when it processes several files so we should feed it with filenames one by one. Like this:



              musicdir='/home/ootync/music'
              find "$musicdir" -iregex ".*.(mp3|ogg|flac|ape)" -exec bpmcount {} ;
              | fgrep "$musicdir" > "$musicdir/BPMs.txt"




              Step 2



              We'll need some additional packages: apt-get install vorbis-tools flac python-mutagen.
              Now have a look at how the 'bpm' tag can be added:



              mid3v2 --TBPM 100 doom3.mp3
              vorbiscomment -a -t "BPM=100" mother.ogg
              metaflac --set-tag="BPM=100" metallica.flac


              Alas, I have no *.ape tracks



              Now we have the BPMs and the entire collection should be retagged. Here's the script:



              cat "$musicdir/BPMs.txt" | while read bpm file ; do
              bpm=`printf "%.0f" "$bpm"` ;
              case "$file" in
              *.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
              *.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              *.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
              esac
              done




              Step 2.1 Revisited
              Here's a script that will add BPM tags to your collection.



              It runs one process per CPU Core to make the process faster. Additionally, it uses no temporary files and it capable of detecting whether a file is already tagged.



              Additionally, I've discovered that FLAC sometimes has both ID3 and VorbisComment inside. This script updates both.



              #!/bin/bash

              function display_help() {
              cat <<-HELP
              Recursive BPM-writer for multicore CPUs.
              It analyzes BPMs of every media file and writes a correct tag there.
              Usage: $(basename "$0") path [...]
              HELP
              exit 0
              }

              [ $# -lt 1 ] && display_help

              #=== Requirements
              requires="bpmcount mid3v2 vorbiscomment metaflac"
              which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }

              #=== Functions

              function bpm_read(){
              local file="$1"
              local ext="${file##*.}"
              declare -l ext
              # Detect
              { case "$ext" in
              'mp3') mid3v2 -l "$file" ;;
              'ogg') vorbiscomment -l "$file" ;;
              'flac') metaflac --export-tags-to=- "$file" ;;
              esac ; } | fgrep 'BPM=' | cut -d'=' -f2
              }
              function bpm_write(){
              local file="$1"
              local bpm="${2%%.*}"
              local ext="${file##*.}"
              declare -l ext
              echo "BPM=$bpm @$file"
              # Write
              case "$ext" in
              'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
              'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              'flac') metaflac --set-tag="BPM=$bpm" "$file"
              mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
              ;;
              esac
              }

              #=== Process
              function oneThread(){
              local file="$1"
              #=== Check whether there's an existing BPM
              local bpm=$(bpm_read "$file")
              [ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
              #=== Detect a new BPM
              # Detect a new bpm
              local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
              [ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
              # Write it
              bpm_write "$file" "${bpm%%.*}" >/dev/null
              }

              NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
              find $@ -type f -regextype posix-awk -iregex '.*.(mp3|ogg|flac)'
              | while read file ; do
              [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
              echo "$file"
              oneThread "$file" &
              done


              Enjoy! :)






              share|improve this answer


























              • Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

                – DaveParillo
                Apr 9 '10 at 16:24






              • 1





                Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

                – kolypto
                Apr 9 '10 at 19:51






              • 1





                Nice work on step #2. Wish I could upvote twice!

                – DaveParillo
                Apr 12 '10 at 4:31






              • 1





                Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

                – kolypto
                Apr 12 '10 at 15:36













              • How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

                – pedrosaurio
                Jul 25 '12 at 18:56














              15












              15








              15







              At the site DaveParillo suggested I've found BpmDj project. It has a bpmcount executable that calculates the bpm very nice: it handles mp3 as well as flac:



              161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
              63.5645 Doom3.mp3


              The only thing that's left is to retag the collection. I'll update this answer whenever I succeed.
              Thanks! :)





              Step 1



              Run bpmcount against the entire collection and store the results into a textfile.
              The problem is that bpmcount crashes from time to time and tries to eat up to 2GB of memory when it processes several files so we should feed it with filenames one by one. Like this:



              musicdir='/home/ootync/music'
              find "$musicdir" -iregex ".*.(mp3|ogg|flac|ape)" -exec bpmcount {} ;
              | fgrep "$musicdir" > "$musicdir/BPMs.txt"




              Step 2



              We'll need some additional packages: apt-get install vorbis-tools flac python-mutagen.
              Now have a look at how the 'bpm' tag can be added:



              mid3v2 --TBPM 100 doom3.mp3
              vorbiscomment -a -t "BPM=100" mother.ogg
              metaflac --set-tag="BPM=100" metallica.flac


              Alas, I have no *.ape tracks



              Now we have the BPMs and the entire collection should be retagged. Here's the script:



              cat "$musicdir/BPMs.txt" | while read bpm file ; do
              bpm=`printf "%.0f" "$bpm"` ;
              case "$file" in
              *.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
              *.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              *.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
              esac
              done




              Step 2.1 Revisited
              Here's a script that will add BPM tags to your collection.



              It runs one process per CPU Core to make the process faster. Additionally, it uses no temporary files and it capable of detecting whether a file is already tagged.



              Additionally, I've discovered that FLAC sometimes has both ID3 and VorbisComment inside. This script updates both.



              #!/bin/bash

              function display_help() {
              cat <<-HELP
              Recursive BPM-writer for multicore CPUs.
              It analyzes BPMs of every media file and writes a correct tag there.
              Usage: $(basename "$0") path [...]
              HELP
              exit 0
              }

              [ $# -lt 1 ] && display_help

              #=== Requirements
              requires="bpmcount mid3v2 vorbiscomment metaflac"
              which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }

              #=== Functions

              function bpm_read(){
              local file="$1"
              local ext="${file##*.}"
              declare -l ext
              # Detect
              { case "$ext" in
              'mp3') mid3v2 -l "$file" ;;
              'ogg') vorbiscomment -l "$file" ;;
              'flac') metaflac --export-tags-to=- "$file" ;;
              esac ; } | fgrep 'BPM=' | cut -d'=' -f2
              }
              function bpm_write(){
              local file="$1"
              local bpm="${2%%.*}"
              local ext="${file##*.}"
              declare -l ext
              echo "BPM=$bpm @$file"
              # Write
              case "$ext" in
              'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
              'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              'flac') metaflac --set-tag="BPM=$bpm" "$file"
              mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
              ;;
              esac
              }

              #=== Process
              function oneThread(){
              local file="$1"
              #=== Check whether there's an existing BPM
              local bpm=$(bpm_read "$file")
              [ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
              #=== Detect a new BPM
              # Detect a new bpm
              local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
              [ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
              # Write it
              bpm_write "$file" "${bpm%%.*}" >/dev/null
              }

              NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
              find $@ -type f -regextype posix-awk -iregex '.*.(mp3|ogg|flac)'
              | while read file ; do
              [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
              echo "$file"
              oneThread "$file" &
              done


              Enjoy! :)






              share|improve this answer















              At the site DaveParillo suggested I've found BpmDj project. It has a bpmcount executable that calculates the bpm very nice: it handles mp3 as well as flac:



              161.135 Metallica/2008 - Death Magnetic/01-That Was Just Your Life.flac
              63.5645 Doom3.mp3


              The only thing that's left is to retag the collection. I'll update this answer whenever I succeed.
              Thanks! :)





              Step 1



              Run bpmcount against the entire collection and store the results into a textfile.
              The problem is that bpmcount crashes from time to time and tries to eat up to 2GB of memory when it processes several files so we should feed it with filenames one by one. Like this:



              musicdir='/home/ootync/music'
              find "$musicdir" -iregex ".*.(mp3|ogg|flac|ape)" -exec bpmcount {} ;
              | fgrep "$musicdir" > "$musicdir/BPMs.txt"




              Step 2



              We'll need some additional packages: apt-get install vorbis-tools flac python-mutagen.
              Now have a look at how the 'bpm' tag can be added:



              mid3v2 --TBPM 100 doom3.mp3
              vorbiscomment -a -t "BPM=100" mother.ogg
              metaflac --set-tag="BPM=100" metallica.flac


              Alas, I have no *.ape tracks



              Now we have the BPMs and the entire collection should be retagged. Here's the script:



              cat "$musicdir/BPMs.txt" | while read bpm file ; do
              bpm=`printf "%.0f" "$bpm"` ;
              case "$file" in
              *.mp3) mid3v2 --TBPM "$bpm" "$file" > /dev/null ;;
              *.ogg) vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              *.flac) metaflac --set-tag="BPM=$bpm" "$file" ;;
              esac
              done




              Step 2.1 Revisited
              Here's a script that will add BPM tags to your collection.



              It runs one process per CPU Core to make the process faster. Additionally, it uses no temporary files and it capable of detecting whether a file is already tagged.



              Additionally, I've discovered that FLAC sometimes has both ID3 and VorbisComment inside. This script updates both.



              #!/bin/bash

              function display_help() {
              cat <<-HELP
              Recursive BPM-writer for multicore CPUs.
              It analyzes BPMs of every media file and writes a correct tag there.
              Usage: $(basename "$0") path [...]
              HELP
              exit 0
              }

              [ $# -lt 1 ] && display_help

              #=== Requirements
              requires="bpmcount mid3v2 vorbiscomment metaflac"
              which $requires > /dev/null || { echo "E: These binaries are required: $requires" >&2 ; exit 1; }

              #=== Functions

              function bpm_read(){
              local file="$1"
              local ext="${file##*.}"
              declare -l ext
              # Detect
              { case "$ext" in
              'mp3') mid3v2 -l "$file" ;;
              'ogg') vorbiscomment -l "$file" ;;
              'flac') metaflac --export-tags-to=- "$file" ;;
              esac ; } | fgrep 'BPM=' | cut -d'=' -f2
              }
              function bpm_write(){
              local file="$1"
              local bpm="${2%%.*}"
              local ext="${file##*.}"
              declare -l ext
              echo "BPM=$bpm @$file"
              # Write
              case "$ext" in
              'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
              'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
              'flac') metaflac --set-tag="BPM=$bpm" "$file"
              mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
              ;;
              esac
              }

              #=== Process
              function oneThread(){
              local file="$1"
              #=== Check whether there's an existing BPM
              local bpm=$(bpm_read "$file")
              [ "$bpm" != '' ] && return 0 # there's a nonempty BPM tag
              #=== Detect a new BPM
              # Detect a new bpm
              local bpm=$(bpmcount "$file" | grep '^[0-9]' | cut -f1)
              [ "$bpm" == '' ] && { echo "W: Invalid BPM '$bpm' detected @ $file" >&2 ; return 0 ; } # problems
              # Write it
              bpm_write "$file" "${bpm%%.*}" >/dev/null
              }

              NUMCPU="$(grep ^processor /proc/cpuinfo | wc -l)"
              find $@ -type f -regextype posix-awk -iregex '.*.(mp3|ogg|flac)'
              | while read file ; do
              [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
              echo "$file"
              oneThread "$file" &
              done


              Enjoy! :)







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Jun 16 '14 at 5:24







              user277533

















              answered Apr 9 '10 at 11:16









              kolyptokolypto

              1,98852234




              1,98852234













              • Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

                – DaveParillo
                Apr 9 '10 at 16:24






              • 1





                Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

                – kolypto
                Apr 9 '10 at 19:51






              • 1





                Nice work on step #2. Wish I could upvote twice!

                – DaveParillo
                Apr 12 '10 at 4:31






              • 1





                Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

                – kolypto
                Apr 12 '10 at 15:36













              • How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

                – pedrosaurio
                Jul 25 '12 at 18:56



















              • Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

                – DaveParillo
                Apr 9 '10 at 16:24






              • 1





                Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

                – kolypto
                Apr 9 '10 at 19:51






              • 1





                Nice work on step #2. Wish I could upvote twice!

                – DaveParillo
                Apr 12 '10 at 4:31






              • 1





                Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

                – kolypto
                Apr 12 '10 at 15:36













              • How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

                – pedrosaurio
                Jul 25 '12 at 18:56

















              Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

              – DaveParillo
              Apr 9 '10 at 16:24





              Excellent! I hadn't gotten around to trying this last night. As far as command line tagging, try mid3v2: linux.die.net/man/1/mid3v2, serviceable at least until Ex Falso supports command line editing. The id3v2 tad id is TBPM

              – DaveParillo
              Apr 9 '10 at 16:24




              1




              1





              Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

              – kolypto
              Apr 9 '10 at 19:51





              Thanks, I'll try in a couple of days and post the results :) I wonder whether FLAC supports such thing: I'll have to check this out.

              – kolypto
              Apr 9 '10 at 19:51




              1




              1





              Nice work on step #2. Wish I could upvote twice!

              – DaveParillo
              Apr 12 '10 at 4:31





              Nice work on step #2. Wish I could upvote twice!

              – DaveParillo
              Apr 12 '10 at 4:31




              1




              1





              Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

              – kolypto
              Apr 12 '10 at 15:36







              Thanks :) Alas, my Amarok didn't notice the new tag in FLACs which I like the most :)) bug submitted.

              – kolypto
              Apr 12 '10 at 15:36















              How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

              – pedrosaurio
              Jul 25 '12 at 18:56





              How did you install it? the rpm they provide doesn't seem to work in my computer and I am struggling with the compilation.

              – pedrosaurio
              Jul 25 '12 at 18:56













              8














              This is a command-line tool to detect the BPM and put it in the FLAC file tags:



              http://www.pogo.org.uk/~mark/bpm-tools/






              share|improve this answer
























              • The latest version also handles mp3s and ogg vorbis.

                – encoded
                Jan 10 '13 at 20:51











              • Ubuntu has bpm-tools packages available in saucy.

                – naught101
                Apr 5 '14 at 6:44
















              8














              This is a command-line tool to detect the BPM and put it in the FLAC file tags:



              http://www.pogo.org.uk/~mark/bpm-tools/






              share|improve this answer
























              • The latest version also handles mp3s and ogg vorbis.

                – encoded
                Jan 10 '13 at 20:51











              • Ubuntu has bpm-tools packages available in saucy.

                – naught101
                Apr 5 '14 at 6:44














              8












              8








              8







              This is a command-line tool to detect the BPM and put it in the FLAC file tags:



              http://www.pogo.org.uk/~mark/bpm-tools/






              share|improve this answer













              This is a command-line tool to detect the BPM and put it in the FLAC file tags:



              http://www.pogo.org.uk/~mark/bpm-tools/







              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered Oct 21 '12 at 21:42









              mmxmmx

              8111




              8111













              • The latest version also handles mp3s and ogg vorbis.

                – encoded
                Jan 10 '13 at 20:51











              • Ubuntu has bpm-tools packages available in saucy.

                – naught101
                Apr 5 '14 at 6:44



















              • The latest version also handles mp3s and ogg vorbis.

                – encoded
                Jan 10 '13 at 20:51











              • Ubuntu has bpm-tools packages available in saucy.

                – naught101
                Apr 5 '14 at 6:44

















              The latest version also handles mp3s and ogg vorbis.

              – encoded
              Jan 10 '13 at 20:51





              The latest version also handles mp3s and ogg vorbis.

              – encoded
              Jan 10 '13 at 20:51













              Ubuntu has bpm-tools packages available in saucy.

              – naught101
              Apr 5 '14 at 6:44





              Ubuntu has bpm-tools packages available in saucy.

              – naught101
              Apr 5 '14 at 6:44











              5














              I used kolypto's original script using bpmcount and rewrote it for bpm-tag (utility of bpm-tools) which I had better luck with installing. I also made some improvements of my own.



              You can find it on GitHub https://github.com/meridius/bpmwrap






              share|improve this answer
























              • This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

                – Adrian
                Nov 17 '16 at 23:14
















              5














              I used kolypto's original script using bpmcount and rewrote it for bpm-tag (utility of bpm-tools) which I had better luck with installing. I also made some improvements of my own.



              You can find it on GitHub https://github.com/meridius/bpmwrap






              share|improve this answer
























              • This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

                – Adrian
                Nov 17 '16 at 23:14














              5












              5








              5







              I used kolypto's original script using bpmcount and rewrote it for bpm-tag (utility of bpm-tools) which I had better luck with installing. I also made some improvements of my own.



              You can find it on GitHub https://github.com/meridius/bpmwrap






              share|improve this answer













              I used kolypto's original script using bpmcount and rewrote it for bpm-tag (utility of bpm-tools) which I had better luck with installing. I also made some improvements of my own.



              You can find it on GitHub https://github.com/meridius/bpmwrap







              share|improve this answer












              share|improve this answer



              share|improve this answer










              answered May 8 '14 at 17:38









              meridiusmeridius

              16316




              16316













              • This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

                – Adrian
                Nov 17 '16 at 23:14



















              • This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

                – Adrian
                Nov 17 '16 at 23:14

















              This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

              – Adrian
              Nov 17 '16 at 23:14





              This required a few modifications to work on a Mac, which I have included in my own answer below (because it is too long for a comment)

              – Adrian
              Nov 17 '16 at 23:14











              2














              I don't know of a tool that does exactly what you are looking for, but I have played around with MusicIP.



              Used the linux / java version - it takes a long time to completely analyze a music library, but it really does work. You can find songs that are similar to other songs. You can right click on the playlist generated and select option to select more or fewer songs like the one selected. You can also choose to eliminate certain genre's. It's kind of cool, but after the wow factor wore off, I stopped using it.



              The free version exports playlists up to 75 songs in (at least) m3u format.



              It's currently unsupported, but I think they have tried to take it commercial as Predexis.






              share|improve this answer




























                2














                I don't know of a tool that does exactly what you are looking for, but I have played around with MusicIP.



                Used the linux / java version - it takes a long time to completely analyze a music library, but it really does work. You can find songs that are similar to other songs. You can right click on the playlist generated and select option to select more or fewer songs like the one selected. You can also choose to eliminate certain genre's. It's kind of cool, but after the wow factor wore off, I stopped using it.



                The free version exports playlists up to 75 songs in (at least) m3u format.



                It's currently unsupported, but I think they have tried to take it commercial as Predexis.






                share|improve this answer


























                  2












                  2








                  2







                  I don't know of a tool that does exactly what you are looking for, but I have played around with MusicIP.



                  Used the linux / java version - it takes a long time to completely analyze a music library, but it really does work. You can find songs that are similar to other songs. You can right click on the playlist generated and select option to select more or fewer songs like the one selected. You can also choose to eliminate certain genre's. It's kind of cool, but after the wow factor wore off, I stopped using it.



                  The free version exports playlists up to 75 songs in (at least) m3u format.



                  It's currently unsupported, but I think they have tried to take it commercial as Predexis.






                  share|improve this answer













                  I don't know of a tool that does exactly what you are looking for, but I have played around with MusicIP.



                  Used the linux / java version - it takes a long time to completely analyze a music library, but it really does work. You can find songs that are similar to other songs. You can right click on the playlist generated and select option to select more or fewer songs like the one selected. You can also choose to eliminate certain genre's. It's kind of cool, but after the wow factor wore off, I stopped using it.



                  The free version exports playlists up to 75 songs in (at least) m3u format.



                  It's currently unsupported, but I think they have tried to take it commercial as Predexis.







                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Apr 9 '10 at 4:21









                  DaveParilloDaveParillo

                  13k3444




                  13k3444























                      1














                      While it is not just a tool like you say you are looking for, Banshee media player can detect bpm.



                      I use Banshee for all my music playing, organisation and synchronizing to portable players.
                      I'm not affiliated, but I like the program the best of all that I've tried.
                      It can also generate "smart playlists" based on all sorts of properties of the tracks, including bpm.



                      There is an extension which analyses all sorts of things about the song, and will find similar songs to the one you're playing. It's called Mirage, and I used it for a while, but I don't any more, as I've created a number of playlists of ones that suit various moods (not necessarily similar according to Mirage).



                      I don't know if Banshee will save the bpm it detected back into the ID3v2 "bpm" tag of the file. If anyone knows how to easily check the bpm tag from outside the program I'll check.






                      share|improve this answer




























                        1














                        While it is not just a tool like you say you are looking for, Banshee media player can detect bpm.



                        I use Banshee for all my music playing, organisation and synchronizing to portable players.
                        I'm not affiliated, but I like the program the best of all that I've tried.
                        It can also generate "smart playlists" based on all sorts of properties of the tracks, including bpm.



                        There is an extension which analyses all sorts of things about the song, and will find similar songs to the one you're playing. It's called Mirage, and I used it for a while, but I don't any more, as I've created a number of playlists of ones that suit various moods (not necessarily similar according to Mirage).



                        I don't know if Banshee will save the bpm it detected back into the ID3v2 "bpm" tag of the file. If anyone knows how to easily check the bpm tag from outside the program I'll check.






                        share|improve this answer


























                          1












                          1








                          1







                          While it is not just a tool like you say you are looking for, Banshee media player can detect bpm.



                          I use Banshee for all my music playing, organisation and synchronizing to portable players.
                          I'm not affiliated, but I like the program the best of all that I've tried.
                          It can also generate "smart playlists" based on all sorts of properties of the tracks, including bpm.



                          There is an extension which analyses all sorts of things about the song, and will find similar songs to the one you're playing. It's called Mirage, and I used it for a while, but I don't any more, as I've created a number of playlists of ones that suit various moods (not necessarily similar according to Mirage).



                          I don't know if Banshee will save the bpm it detected back into the ID3v2 "bpm" tag of the file. If anyone knows how to easily check the bpm tag from outside the program I'll check.






                          share|improve this answer













                          While it is not just a tool like you say you are looking for, Banshee media player can detect bpm.



                          I use Banshee for all my music playing, organisation and synchronizing to portable players.
                          I'm not affiliated, but I like the program the best of all that I've tried.
                          It can also generate "smart playlists" based on all sorts of properties of the tracks, including bpm.



                          There is an extension which analyses all sorts of things about the song, and will find similar songs to the one you're playing. It's called Mirage, and I used it for a while, but I don't any more, as I've created a number of playlists of ones that suit various moods (not necessarily similar according to Mirage).



                          I don't know if Banshee will save the bpm it detected back into the ID3v2 "bpm" tag of the file. If anyone knows how to easily check the bpm tag from outside the program I'll check.







                          share|improve this answer












                          share|improve this answer



                          share|improve this answer










                          answered Apr 9 '10 at 10:35









                          DomDom

                          546312




                          546312























                              1














                              It's not Linux but may well work in Wine - I use MixMeister BPM Analyzer






                              share|improve this answer




























                                1














                                It's not Linux but may well work in Wine - I use MixMeister BPM Analyzer






                                share|improve this answer


























                                  1












                                  1








                                  1







                                  It's not Linux but may well work in Wine - I use MixMeister BPM Analyzer






                                  share|improve this answer













                                  It's not Linux but may well work in Wine - I use MixMeister BPM Analyzer







                                  share|improve this answer












                                  share|improve this answer



                                  share|improve this answer










                                  answered Apr 9 '10 at 16:42









                                  ShevekShevek

                                  14.1k54075




                                  14.1k54075























                                      0














                                      I found another tool for tagging MP3 files with the correct BPM value.



                                      It's called BPMDetect. Open-source. QT libs so works fine under Gnome. Comes with a GUI but can be compiled as a console only version (run "scons console=1" as stated in the readme.txt).



                                      Otherwise, in the end, i've too used the "bpmcount" from BpmDJ as i had difficulties to compile BPMDetect on a 64 bits Ubuntu host (due to the fmodex dependency). So i took the (very cool and well-written) shell script above (see below), the "bpmcount" binary extracted from the [x64 .rpm][3] available on the BpmDJ website (i've just extract the .rpm with



                                      pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv


                                      and it worked like a charm. I just had to modify the above script as, out of the box, it weren't working on my side (problem with stdout / stderr of the bpmcount binary). My modification is about file redirection :



                                      local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)





                                      share|improve this answer




























                                        0














                                        I found another tool for tagging MP3 files with the correct BPM value.



                                        It's called BPMDetect. Open-source. QT libs so works fine under Gnome. Comes with a GUI but can be compiled as a console only version (run "scons console=1" as stated in the readme.txt).



                                        Otherwise, in the end, i've too used the "bpmcount" from BpmDJ as i had difficulties to compile BPMDetect on a 64 bits Ubuntu host (due to the fmodex dependency). So i took the (very cool and well-written) shell script above (see below), the "bpmcount" binary extracted from the [x64 .rpm][3] available on the BpmDJ website (i've just extract the .rpm with



                                        pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv


                                        and it worked like a charm. I just had to modify the above script as, out of the box, it weren't working on my side (problem with stdout / stderr of the bpmcount binary). My modification is about file redirection :



                                        local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)





                                        share|improve this answer


























                                          0












                                          0








                                          0







                                          I found another tool for tagging MP3 files with the correct BPM value.



                                          It's called BPMDetect. Open-source. QT libs so works fine under Gnome. Comes with a GUI but can be compiled as a console only version (run "scons console=1" as stated in the readme.txt).



                                          Otherwise, in the end, i've too used the "bpmcount" from BpmDJ as i had difficulties to compile BPMDetect on a 64 bits Ubuntu host (due to the fmodex dependency). So i took the (very cool and well-written) shell script above (see below), the "bpmcount" binary extracted from the [x64 .rpm][3] available on the BpmDJ website (i've just extract the .rpm with



                                          pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv


                                          and it worked like a charm. I just had to modify the above script as, out of the box, it weren't working on my side (problem with stdout / stderr of the bpmcount binary). My modification is about file redirection :



                                          local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)





                                          share|improve this answer













                                          I found another tool for tagging MP3 files with the correct BPM value.



                                          It's called BPMDetect. Open-source. QT libs so works fine under Gnome. Comes with a GUI but can be compiled as a console only version (run "scons console=1" as stated in the readme.txt).



                                          Otherwise, in the end, i've too used the "bpmcount" from BpmDJ as i had difficulties to compile BPMDetect on a 64 bits Ubuntu host (due to the fmodex dependency). So i took the (very cool and well-written) shell script above (see below), the "bpmcount" binary extracted from the [x64 .rpm][3] available on the BpmDJ website (i've just extract the .rpm with



                                          pm2cpio bpmdj-4.2.pl2-0.x86_64.rpm|cpio -idv


                                          and it worked like a charm. I just had to modify the above script as, out of the box, it weren't working on my side (problem with stdout / stderr of the bpmcount binary). My modification is about file redirection :



                                          local bpm=$(bpmcount "$file" 3>&1 1>/dev/null 2>&3 | grep '^[0-9]' | cut -f1)






                                          share|improve this answer












                                          share|improve this answer



                                          share|improve this answer










                                          answered Jan 14 '12 at 14:05









                                          SergioSergio

                                          25515




                                          25515























                                              0














                                              There is another tool recommended in this question on stackoverflow: aubio, which comes along with python modules.



                                              I haven't tried it because I was kinda busy taking care of compiling BpmDj. Just in case anybody else finds themselves struggling similar troubles while trying, I'd like to strongly recommend to make absolutely sure:




                                              1. having downloaded the latest release of the BpmDj sources

                                              2. having the appropriate boost libraries installed


                                              With the latest g++ compiler upgrades, some issues seem to have arisen especially concerning recent debian and ubuntu releases. As soon as he became aware of these problems, the author had the kindness to fix the emerged incompatibilities and put together a new release which now compiles like a charm. So anybody who have been close to falling into despair over relentless compile errors lately: you are save now.



                                              @mmx, your tools look good too, but they rely on SoX, which by default has no mp3 features. So they require compiling SoX with Lame/MAD support first, which unfortunately is too much effort for people as lazy as me.






                                              share|improve this answer






























                                                0














                                                There is another tool recommended in this question on stackoverflow: aubio, which comes along with python modules.



                                                I haven't tried it because I was kinda busy taking care of compiling BpmDj. Just in case anybody else finds themselves struggling similar troubles while trying, I'd like to strongly recommend to make absolutely sure:




                                                1. having downloaded the latest release of the BpmDj sources

                                                2. having the appropriate boost libraries installed


                                                With the latest g++ compiler upgrades, some issues seem to have arisen especially concerning recent debian and ubuntu releases. As soon as he became aware of these problems, the author had the kindness to fix the emerged incompatibilities and put together a new release which now compiles like a charm. So anybody who have been close to falling into despair over relentless compile errors lately: you are save now.



                                                @mmx, your tools look good too, but they rely on SoX, which by default has no mp3 features. So they require compiling SoX with Lame/MAD support first, which unfortunately is too much effort for people as lazy as me.






                                                share|improve this answer




























                                                  0












                                                  0








                                                  0







                                                  There is another tool recommended in this question on stackoverflow: aubio, which comes along with python modules.



                                                  I haven't tried it because I was kinda busy taking care of compiling BpmDj. Just in case anybody else finds themselves struggling similar troubles while trying, I'd like to strongly recommend to make absolutely sure:




                                                  1. having downloaded the latest release of the BpmDj sources

                                                  2. having the appropriate boost libraries installed


                                                  With the latest g++ compiler upgrades, some issues seem to have arisen especially concerning recent debian and ubuntu releases. As soon as he became aware of these problems, the author had the kindness to fix the emerged incompatibilities and put together a new release which now compiles like a charm. So anybody who have been close to falling into despair over relentless compile errors lately: you are save now.



                                                  @mmx, your tools look good too, but they rely on SoX, which by default has no mp3 features. So they require compiling SoX with Lame/MAD support first, which unfortunately is too much effort for people as lazy as me.






                                                  share|improve this answer















                                                  There is another tool recommended in this question on stackoverflow: aubio, which comes along with python modules.



                                                  I haven't tried it because I was kinda busy taking care of compiling BpmDj. Just in case anybody else finds themselves struggling similar troubles while trying, I'd like to strongly recommend to make absolutely sure:




                                                  1. having downloaded the latest release of the BpmDj sources

                                                  2. having the appropriate boost libraries installed


                                                  With the latest g++ compiler upgrades, some issues seem to have arisen especially concerning recent debian and ubuntu releases. As soon as he became aware of these problems, the author had the kindness to fix the emerged incompatibilities and put together a new release which now compiles like a charm. So anybody who have been close to falling into despair over relentless compile errors lately: you are save now.



                                                  @mmx, your tools look good too, but they rely on SoX, which by default has no mp3 features. So they require compiling SoX with Lame/MAD support first, which unfortunately is too much effort for people as lazy as me.







                                                  share|improve this answer














                                                  share|improve this answer



                                                  share|improve this answer








                                                  edited May 23 '17 at 12:41









                                                  Community

                                                  1




                                                  1










                                                  answered Dec 28 '12 at 17:23









                                                  J. KatzwinkelJ. Katzwinkel

                                                  1011




                                                  1011























                                                      0














                                                      To get @meridius' solution working on my Mac I had to do a bit of extra legwork and modify the script a bit:



                                                      # Let's install bpm-tools
                                                      git clone http://www.pogo.org.uk/~mark/bpm-tools.git
                                                      cd bpm-tools
                                                      make && make install
                                                      # There will be errors, but they did not affect the result

                                                      # The following three lines could be replaced by including this directory in your $PATH
                                                      ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
                                                      ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
                                                      ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
                                                      cd ..

                                                      # Time to install a bunch of GNU tools
                                                      # Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
                                                      brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
                                                      brew tap homebrew/dupes; brew install grep

                                                      # Now for Mutagen (contains mid3v2)
                                                      git clone https://github.com/nex3/mutagen.git
                                                      cd mutagen
                                                      ./setup.py build
                                                      sudo ./setup.py install
                                                      # There will be errors, but they did not affect the result
                                                      cd ..


                                                      Then I had to modify the script to point to the GNU versions of everything, and a few other tweaks:



                                                      #!/usr/local/bin/bash

                                                      # ================================= FUNCTIONS =================================

                                                      function help() {
                                                      less <<< 'BPMWRAP

                                                      Description:
                                                      This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
                                                      audio tagging utilities. The purpose is to make BPM (beats per minute)
                                                      tagging as easy as possible.
                                                      Default behaviour is to look through working directory for *.mp3 files
                                                      and compute and print their BPM in the following manner:
                                                      [current (if any)] [computed] [filename]

                                                      Usage:
                                                      bpmwrap [options] [directory or filenames]

                                                      Options:
                                                      You can specify files to process by one of these ways:
                                                      1) state files and/or directories containing them after options
                                                      2) specify --import file
                                                      3) specify --input file
                                                      With either way you still can filter the resulting list using --type option(s).
                                                      Remember that the script will process only mp3 files by default, unless
                                                      specified otherwise!

                                                      -i, --import file
                                                      Use this option to set BPM tag for all files in given file instead of
                                                      computing it. Expected format of every row is BPM number and absolute path
                                                      to filename separated by semicolon like so:
                                                      145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
                                                      Remember to use --write option too.
                                                      -n, --input file
                                                      Use this option to give the script list of FILES to process INSTEAD of paths
                                                      where to look for them. Each row whould have one absolute path.
                                                      This will bypass the searching part and is that way useful when you want
                                                      to process large number of files several times. Like when you are not yet
                                                      sure what BPM limits to set. Extension filtering will still work.
                                                      -o, --output file
                                                      Save output also to a file.
                                                      -l, --list-save file
                                                      Save list of files about to get processed. You can use this list later
                                                      as a file for --input option.
                                                      -t, --type filetype
                                                      Extension of file type to work with. Defaults to mp3. Can be specified
                                                      multiple times for more filetypes. Currently supported are mp3 ogg flac.
                                                      -e, --existing-only
                                                      Only show BPM for files that have it. Do NOT compute new one.
                                                      -w, --write
                                                      Write computed BPM to audio file but do NOT overwrite existing value.
                                                      -f, --force
                                                      Write computed BPM to audio file even if it already has one. Aplicable only
                                                      with --write option.
                                                      -m, --min minbpm
                                                      Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
                                                      -x, --max maxbpm
                                                      Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
                                                      -v, --verbose
                                                      Show "progress" messages.
                                                      -c, --csv-friendly
                                                      Use semicolon (;) instead of space to separate output columns.
                                                      -h, --help
                                                      Show this help.

                                                      Note:
                                                      Program bpm-tag (on whis is this script based) is looking only for lowercase
                                                      file extensions. If you get 0 (zero) BPM, this should be the case. So just
                                                      rename the file.

                                                      License:
                                                      GPL V2

                                                      Links:
                                                      bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)

                                                      Dependencies:
                                                      bpm-tag mid3v2 vorbiscomment metaflac

                                                      Author:
                                                      Martin Lukeš (martin.meridius@gmail.com)
                                                      Based on work of kolypto (http://superuser.com/a/129157/137326)
                                                      '
                                                      }

                                                      # Usage: result=$(inArray $needle haystack[@])
                                                      # @param string needle
                                                      # @param array haystack
                                                      # @returns int (1 = NOT / 0 = IS) in array
                                                      function inArray() {
                                                      needle="$1"
                                                      haystack=("${!2}")
                                                      out=1
                                                      for e in "${haystack[@]}" ; do
                                                      if [[ "$e" = "$needle" ]] ; then
                                                      out=0
                                                      break
                                                      fi
                                                      done
                                                      echo $out
                                                      }

                                                      # Usage: result=$(implode $separator array[@])
                                                      # @param char separator
                                                      # @param array array to implode
                                                      # @returns string separated array elements
                                                      function implode() {
                                                      separator="$1"
                                                      array=("${!2}")
                                                      IFSORIG=$IFS
                                                      IFS="$separator"
                                                      echo "${array[*]}"
                                                      IFS=$IFSORIG
                                                      }

                                                      # @param string file
                                                      # @returns int BPM value
                                                      function getBpm() {
                                                      local file="$1"
                                                      local ext="${file##*.}"
                                                      declare -l ext # convert to lowercase
                                                      { case "$ext" in
                                                      'mp3') mid3v2 -l "$file" ;;
                                                      'ogg') vorbiscomment -l "$file" ;;
                                                      'flac') metaflac --export-tags-to=- "$file" ;;
                                                      esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
                                                      }

                                                      # @param string file
                                                      # @param int BPM value
                                                      function setBpm() {
                                                      local file="$1"
                                                      local bpm="${2%%.*}"
                                                      local ext="${file##*.}"
                                                      declare -l ext # convert to lowercase
                                                      case "$ext" in
                                                      'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
                                                      'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
                                                      'flac') metaflac --set-tag="BPM=$bpm" "$file"
                                                      mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
                                                      ;;
                                                      esac
                                                      }

                                                      # # @param string file
                                                      # # @returns int BPM value
                                                      function computeBpm() {
                                                      local file="$1"
                                                      local m_opt=""
                                                      [ ! -z "$m" ] && m_opt="-m $m"
                                                      local x_opt=""
                                                      [ ! -z "$x" ] && x_opt="-x $x"
                                                      local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
                                                      echo $(echo "$row"
                                                      | gsed -r 's/.+ ([0-9]+.[0-9]{3}) BPM/1/'
                                                      | gawk '{printf("%.0fn", $1)}')
                                                      }

                                                      # @param string file
                                                      # @param int file number
                                                      # @param int BPM from file list given by --import option
                                                      function oneThread() {
                                                      local file="$1"
                                                      local filenumber="$2"
                                                      local bpm_hard="$3"
                                                      local bpm_old=$(getBpm "$file")
                                                      [ -z "$bpm_old" ] && bpm_old="NONE"
                                                      if [ "$e" ] ; then # only show existing
                                                      myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
                                                      else # compute new one
                                                      if [ "$bpm_hard" ] ; then
                                                      local bpm_new="$bpm_hard"
                                                      else
                                                      local bpm_new=$(computeBpm "$file")
                                                      fi
                                                      [ "$w" ] && { # write new one
                                                      if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
                                                      setBpm "$file" "$bpm_new"
                                                      else
                                                      [ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
                                                      fi
                                                      }
                                                      myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
                                                      fi
                                                      }

                                                      function myEcho() {
                                                      [ "$o" ] && echo -e "$1" >> "$o"
                                                      echo -e "$1"
                                                      }


                                                      # ================================== OPTIONS ==================================

                                                      eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch"
                                                      -l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")

                                                      declare i n o l t e w f m x v c h
                                                      declare -a INPUTFILES
                                                      declare -a INPUTTYPES
                                                      while [ $# -gt 0 ] ; do
                                                      case "$1" in
                                                      -i|--import) shift ; i="$1" ; shift ;;
                                                      -n|--input) shift ; n="$1" ; shift ;;
                                                      -o|--output) shift ; o="$1" ; shift ;;
                                                      -l|--list-save) shift ; l="$1" ; shift ;;
                                                      -t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
                                                      -e|--existing-only) e=1 ; shift ;;
                                                      -w|--write) w=1 ; shift ;;
                                                      -f|--force) f=1 ; shift ;;
                                                      -m|--min) shift ; m="$1" ; shift ;;
                                                      -x|--max) shift ; x="$1" ; shift ;;
                                                      -v|--verbose) v=1 ; shift ;;
                                                      -c|--csv-friendly) c=1 ; shift ;;
                                                      -h|--help) h=1 ; shift ;;
                                                      --) shift ;;
                                                      -*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
                                                      *) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
                                                      esac
                                                      done


                                                      # ================================= DEFAULTS ==================================

                                                      #NOTE Remove what requisities you don't need but don't try to use them after!
                                                      # always mp3/flac ogg flac
                                                      REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
                                                      which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }

                                                      [ "$h" ] && {
                                                      help
                                                      exit 0
                                                      }

                                                      [[ $m && $x && ( $m -ge $x ) ]] && {
                                                      myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
                                                      exit 1
                                                      }
                                                      [[ "$i" && "$n" ]] && {
                                                      echo "You cannot specify both -i and -n options!"
                                                      exit 1
                                                      }
                                                      [[ "$i" && ( "$m" || "$x" ) ]] && {
                                                      echo "You cannot use -m nor -x option with -i option!"
                                                      exit 1
                                                      }
                                                      [ "$e" ] && {
                                                      [[ "$w" || "$f" ]] && {
                                                      echo "With -e option you don't have any value to write!"
                                                      exit 1
                                                      }
                                                      [[ "$m" || "$x" ]] && {
                                                      echo "With -e option you don't have any value to count!"
                                                      exit 1
                                                      }
                                                      }

                                                      for file in "$o" "$l" ; do
                                                      if [ -f "$file" ] ; then
                                                      while true ; do
                                                      read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
                                                      case "$key" in
                                                      y|Y|"") echo "" > "$file" ; break ;;
                                                      n|N) exit 0 ;;
                                                      esac
                                                      echo ""
                                                      done
                                                      echo ""
                                                      fi
                                                      done

                                                      [ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")

                                                      # NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
                                                      NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/2/')"
                                                      LASTPID=0
                                                      TYPESALLOWED=("mp3" "ogg" "flac")
                                                      # declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
                                                      declare -A BPMIMPORT # array of BPMs from --import file, keys are file names

                                                      for type in "${INPUTTYPES[@]}" ; do
                                                      [[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
                                                      myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
                                                      exit 1
                                                      }
                                                      done

                                                      ### here are three ways how to pass files to the script...
                                                      if [ "$i" ] ; then # just parse given file list and set BPM to listed files
                                                      if [ -f "$i" ] ; then
                                                      # myEcho "Setting BPM tags from given file ..."
                                                      while read row ; do
                                                      bpm="${row%%;*}"
                                                      file="${row#*;}"
                                                      ext="${file##*.}"
                                                      ext="${ext,,}" # convert to lowercase
                                                      if [ -f "$file" ] ; then
                                                      if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                      FILES=("${FILES[@]}" "$file")
                                                      BPMIMPORT["$file"]="$bpm"
                                                      else
                                                      myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                      fi
                                                      else
                                                      myEcho "Skipping non-existing file $file"
                                                      fi
                                                      done < "$i"
                                                      else
                                                      myEcho "Given import file does not exists!"
                                                      exit 1
                                                      fi
                                                      elif [ "$n" ] ; then # get files from file list
                                                      if [ -f "$n" ] ; then
                                                      rownumber=1
                                                      while read file ; do
                                                      if [ -f "$file" ] ; then
                                                      ext="${file##*.}"
                                                      ext="${ext,,}" # convert to lowercase
                                                      if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                      FILES=("${FILES[@]}" "$file")
                                                      else
                                                      myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                      fi
                                                      else
                                                      myEcho "Skipping file on row $rownumber (non-existing) ... $file"
                                                      fi
                                                      let rownumber++
                                                      done < "$n"
                                                      unset rownumber
                                                      else
                                                      myEcho "Given input file $n does not exists!"
                                                      exit 1
                                                      fi
                                                      else # get files from given parameters
                                                      [ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
                                                      for file in "${INPUTFILES[@]}" ; do
                                                      [ ! -e "$file" ] && {
                                                      myEcho "File or directory $file does not exist!"
                                                      exit 1
                                                      }
                                                      done
                                                      impl_types=`implode "|" INPUTTYPES[@]`
                                                      while read file ; do
                                                      echo -ne "Creating list of files ... (${#FILES[@]}) ${file}33[0K"\r
                                                      FILES=("${FILES[@]}" "$file")
                                                      done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*.($impl_types)")
                                                      echo -e "Counted ${#FILES[@]} files33[0K"\r
                                                      fi

                                                      [ "$l" ] && printf '%sn' "${FILES[@]}" > "$l"

                                                      NUMFILES=${#FILES[@]}
                                                      FILENUMBER=1

                                                      [ $NUMFILES -eq 0 ] && {
                                                      myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
                                                      exit 1
                                                      }

                                                      declare SEP=" "
                                                      [ "$c" ] && SEP=";"


                                                      # =============================== MAIN SECTION ================================

                                                      if [ "$e" ] ; then # what heading to show
                                                      myEcho "num${SEP}old${SEP}filename"
                                                      else
                                                      myEcho "num${SEP}old${SEP}new${SEP}filename"
                                                      fi

                                                      for file in "${FILES[@]}" ; do
                                                      [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
                                                      [ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})t$file ..."
                                                      oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
                                                      LASTPID="$!"
                                                      let FILENUMBER++
                                                      done

                                                      [ "$v" ] && myEcho "Waiting for last process ..."
                                                      wait $LASTPID
                                                      [ "$v" ] && myEcho \n"DONE"


                                                      Thank you for your hard work @kolypto and @meridius.



                                                      ...the pain I go through to maintain a CLI workflow and pay no money for music tools...






                                                      share|improve this answer




























                                                        0














                                                        To get @meridius' solution working on my Mac I had to do a bit of extra legwork and modify the script a bit:



                                                        # Let's install bpm-tools
                                                        git clone http://www.pogo.org.uk/~mark/bpm-tools.git
                                                        cd bpm-tools
                                                        make && make install
                                                        # There will be errors, but they did not affect the result

                                                        # The following three lines could be replaced by including this directory in your $PATH
                                                        ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
                                                        ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
                                                        ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
                                                        cd ..

                                                        # Time to install a bunch of GNU tools
                                                        # Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
                                                        brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
                                                        brew tap homebrew/dupes; brew install grep

                                                        # Now for Mutagen (contains mid3v2)
                                                        git clone https://github.com/nex3/mutagen.git
                                                        cd mutagen
                                                        ./setup.py build
                                                        sudo ./setup.py install
                                                        # There will be errors, but they did not affect the result
                                                        cd ..


                                                        Then I had to modify the script to point to the GNU versions of everything, and a few other tweaks:



                                                        #!/usr/local/bin/bash

                                                        # ================================= FUNCTIONS =================================

                                                        function help() {
                                                        less <<< 'BPMWRAP

                                                        Description:
                                                        This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
                                                        audio tagging utilities. The purpose is to make BPM (beats per minute)
                                                        tagging as easy as possible.
                                                        Default behaviour is to look through working directory for *.mp3 files
                                                        and compute and print their BPM in the following manner:
                                                        [current (if any)] [computed] [filename]

                                                        Usage:
                                                        bpmwrap [options] [directory or filenames]

                                                        Options:
                                                        You can specify files to process by one of these ways:
                                                        1) state files and/or directories containing them after options
                                                        2) specify --import file
                                                        3) specify --input file
                                                        With either way you still can filter the resulting list using --type option(s).
                                                        Remember that the script will process only mp3 files by default, unless
                                                        specified otherwise!

                                                        -i, --import file
                                                        Use this option to set BPM tag for all files in given file instead of
                                                        computing it. Expected format of every row is BPM number and absolute path
                                                        to filename separated by semicolon like so:
                                                        145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
                                                        Remember to use --write option too.
                                                        -n, --input file
                                                        Use this option to give the script list of FILES to process INSTEAD of paths
                                                        where to look for them. Each row whould have one absolute path.
                                                        This will bypass the searching part and is that way useful when you want
                                                        to process large number of files several times. Like when you are not yet
                                                        sure what BPM limits to set. Extension filtering will still work.
                                                        -o, --output file
                                                        Save output also to a file.
                                                        -l, --list-save file
                                                        Save list of files about to get processed. You can use this list later
                                                        as a file for --input option.
                                                        -t, --type filetype
                                                        Extension of file type to work with. Defaults to mp3. Can be specified
                                                        multiple times for more filetypes. Currently supported are mp3 ogg flac.
                                                        -e, --existing-only
                                                        Only show BPM for files that have it. Do NOT compute new one.
                                                        -w, --write
                                                        Write computed BPM to audio file but do NOT overwrite existing value.
                                                        -f, --force
                                                        Write computed BPM to audio file even if it already has one. Aplicable only
                                                        with --write option.
                                                        -m, --min minbpm
                                                        Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
                                                        -x, --max maxbpm
                                                        Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
                                                        -v, --verbose
                                                        Show "progress" messages.
                                                        -c, --csv-friendly
                                                        Use semicolon (;) instead of space to separate output columns.
                                                        -h, --help
                                                        Show this help.

                                                        Note:
                                                        Program bpm-tag (on whis is this script based) is looking only for lowercase
                                                        file extensions. If you get 0 (zero) BPM, this should be the case. So just
                                                        rename the file.

                                                        License:
                                                        GPL V2

                                                        Links:
                                                        bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)

                                                        Dependencies:
                                                        bpm-tag mid3v2 vorbiscomment metaflac

                                                        Author:
                                                        Martin Lukeš (martin.meridius@gmail.com)
                                                        Based on work of kolypto (http://superuser.com/a/129157/137326)
                                                        '
                                                        }

                                                        # Usage: result=$(inArray $needle haystack[@])
                                                        # @param string needle
                                                        # @param array haystack
                                                        # @returns int (1 = NOT / 0 = IS) in array
                                                        function inArray() {
                                                        needle="$1"
                                                        haystack=("${!2}")
                                                        out=1
                                                        for e in "${haystack[@]}" ; do
                                                        if [[ "$e" = "$needle" ]] ; then
                                                        out=0
                                                        break
                                                        fi
                                                        done
                                                        echo $out
                                                        }

                                                        # Usage: result=$(implode $separator array[@])
                                                        # @param char separator
                                                        # @param array array to implode
                                                        # @returns string separated array elements
                                                        function implode() {
                                                        separator="$1"
                                                        array=("${!2}")
                                                        IFSORIG=$IFS
                                                        IFS="$separator"
                                                        echo "${array[*]}"
                                                        IFS=$IFSORIG
                                                        }

                                                        # @param string file
                                                        # @returns int BPM value
                                                        function getBpm() {
                                                        local file="$1"
                                                        local ext="${file##*.}"
                                                        declare -l ext # convert to lowercase
                                                        { case "$ext" in
                                                        'mp3') mid3v2 -l "$file" ;;
                                                        'ogg') vorbiscomment -l "$file" ;;
                                                        'flac') metaflac --export-tags-to=- "$file" ;;
                                                        esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
                                                        }

                                                        # @param string file
                                                        # @param int BPM value
                                                        function setBpm() {
                                                        local file="$1"
                                                        local bpm="${2%%.*}"
                                                        local ext="${file##*.}"
                                                        declare -l ext # convert to lowercase
                                                        case "$ext" in
                                                        'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
                                                        'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
                                                        'flac') metaflac --set-tag="BPM=$bpm" "$file"
                                                        mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
                                                        ;;
                                                        esac
                                                        }

                                                        # # @param string file
                                                        # # @returns int BPM value
                                                        function computeBpm() {
                                                        local file="$1"
                                                        local m_opt=""
                                                        [ ! -z "$m" ] && m_opt="-m $m"
                                                        local x_opt=""
                                                        [ ! -z "$x" ] && x_opt="-x $x"
                                                        local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
                                                        echo $(echo "$row"
                                                        | gsed -r 's/.+ ([0-9]+.[0-9]{3}) BPM/1/'
                                                        | gawk '{printf("%.0fn", $1)}')
                                                        }

                                                        # @param string file
                                                        # @param int file number
                                                        # @param int BPM from file list given by --import option
                                                        function oneThread() {
                                                        local file="$1"
                                                        local filenumber="$2"
                                                        local bpm_hard="$3"
                                                        local bpm_old=$(getBpm "$file")
                                                        [ -z "$bpm_old" ] && bpm_old="NONE"
                                                        if [ "$e" ] ; then # only show existing
                                                        myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
                                                        else # compute new one
                                                        if [ "$bpm_hard" ] ; then
                                                        local bpm_new="$bpm_hard"
                                                        else
                                                        local bpm_new=$(computeBpm "$file")
                                                        fi
                                                        [ "$w" ] && { # write new one
                                                        if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
                                                        setBpm "$file" "$bpm_new"
                                                        else
                                                        [ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
                                                        fi
                                                        }
                                                        myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
                                                        fi
                                                        }

                                                        function myEcho() {
                                                        [ "$o" ] && echo -e "$1" >> "$o"
                                                        echo -e "$1"
                                                        }


                                                        # ================================== OPTIONS ==================================

                                                        eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch"
                                                        -l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")

                                                        declare i n o l t e w f m x v c h
                                                        declare -a INPUTFILES
                                                        declare -a INPUTTYPES
                                                        while [ $# -gt 0 ] ; do
                                                        case "$1" in
                                                        -i|--import) shift ; i="$1" ; shift ;;
                                                        -n|--input) shift ; n="$1" ; shift ;;
                                                        -o|--output) shift ; o="$1" ; shift ;;
                                                        -l|--list-save) shift ; l="$1" ; shift ;;
                                                        -t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
                                                        -e|--existing-only) e=1 ; shift ;;
                                                        -w|--write) w=1 ; shift ;;
                                                        -f|--force) f=1 ; shift ;;
                                                        -m|--min) shift ; m="$1" ; shift ;;
                                                        -x|--max) shift ; x="$1" ; shift ;;
                                                        -v|--verbose) v=1 ; shift ;;
                                                        -c|--csv-friendly) c=1 ; shift ;;
                                                        -h|--help) h=1 ; shift ;;
                                                        --) shift ;;
                                                        -*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
                                                        *) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
                                                        esac
                                                        done


                                                        # ================================= DEFAULTS ==================================

                                                        #NOTE Remove what requisities you don't need but don't try to use them after!
                                                        # always mp3/flac ogg flac
                                                        REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
                                                        which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }

                                                        [ "$h" ] && {
                                                        help
                                                        exit 0
                                                        }

                                                        [[ $m && $x && ( $m -ge $x ) ]] && {
                                                        myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
                                                        exit 1
                                                        }
                                                        [[ "$i" && "$n" ]] && {
                                                        echo "You cannot specify both -i and -n options!"
                                                        exit 1
                                                        }
                                                        [[ "$i" && ( "$m" || "$x" ) ]] && {
                                                        echo "You cannot use -m nor -x option with -i option!"
                                                        exit 1
                                                        }
                                                        [ "$e" ] && {
                                                        [[ "$w" || "$f" ]] && {
                                                        echo "With -e option you don't have any value to write!"
                                                        exit 1
                                                        }
                                                        [[ "$m" || "$x" ]] && {
                                                        echo "With -e option you don't have any value to count!"
                                                        exit 1
                                                        }
                                                        }

                                                        for file in "$o" "$l" ; do
                                                        if [ -f "$file" ] ; then
                                                        while true ; do
                                                        read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
                                                        case "$key" in
                                                        y|Y|"") echo "" > "$file" ; break ;;
                                                        n|N) exit 0 ;;
                                                        esac
                                                        echo ""
                                                        done
                                                        echo ""
                                                        fi
                                                        done

                                                        [ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")

                                                        # NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
                                                        NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/2/')"
                                                        LASTPID=0
                                                        TYPESALLOWED=("mp3" "ogg" "flac")
                                                        # declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
                                                        declare -A BPMIMPORT # array of BPMs from --import file, keys are file names

                                                        for type in "${INPUTTYPES[@]}" ; do
                                                        [[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
                                                        myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
                                                        exit 1
                                                        }
                                                        done

                                                        ### here are three ways how to pass files to the script...
                                                        if [ "$i" ] ; then # just parse given file list and set BPM to listed files
                                                        if [ -f "$i" ] ; then
                                                        # myEcho "Setting BPM tags from given file ..."
                                                        while read row ; do
                                                        bpm="${row%%;*}"
                                                        file="${row#*;}"
                                                        ext="${file##*.}"
                                                        ext="${ext,,}" # convert to lowercase
                                                        if [ -f "$file" ] ; then
                                                        if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                        FILES=("${FILES[@]}" "$file")
                                                        BPMIMPORT["$file"]="$bpm"
                                                        else
                                                        myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                        fi
                                                        else
                                                        myEcho "Skipping non-existing file $file"
                                                        fi
                                                        done < "$i"
                                                        else
                                                        myEcho "Given import file does not exists!"
                                                        exit 1
                                                        fi
                                                        elif [ "$n" ] ; then # get files from file list
                                                        if [ -f "$n" ] ; then
                                                        rownumber=1
                                                        while read file ; do
                                                        if [ -f "$file" ] ; then
                                                        ext="${file##*.}"
                                                        ext="${ext,,}" # convert to lowercase
                                                        if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                        FILES=("${FILES[@]}" "$file")
                                                        else
                                                        myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                        fi
                                                        else
                                                        myEcho "Skipping file on row $rownumber (non-existing) ... $file"
                                                        fi
                                                        let rownumber++
                                                        done < "$n"
                                                        unset rownumber
                                                        else
                                                        myEcho "Given input file $n does not exists!"
                                                        exit 1
                                                        fi
                                                        else # get files from given parameters
                                                        [ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
                                                        for file in "${INPUTFILES[@]}" ; do
                                                        [ ! -e "$file" ] && {
                                                        myEcho "File or directory $file does not exist!"
                                                        exit 1
                                                        }
                                                        done
                                                        impl_types=`implode "|" INPUTTYPES[@]`
                                                        while read file ; do
                                                        echo -ne "Creating list of files ... (${#FILES[@]}) ${file}33[0K"\r
                                                        FILES=("${FILES[@]}" "$file")
                                                        done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*.($impl_types)")
                                                        echo -e "Counted ${#FILES[@]} files33[0K"\r
                                                        fi

                                                        [ "$l" ] && printf '%sn' "${FILES[@]}" > "$l"

                                                        NUMFILES=${#FILES[@]}
                                                        FILENUMBER=1

                                                        [ $NUMFILES -eq 0 ] && {
                                                        myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
                                                        exit 1
                                                        }

                                                        declare SEP=" "
                                                        [ "$c" ] && SEP=";"


                                                        # =============================== MAIN SECTION ================================

                                                        if [ "$e" ] ; then # what heading to show
                                                        myEcho "num${SEP}old${SEP}filename"
                                                        else
                                                        myEcho "num${SEP}old${SEP}new${SEP}filename"
                                                        fi

                                                        for file in "${FILES[@]}" ; do
                                                        [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
                                                        [ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})t$file ..."
                                                        oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
                                                        LASTPID="$!"
                                                        let FILENUMBER++
                                                        done

                                                        [ "$v" ] && myEcho "Waiting for last process ..."
                                                        wait $LASTPID
                                                        [ "$v" ] && myEcho \n"DONE"


                                                        Thank you for your hard work @kolypto and @meridius.



                                                        ...the pain I go through to maintain a CLI workflow and pay no money for music tools...






                                                        share|improve this answer


























                                                          0












                                                          0








                                                          0







                                                          To get @meridius' solution working on my Mac I had to do a bit of extra legwork and modify the script a bit:



                                                          # Let's install bpm-tools
                                                          git clone http://www.pogo.org.uk/~mark/bpm-tools.git
                                                          cd bpm-tools
                                                          make && make install
                                                          # There will be errors, but they did not affect the result

                                                          # The following three lines could be replaced by including this directory in your $PATH
                                                          ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
                                                          ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
                                                          ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
                                                          cd ..

                                                          # Time to install a bunch of GNU tools
                                                          # Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
                                                          brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
                                                          brew tap homebrew/dupes; brew install grep

                                                          # Now for Mutagen (contains mid3v2)
                                                          git clone https://github.com/nex3/mutagen.git
                                                          cd mutagen
                                                          ./setup.py build
                                                          sudo ./setup.py install
                                                          # There will be errors, but they did not affect the result
                                                          cd ..


                                                          Then I had to modify the script to point to the GNU versions of everything, and a few other tweaks:



                                                          #!/usr/local/bin/bash

                                                          # ================================= FUNCTIONS =================================

                                                          function help() {
                                                          less <<< 'BPMWRAP

                                                          Description:
                                                          This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
                                                          audio tagging utilities. The purpose is to make BPM (beats per minute)
                                                          tagging as easy as possible.
                                                          Default behaviour is to look through working directory for *.mp3 files
                                                          and compute and print their BPM in the following manner:
                                                          [current (if any)] [computed] [filename]

                                                          Usage:
                                                          bpmwrap [options] [directory or filenames]

                                                          Options:
                                                          You can specify files to process by one of these ways:
                                                          1) state files and/or directories containing them after options
                                                          2) specify --import file
                                                          3) specify --input file
                                                          With either way you still can filter the resulting list using --type option(s).
                                                          Remember that the script will process only mp3 files by default, unless
                                                          specified otherwise!

                                                          -i, --import file
                                                          Use this option to set BPM tag for all files in given file instead of
                                                          computing it. Expected format of every row is BPM number and absolute path
                                                          to filename separated by semicolon like so:
                                                          145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
                                                          Remember to use --write option too.
                                                          -n, --input file
                                                          Use this option to give the script list of FILES to process INSTEAD of paths
                                                          where to look for them. Each row whould have one absolute path.
                                                          This will bypass the searching part and is that way useful when you want
                                                          to process large number of files several times. Like when you are not yet
                                                          sure what BPM limits to set. Extension filtering will still work.
                                                          -o, --output file
                                                          Save output also to a file.
                                                          -l, --list-save file
                                                          Save list of files about to get processed. You can use this list later
                                                          as a file for --input option.
                                                          -t, --type filetype
                                                          Extension of file type to work with. Defaults to mp3. Can be specified
                                                          multiple times for more filetypes. Currently supported are mp3 ogg flac.
                                                          -e, --existing-only
                                                          Only show BPM for files that have it. Do NOT compute new one.
                                                          -w, --write
                                                          Write computed BPM to audio file but do NOT overwrite existing value.
                                                          -f, --force
                                                          Write computed BPM to audio file even if it already has one. Aplicable only
                                                          with --write option.
                                                          -m, --min minbpm
                                                          Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
                                                          -x, --max maxbpm
                                                          Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
                                                          -v, --verbose
                                                          Show "progress" messages.
                                                          -c, --csv-friendly
                                                          Use semicolon (;) instead of space to separate output columns.
                                                          -h, --help
                                                          Show this help.

                                                          Note:
                                                          Program bpm-tag (on whis is this script based) is looking only for lowercase
                                                          file extensions. If you get 0 (zero) BPM, this should be the case. So just
                                                          rename the file.

                                                          License:
                                                          GPL V2

                                                          Links:
                                                          bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)

                                                          Dependencies:
                                                          bpm-tag mid3v2 vorbiscomment metaflac

                                                          Author:
                                                          Martin Lukeš (martin.meridius@gmail.com)
                                                          Based on work of kolypto (http://superuser.com/a/129157/137326)
                                                          '
                                                          }

                                                          # Usage: result=$(inArray $needle haystack[@])
                                                          # @param string needle
                                                          # @param array haystack
                                                          # @returns int (1 = NOT / 0 = IS) in array
                                                          function inArray() {
                                                          needle="$1"
                                                          haystack=("${!2}")
                                                          out=1
                                                          for e in "${haystack[@]}" ; do
                                                          if [[ "$e" = "$needle" ]] ; then
                                                          out=0
                                                          break
                                                          fi
                                                          done
                                                          echo $out
                                                          }

                                                          # Usage: result=$(implode $separator array[@])
                                                          # @param char separator
                                                          # @param array array to implode
                                                          # @returns string separated array elements
                                                          function implode() {
                                                          separator="$1"
                                                          array=("${!2}")
                                                          IFSORIG=$IFS
                                                          IFS="$separator"
                                                          echo "${array[*]}"
                                                          IFS=$IFSORIG
                                                          }

                                                          # @param string file
                                                          # @returns int BPM value
                                                          function getBpm() {
                                                          local file="$1"
                                                          local ext="${file##*.}"
                                                          declare -l ext # convert to lowercase
                                                          { case "$ext" in
                                                          'mp3') mid3v2 -l "$file" ;;
                                                          'ogg') vorbiscomment -l "$file" ;;
                                                          'flac') metaflac --export-tags-to=- "$file" ;;
                                                          esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
                                                          }

                                                          # @param string file
                                                          # @param int BPM value
                                                          function setBpm() {
                                                          local file="$1"
                                                          local bpm="${2%%.*}"
                                                          local ext="${file##*.}"
                                                          declare -l ext # convert to lowercase
                                                          case "$ext" in
                                                          'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
                                                          'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
                                                          'flac') metaflac --set-tag="BPM=$bpm" "$file"
                                                          mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
                                                          ;;
                                                          esac
                                                          }

                                                          # # @param string file
                                                          # # @returns int BPM value
                                                          function computeBpm() {
                                                          local file="$1"
                                                          local m_opt=""
                                                          [ ! -z "$m" ] && m_opt="-m $m"
                                                          local x_opt=""
                                                          [ ! -z "$x" ] && x_opt="-x $x"
                                                          local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
                                                          echo $(echo "$row"
                                                          | gsed -r 's/.+ ([0-9]+.[0-9]{3}) BPM/1/'
                                                          | gawk '{printf("%.0fn", $1)}')
                                                          }

                                                          # @param string file
                                                          # @param int file number
                                                          # @param int BPM from file list given by --import option
                                                          function oneThread() {
                                                          local file="$1"
                                                          local filenumber="$2"
                                                          local bpm_hard="$3"
                                                          local bpm_old=$(getBpm "$file")
                                                          [ -z "$bpm_old" ] && bpm_old="NONE"
                                                          if [ "$e" ] ; then # only show existing
                                                          myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
                                                          else # compute new one
                                                          if [ "$bpm_hard" ] ; then
                                                          local bpm_new="$bpm_hard"
                                                          else
                                                          local bpm_new=$(computeBpm "$file")
                                                          fi
                                                          [ "$w" ] && { # write new one
                                                          if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
                                                          setBpm "$file" "$bpm_new"
                                                          else
                                                          [ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
                                                          fi
                                                          }
                                                          myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
                                                          fi
                                                          }

                                                          function myEcho() {
                                                          [ "$o" ] && echo -e "$1" >> "$o"
                                                          echo -e "$1"
                                                          }


                                                          # ================================== OPTIONS ==================================

                                                          eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch"
                                                          -l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")

                                                          declare i n o l t e w f m x v c h
                                                          declare -a INPUTFILES
                                                          declare -a INPUTTYPES
                                                          while [ $# -gt 0 ] ; do
                                                          case "$1" in
                                                          -i|--import) shift ; i="$1" ; shift ;;
                                                          -n|--input) shift ; n="$1" ; shift ;;
                                                          -o|--output) shift ; o="$1" ; shift ;;
                                                          -l|--list-save) shift ; l="$1" ; shift ;;
                                                          -t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
                                                          -e|--existing-only) e=1 ; shift ;;
                                                          -w|--write) w=1 ; shift ;;
                                                          -f|--force) f=1 ; shift ;;
                                                          -m|--min) shift ; m="$1" ; shift ;;
                                                          -x|--max) shift ; x="$1" ; shift ;;
                                                          -v|--verbose) v=1 ; shift ;;
                                                          -c|--csv-friendly) c=1 ; shift ;;
                                                          -h|--help) h=1 ; shift ;;
                                                          --) shift ;;
                                                          -*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
                                                          *) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
                                                          esac
                                                          done


                                                          # ================================= DEFAULTS ==================================

                                                          #NOTE Remove what requisities you don't need but don't try to use them after!
                                                          # always mp3/flac ogg flac
                                                          REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
                                                          which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }

                                                          [ "$h" ] && {
                                                          help
                                                          exit 0
                                                          }

                                                          [[ $m && $x && ( $m -ge $x ) ]] && {
                                                          myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
                                                          exit 1
                                                          }
                                                          [[ "$i" && "$n" ]] && {
                                                          echo "You cannot specify both -i and -n options!"
                                                          exit 1
                                                          }
                                                          [[ "$i" && ( "$m" || "$x" ) ]] && {
                                                          echo "You cannot use -m nor -x option with -i option!"
                                                          exit 1
                                                          }
                                                          [ "$e" ] && {
                                                          [[ "$w" || "$f" ]] && {
                                                          echo "With -e option you don't have any value to write!"
                                                          exit 1
                                                          }
                                                          [[ "$m" || "$x" ]] && {
                                                          echo "With -e option you don't have any value to count!"
                                                          exit 1
                                                          }
                                                          }

                                                          for file in "$o" "$l" ; do
                                                          if [ -f "$file" ] ; then
                                                          while true ; do
                                                          read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
                                                          case "$key" in
                                                          y|Y|"") echo "" > "$file" ; break ;;
                                                          n|N) exit 0 ;;
                                                          esac
                                                          echo ""
                                                          done
                                                          echo ""
                                                          fi
                                                          done

                                                          [ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")

                                                          # NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
                                                          NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/2/')"
                                                          LASTPID=0
                                                          TYPESALLOWED=("mp3" "ogg" "flac")
                                                          # declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
                                                          declare -A BPMIMPORT # array of BPMs from --import file, keys are file names

                                                          for type in "${INPUTTYPES[@]}" ; do
                                                          [[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
                                                          myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
                                                          exit 1
                                                          }
                                                          done

                                                          ### here are three ways how to pass files to the script...
                                                          if [ "$i" ] ; then # just parse given file list and set BPM to listed files
                                                          if [ -f "$i" ] ; then
                                                          # myEcho "Setting BPM tags from given file ..."
                                                          while read row ; do
                                                          bpm="${row%%;*}"
                                                          file="${row#*;}"
                                                          ext="${file##*.}"
                                                          ext="${ext,,}" # convert to lowercase
                                                          if [ -f "$file" ] ; then
                                                          if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                          FILES=("${FILES[@]}" "$file")
                                                          BPMIMPORT["$file"]="$bpm"
                                                          else
                                                          myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                          fi
                                                          else
                                                          myEcho "Skipping non-existing file $file"
                                                          fi
                                                          done < "$i"
                                                          else
                                                          myEcho "Given import file does not exists!"
                                                          exit 1
                                                          fi
                                                          elif [ "$n" ] ; then # get files from file list
                                                          if [ -f "$n" ] ; then
                                                          rownumber=1
                                                          while read file ; do
                                                          if [ -f "$file" ] ; then
                                                          ext="${file##*.}"
                                                          ext="${ext,,}" # convert to lowercase
                                                          if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                          FILES=("${FILES[@]}" "$file")
                                                          else
                                                          myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                          fi
                                                          else
                                                          myEcho "Skipping file on row $rownumber (non-existing) ... $file"
                                                          fi
                                                          let rownumber++
                                                          done < "$n"
                                                          unset rownumber
                                                          else
                                                          myEcho "Given input file $n does not exists!"
                                                          exit 1
                                                          fi
                                                          else # get files from given parameters
                                                          [ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
                                                          for file in "${INPUTFILES[@]}" ; do
                                                          [ ! -e "$file" ] && {
                                                          myEcho "File or directory $file does not exist!"
                                                          exit 1
                                                          }
                                                          done
                                                          impl_types=`implode "|" INPUTTYPES[@]`
                                                          while read file ; do
                                                          echo -ne "Creating list of files ... (${#FILES[@]}) ${file}33[0K"\r
                                                          FILES=("${FILES[@]}" "$file")
                                                          done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*.($impl_types)")
                                                          echo -e "Counted ${#FILES[@]} files33[0K"\r
                                                          fi

                                                          [ "$l" ] && printf '%sn' "${FILES[@]}" > "$l"

                                                          NUMFILES=${#FILES[@]}
                                                          FILENUMBER=1

                                                          [ $NUMFILES -eq 0 ] && {
                                                          myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
                                                          exit 1
                                                          }

                                                          declare SEP=" "
                                                          [ "$c" ] && SEP=";"


                                                          # =============================== MAIN SECTION ================================

                                                          if [ "$e" ] ; then # what heading to show
                                                          myEcho "num${SEP}old${SEP}filename"
                                                          else
                                                          myEcho "num${SEP}old${SEP}new${SEP}filename"
                                                          fi

                                                          for file in "${FILES[@]}" ; do
                                                          [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
                                                          [ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})t$file ..."
                                                          oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
                                                          LASTPID="$!"
                                                          let FILENUMBER++
                                                          done

                                                          [ "$v" ] && myEcho "Waiting for last process ..."
                                                          wait $LASTPID
                                                          [ "$v" ] && myEcho \n"DONE"


                                                          Thank you for your hard work @kolypto and @meridius.



                                                          ...the pain I go through to maintain a CLI workflow and pay no money for music tools...






                                                          share|improve this answer













                                                          To get @meridius' solution working on my Mac I had to do a bit of extra legwork and modify the script a bit:



                                                          # Let's install bpm-tools
                                                          git clone http://www.pogo.org.uk/~mark/bpm-tools.git
                                                          cd bpm-tools
                                                          make && make install
                                                          # There will be errors, but they did not affect the result

                                                          # The following three lines could be replaced by including this directory in your $PATH
                                                          ln -s <absolute path to bpm-tools>/bpm /usr/local/bin/bpm
                                                          ln -s <absolute path to bpm-tools>/bpm-tag /usr/local/bin/bpm-tag
                                                          ln -s <absolute path to bpm-tools>/bpm-graph /usr/local/bin/bpm-graph
                                                          cd ..

                                                          # Time to install a bunch of GNU tools
                                                          # Not all of these packages are strictly necessary for this script, but I decided I wanted the whole GNU toolchain in order to avoid this song-and-dance in the future
                                                          brew install coreutils findutils gnu-tar gnu-sed gawk gnutls gnu-indent gnu-getopt bash flac vorbis-tools
                                                          brew tap homebrew/dupes; brew install grep

                                                          # Now for Mutagen (contains mid3v2)
                                                          git clone https://github.com/nex3/mutagen.git
                                                          cd mutagen
                                                          ./setup.py build
                                                          sudo ./setup.py install
                                                          # There will be errors, but they did not affect the result
                                                          cd ..


                                                          Then I had to modify the script to point to the GNU versions of everything, and a few other tweaks:



                                                          #!/usr/local/bin/bash

                                                          # ================================= FUNCTIONS =================================

                                                          function help() {
                                                          less <<< 'BPMWRAP

                                                          Description:
                                                          This BASH script is a wrapper for bpm-tag utility of bpm-tools and several
                                                          audio tagging utilities. The purpose is to make BPM (beats per minute)
                                                          tagging as easy as possible.
                                                          Default behaviour is to look through working directory for *.mp3 files
                                                          and compute and print their BPM in the following manner:
                                                          [current (if any)] [computed] [filename]

                                                          Usage:
                                                          bpmwrap [options] [directory or filenames]

                                                          Options:
                                                          You can specify files to process by one of these ways:
                                                          1) state files and/or directories containing them after options
                                                          2) specify --import file
                                                          3) specify --input file
                                                          With either way you still can filter the resulting list using --type option(s).
                                                          Remember that the script will process only mp3 files by default, unless
                                                          specified otherwise!

                                                          -i, --import file
                                                          Use this option to set BPM tag for all files in given file instead of
                                                          computing it. Expected format of every row is BPM number and absolute path
                                                          to filename separated by semicolon like so:
                                                          145;/home/trinity/music/Apocalyptica/07 beyond time.mp3
                                                          Remember to use --write option too.
                                                          -n, --input file
                                                          Use this option to give the script list of FILES to process INSTEAD of paths
                                                          where to look for them. Each row whould have one absolute path.
                                                          This will bypass the searching part and is that way useful when you want
                                                          to process large number of files several times. Like when you are not yet
                                                          sure what BPM limits to set. Extension filtering will still work.
                                                          -o, --output file
                                                          Save output also to a file.
                                                          -l, --list-save file
                                                          Save list of files about to get processed. You can use this list later
                                                          as a file for --input option.
                                                          -t, --type filetype
                                                          Extension of file type to work with. Defaults to mp3. Can be specified
                                                          multiple times for more filetypes. Currently supported are mp3 ogg flac.
                                                          -e, --existing-only
                                                          Only show BPM for files that have it. Do NOT compute new one.
                                                          -w, --write
                                                          Write computed BPM to audio file but do NOT overwrite existing value.
                                                          -f, --force
                                                          Write computed BPM to audio file even if it already has one. Aplicable only
                                                          with --write option.
                                                          -m, --min minbpm
                                                          Set minimal BPM to look for when computing. Defaults to bpm-tag minimum 84.
                                                          -x, --max maxbpm
                                                          Set maximal BPM to look for when computing. Defaults to bpm-tag maximum 146.
                                                          -v, --verbose
                                                          Show "progress" messages.
                                                          -c, --csv-friendly
                                                          Use semicolon (;) instead of space to separate output columns.
                                                          -h, --help
                                                          Show this help.

                                                          Note:
                                                          Program bpm-tag (on whis is this script based) is looking only for lowercase
                                                          file extensions. If you get 0 (zero) BPM, this should be the case. So just
                                                          rename the file.

                                                          License:
                                                          GPL V2

                                                          Links:
                                                          bpm-tools (http://www.pogo.org.uk/~mark/bpm-tools/)

                                                          Dependencies:
                                                          bpm-tag mid3v2 vorbiscomment metaflac

                                                          Author:
                                                          Martin Lukeš (martin.meridius@gmail.com)
                                                          Based on work of kolypto (http://superuser.com/a/129157/137326)
                                                          '
                                                          }

                                                          # Usage: result=$(inArray $needle haystack[@])
                                                          # @param string needle
                                                          # @param array haystack
                                                          # @returns int (1 = NOT / 0 = IS) in array
                                                          function inArray() {
                                                          needle="$1"
                                                          haystack=("${!2}")
                                                          out=1
                                                          for e in "${haystack[@]}" ; do
                                                          if [[ "$e" = "$needle" ]] ; then
                                                          out=0
                                                          break
                                                          fi
                                                          done
                                                          echo $out
                                                          }

                                                          # Usage: result=$(implode $separator array[@])
                                                          # @param char separator
                                                          # @param array array to implode
                                                          # @returns string separated array elements
                                                          function implode() {
                                                          separator="$1"
                                                          array=("${!2}")
                                                          IFSORIG=$IFS
                                                          IFS="$separator"
                                                          echo "${array[*]}"
                                                          IFS=$IFSORIG
                                                          }

                                                          # @param string file
                                                          # @returns int BPM value
                                                          function getBpm() {
                                                          local file="$1"
                                                          local ext="${file##*.}"
                                                          declare -l ext # convert to lowercase
                                                          { case "$ext" in
                                                          'mp3') mid3v2 -l "$file" ;;
                                                          'ogg') vorbiscomment -l "$file" ;;
                                                          'flac') metaflac --export-tags-to=- "$file" ;;
                                                          esac ; } | fgrep 'BPM=' -a | cut -d'=' -f2
                                                          }

                                                          # @param string file
                                                          # @param int BPM value
                                                          function setBpm() {
                                                          local file="$1"
                                                          local bpm="${2%%.*}"
                                                          local ext="${file##*.}"
                                                          declare -l ext # convert to lowercase
                                                          case "$ext" in
                                                          'mp3') mid3v2 --TBPM "$bpm" "$file" ;;
                                                          'ogg') vorbiscomment -a -t "BPM=$bpm" "$file" ;;
                                                          'flac') metaflac --set-tag="BPM=$bpm" "$file"
                                                          mid3v2 --TBPM "$bpm" "$file" # Need to store to ID3 as well :(
                                                          ;;
                                                          esac
                                                          }

                                                          # # @param string file
                                                          # # @returns int BPM value
                                                          function computeBpm() {
                                                          local file="$1"
                                                          local m_opt=""
                                                          [ ! -z "$m" ] && m_opt="-m $m"
                                                          local x_opt=""
                                                          [ ! -z "$x" ] && x_opt="-x $x"
                                                          local row=$(bpm-tag -fn $m_opt $x_opt "$file" 2>&1 | fgrep "$file")
                                                          echo $(echo "$row"
                                                          | gsed -r 's/.+ ([0-9]+.[0-9]{3}) BPM/1/'
                                                          | gawk '{printf("%.0fn", $1)}')
                                                          }

                                                          # @param string file
                                                          # @param int file number
                                                          # @param int BPM from file list given by --import option
                                                          function oneThread() {
                                                          local file="$1"
                                                          local filenumber="$2"
                                                          local bpm_hard="$3"
                                                          local bpm_old=$(getBpm "$file")
                                                          [ -z "$bpm_old" ] && bpm_old="NONE"
                                                          if [ "$e" ] ; then # only show existing
                                                          myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$file"
                                                          else # compute new one
                                                          if [ "$bpm_hard" ] ; then
                                                          local bpm_new="$bpm_hard"
                                                          else
                                                          local bpm_new=$(computeBpm "$file")
                                                          fi
                                                          [ "$w" ] && { # write new one
                                                          if [[ ! ( ("$bpm_old" != "NONE") && ( -z "$f" ) ) ]] ; then
                                                          setBpm "$file" "$bpm_new"
                                                          else
                                                          [ "$v" ] && myEcho "Non-empty old BPM value, skipping ..."
                                                          fi
                                                          }
                                                          myEcho "$filenumber/$NUMFILES${SEP}$bpm_old${SEP}$bpm_new${SEP}$file"
                                                          fi
                                                          }

                                                          function myEcho() {
                                                          [ "$o" ] && echo -e "$1" >> "$o"
                                                          echo -e "$1"
                                                          }


                                                          # ================================== OPTIONS ==================================

                                                          eval set -- $(/usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt -n $0 -o "-i:n:o:l:t:ewfm:x:vch"
                                                          -l "import:,input:,output:,list-save:,type:,existing-only,write,force,min:,max:,verbose,csv-friendly,help" -- "$@")

                                                          declare i n o l t e w f m x v c h
                                                          declare -a INPUTFILES
                                                          declare -a INPUTTYPES
                                                          while [ $# -gt 0 ] ; do
                                                          case "$1" in
                                                          -i|--import) shift ; i="$1" ; shift ;;
                                                          -n|--input) shift ; n="$1" ; shift ;;
                                                          -o|--output) shift ; o="$1" ; shift ;;
                                                          -l|--list-save) shift ; l="$1" ; shift ;;
                                                          -t|--type) shift ; INPUTTYPES=("${INPUTTYPES[@]}" "$1") ; shift ;;
                                                          -e|--existing-only) e=1 ; shift ;;
                                                          -w|--write) w=1 ; shift ;;
                                                          -f|--force) f=1 ; shift ;;
                                                          -m|--min) shift ; m="$1" ; shift ;;
                                                          -x|--max) shift ; x="$1" ; shift ;;
                                                          -v|--verbose) v=1 ; shift ;;
                                                          -c|--csv-friendly) c=1 ; shift ;;
                                                          -h|--help) h=1 ; shift ;;
                                                          --) shift ;;
                                                          -*) echo "bad option '$1'" ; exit 1 ;; #FIXME why this exit isn't fired?
                                                          *) INPUTFILES=("${INPUTFILES[@]}" "$1") ; shift ;;
                                                          esac
                                                          done


                                                          # ================================= DEFAULTS ==================================

                                                          #NOTE Remove what requisities you don't need but don't try to use them after!
                                                          # always mp3/flac ogg flac
                                                          REQUIRES="bpm-tag mid3v2 vorbiscomment metaflac"
                                                          which $REQUIRES > /dev/null || { myEcho "These binaries are required: $REQUIRES" >&2 ; exit 1; }

                                                          [ "$h" ] && {
                                                          help
                                                          exit 0
                                                          }

                                                          [[ $m && $x && ( $m -ge $x ) ]] && {
                                                          myEcho "Minimal BPM can't be bigger than NOR same as maximal BPM!"
                                                          exit 1
                                                          }
                                                          [[ "$i" && "$n" ]] && {
                                                          echo "You cannot specify both -i and -n options!"
                                                          exit 1
                                                          }
                                                          [[ "$i" && ( "$m" || "$x" ) ]] && {
                                                          echo "You cannot use -m nor -x option with -i option!"
                                                          exit 1
                                                          }
                                                          [ "$e" ] && {
                                                          [[ "$w" || "$f" ]] && {
                                                          echo "With -e option you don't have any value to write!"
                                                          exit 1
                                                          }
                                                          [[ "$m" || "$x" ]] && {
                                                          echo "With -e option you don't have any value to count!"
                                                          exit 1
                                                          }
                                                          }

                                                          for file in "$o" "$l" ; do
                                                          if [ -f "$file" ] ; then
                                                          while true ; do
                                                          read -n1 -p "Do you want to overwrite existing file ${file}? (Y/n): " key
                                                          case "$key" in
                                                          y|Y|"") echo "" > "$file" ; break ;;
                                                          n|N) exit 0 ;;
                                                          esac
                                                          echo ""
                                                          done
                                                          echo ""
                                                          fi
                                                          done

                                                          [ ${#INPUTTYPES} -eq 0 ] && INPUTTYPES=("mp3")

                                                          # NUMCPU="$(ggrep ^processor /proc/cpuinfo | wc -l)"
                                                          NUMCPU="$(sysctl -a | ggrep machdep.cpu.core_count | gsed -r 's/(.*)([0-9]+)(.*)/2/')"
                                                          LASTPID=0
                                                          TYPESALLOWED=("mp3" "ogg" "flac")
                                                          # declare -A BPMIMPORT # array of BPMs from --import file, keys are file names
                                                          declare -A BPMIMPORT # array of BPMs from --import file, keys are file names

                                                          for type in "${INPUTTYPES[@]}" ; do
                                                          [[ $(inArray $type TYPESALLOWED[@]) -eq 1 ]] && {
                                                          myEcho "Filetype $type is not one of allowed types (${TYPESALLOWED[@]})!"
                                                          exit 1
                                                          }
                                                          done

                                                          ### here are three ways how to pass files to the script...
                                                          if [ "$i" ] ; then # just parse given file list and set BPM to listed files
                                                          if [ -f "$i" ] ; then
                                                          # myEcho "Setting BPM tags from given file ..."
                                                          while read row ; do
                                                          bpm="${row%%;*}"
                                                          file="${row#*;}"
                                                          ext="${file##*.}"
                                                          ext="${ext,,}" # convert to lowercase
                                                          if [ -f "$file" ] ; then
                                                          if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                          FILES=("${FILES[@]}" "$file")
                                                          BPMIMPORT["$file"]="$bpm"
                                                          else
                                                          myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                          fi
                                                          else
                                                          myEcho "Skipping non-existing file $file"
                                                          fi
                                                          done < "$i"
                                                          else
                                                          myEcho "Given import file does not exists!"
                                                          exit 1
                                                          fi
                                                          elif [ "$n" ] ; then # get files from file list
                                                          if [ -f "$n" ] ; then
                                                          rownumber=1
                                                          while read file ; do
                                                          if [ -f "$file" ] ; then
                                                          ext="${file##*.}"
                                                          ext="${ext,,}" # convert to lowercase
                                                          if [ $(inArray $ext INPUTTYPES[@]) -eq 0 ] ; then
                                                          FILES=("${FILES[@]}" "$file")
                                                          else
                                                          myEcho "Skipping file on row $rownumber (unwanted filetype $ext) ... $file"
                                                          fi
                                                          else
                                                          myEcho "Skipping file on row $rownumber (non-existing) ... $file"
                                                          fi
                                                          let rownumber++
                                                          done < "$n"
                                                          unset rownumber
                                                          else
                                                          myEcho "Given input file $n does not exists!"
                                                          exit 1
                                                          fi
                                                          else # get files from given parameters
                                                          [ ${#INPUTFILES[@]} -eq 0 ] && INPUTFILES=`pwd`
                                                          for file in "${INPUTFILES[@]}" ; do
                                                          [ ! -e "$file" ] && {
                                                          myEcho "File or directory $file does not exist!"
                                                          exit 1
                                                          }
                                                          done
                                                          impl_types=`implode "|" INPUTTYPES[@]`
                                                          while read file ; do
                                                          echo -ne "Creating list of files ... (${#FILES[@]}) ${file}33[0K"\r
                                                          FILES=("${FILES[@]}" "$file")
                                                          done < <(gfind "${INPUTFILES[@]}" -type f -regextype posix-awk -iregex ".*.($impl_types)")
                                                          echo -e "Counted ${#FILES[@]} files33[0K"\r
                                                          fi

                                                          [ "$l" ] && printf '%sn' "${FILES[@]}" > "$l"

                                                          NUMFILES=${#FILES[@]}
                                                          FILENUMBER=1

                                                          [ $NUMFILES -eq 0 ] && {
                                                          myEcho "There are no ${INPUTTYPES[@]} files in given files/paths."
                                                          exit 1
                                                          }

                                                          declare SEP=" "
                                                          [ "$c" ] && SEP=";"


                                                          # =============================== MAIN SECTION ================================

                                                          if [ "$e" ] ; then # what heading to show
                                                          myEcho "num${SEP}old${SEP}filename"
                                                          else
                                                          myEcho "num${SEP}old${SEP}new${SEP}filename"
                                                          fi

                                                          for file in "${FILES[@]}" ; do
                                                          [ `jobs -p | wc -l` -ge $NUMCPU ] && wait
                                                          [ "$v" ] && myEcho "Parsing (${FILENUMBER}/${NUMFILES})t$file ..."
                                                          oneThread "$file" "$FILENUMBER" "${BPMIMPORT[$file]}" &
                                                          LASTPID="$!"
                                                          let FILENUMBER++
                                                          done

                                                          [ "$v" ] && myEcho "Waiting for last process ..."
                                                          wait $LASTPID
                                                          [ "$v" ] && myEcho \n"DONE"


                                                          Thank you for your hard work @kolypto and @meridius.



                                                          ...the pain I go through to maintain a CLI workflow and pay no money for music tools...







                                                          share|improve this answer












                                                          share|improve this answer



                                                          share|improve this answer










                                                          answered Nov 17 '16 at 23:23









                                                          AdrianAdrian

                                                          1663




                                                          1663















                                                              Popular posts from this blog

                                                              Plaza Victoria

                                                              Brian Clough

                                                              Cáceres