Skip to content

Instantly share code, notes, and snippets.

@pschwede
Created July 28, 2012 12:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pschwede/3193087 to your computer and use it in GitHub Desktop.
Save pschwede/3193087 to your computer and use it in GitHub Desktop.
Add bpm tags to your music collection
#!/usr/bin/env python
# Uses bpmcount from bpmdj.sf.net to add BPM-tags to your music files.
# Copyright (C) 2012 spazzpp2
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import os
import sys
import time
import mutagen
import argparse
import subprocess
findtempoline = re.compile("Tempo is [0-9,]+")
oldtime = time.time()
def count(dirname):
"""Count the total number of files in a directory."""
filecount = 0
for root, dirs, files in os.walk(top=dirname):
filecount += len(files)
return filecount
def bpmdetect(dirname, force=False, bpmcounter="bpmcount", callback=None):
"""Search for music files in dirname and sets their BPM tag.
Supported are mp3, mp4, flac, ogg and aac.
"""
print "Searching for music files in '%s'." % dirname
if force:
print "I will replace existing BPM-tags."
else:
print "I won't replace BPM-tags."
i = 0
for root, dirs, files in os.walk(top=dirname):
for f in files:
if f[-4:].lower() in [".mp3", ".mp4", "flac", ".ogg", ".aac"]:
f = str(os.path.join(root, f))
try:
tags = mutagen.File(f, easy=True)
bpm = ""
try:
if force:
raise KeyError()
try:
try:
bpm = int(round(float(tags["bpm"][0])))
if bpm == 0:
raise KeyError()
except ValueError:
raise KeyError()
except TypeError:
raise KeyError()
except KeyError:
try:
output = subprocess.check_output([bpmcounter, f],
stderr=subprocess.STDOUT)
try:
bpm = findtempoline.\
findall(output)[0].\
split(" ")[-1].\
split(",")[0]
tags["bpm"] = bpm
tags.save()
except IndexError:
pass
except subprocess.CalledProcessError:
pass
if callback:
callback(i, str(bpm), root, f)
except Exception:
pass
i += 1
def timefmt(seconds):
"""Take number of seconds and return a readable string."""
hours = str(seconds / 60 / 60)
if len(hours) < 2:
hours = "0" + hours
minutes = str((seconds / 60) % 60)
if len(minutes) < 2:
minutes = "0" + minutes
seconds = str(seconds % 60)
if len(seconds) < 2:
seconds = "0" + seconds
return "%s:%s:%s" % (hours, minutes, seconds,)
def process(count, i, bpm, d, f):
"""Print out the process."""
global done, oldtime
dtime = time.time() - oldtime
if i:
remaining = int((count - i) * dtime / i)
else:
remaining = 0
print "remaining:\tperc:\tbpm:\tfile:"
print "%s\t%.2f%%\t%s\t%s...%s" % (
timefmt(remaining),
100. * i / count,
bpm,
d[:20],
f[-20:])
def check_executable(filename):
"""Checks if file is existing and executable"""
try:
output = subprocess.check_output([filename],
stderr=subprocess.STDOUT)
except OSError:
return False
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=str(bpmdetect.__doc__))
parser.add_argument("dirname", type=str,
help="name of directory where to search for music files")
parser.add_argument("-f", "--force", dest="force", action="store_true",
help="overwrite existing BPM tags")
parser.add_argument("-b", default="bpmcount",
dest="bpmcounter", metavar="PATH",
help="path to bpmcount from bpmdj package")
args = parser.parse_args()
if not check_executable(args.bpmcounter):
print "No bpmcounter: %s" % args.bpmcounter
number = count(args.dirname)
bpmdetect(dirname=args.dirname,
force=args.force,
bpmcounter=args.bpmcounter,
callback=lambda i, b, d, f: process(number, i, b, d, f))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment