added first stab at album autotagging

--HG--
extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%40211
This commit is contained in:
adrian.sampson 2009-04-13 04:03:31 +00:00
parent 13f7f57b17
commit 8a0519c914
2 changed files with 131 additions and 8 deletions

View file

@ -17,14 +17,20 @@
"""Facilities for automatically determining files' correct metadata.
"""
import os
from collections import defaultdict
from beets.autotag.mb import match_album
from beets import library
from beets.mediafile import FileTypeError
# If the MusicBrainz length is more than this many seconds away from the
# track length, an error is reported.
LENGTH_TOLERANCE = 2
def likely_metadata(items):
"""Returns the most likely artist and album for a set of Items.
Each is determined by tag reflected by the plurality of the Items.
"""
# The tags we'll try to determine.
keys = 'artist', 'album'
@ -54,11 +60,121 @@ def likely_metadata(items):
return (likelies['artist'], likelies['album'])
if __name__ == '__main__': # Smoke test.
from beets.library import Item
items = [Item({'artist': 'The Beatles', 'album': 'The White Album'}),
Item({'artist': 'The Beetles', 'album': 'The White Album'}),
Item({'artist': 'The Beatles', 'album': 'Teh White Album'})]
print likely_metadata(items)
def _input_yn(prompt):
"""Prompts user for a "yes" or "no" response where an empty response
is treated as "yes". Keeps prompting until acceptable input is
given; returns a boolean.
"""
resp = raw_input(prompt)
while True:
if len(resp) == 0 or resp[0].tolower() == 'y':
return True
elif len(resp) > 0 and resp[0].tolower() == 'n':
return False
resp = raw_input("Type 'y' or 'n': ")
def tag_album_dir(path, lib):
# Read items from directory.
items = []
for filename in os.listdir(path):
filepath = library._normpath(os.path.join(path, filename))
try:
i = library.Item.from_path(filepath, lib)
except FileTypeError:
continue
items.append(i)
#fixme Check if MB tags are already present.
# Find existing metadata.
cur_artist, cur_album = likely_metadata(items)
# Find "correct" metadata.
info = match_album(cur_artist, cur_album, len(items))
if len(cur_artist) == 0 or len(cur_album) == 0 or \
cur_artist.lower() != info['artist'].lower() or \
cur_album.lower() != info['album'].lower():
# If we're making a "significant" change (changing the artist or
# album), confirm with the user to avoid mistakes.
print "Correcting tags from:"
print '%s - %s' % (cur_artist, cur_album)
print "To:"
print '%s - %s' % (info['artist'], info['album'])
if not _input_yn("Apply change ([y]/n)? "):
return
else:
print 'Tagging album: %s - %s' % (info['artist'], info['album'])
# Ensure that we don't have the album already.
q = library.AndQuery((library.MatchQuery('artist', info['artist']),
library.MatchQuery('album', info['album'])))
count, _ = q.count(lib)
if count >= 1:
print "This album (%s - %s) is already in the library!" % \
(info['artist'], info['album'])
return
# Determine order of existing tracks.
# First, see if the current tags indicate an ordering.
ordered_items = [None]*len(items)
available_indices = set(range(len(items)))
for item in items:
if item.track:
index = item.track - 1
ordered_items[index] = item
available_indices.remove(index)
else:
# If we have any item without an index, give up.
break
if available_indices:
print "Tracks are not correctly ordered."
return
#fixme:
# Otherwise, match based on names and lengths of tracks (confirm).
# Apply new metadata.
for index, (item, track_data) in enumerate(zip(ordered_items,
info['tracks']
)):
# For safety, ensure track lengths match.
if not (item.length - LENGTH_TOLERANCE <
track_data['length'] <
item.length + LENGTH_TOLERANCE):
print "Length mismatch on track %i: actual length is %f and MB " \
"length is %f." % (index, item.length, track_data['length'])
return
if item.title != track_data['title']:
print "%s -> %s" % (item.title, track_data['title'])
item.artist = info['artist']
item.album = info['album']
item.track_total = len(items)
item.year = info['year']
if 'month' in info:
item.month = info['month']
if 'day' in info:
item.day = info['day']
item.title = track_data['title']
item.track = index + 1
#fixme Set MusicBrainz IDs!
# Add items to library and write their tags.
for item in ordered_items:
item.move(True)
item.add()
item.write()
if __name__ == '__main__':
import sys
lib = library.Library()
path = os.path.expanduser(sys.argv[1])
tag_album_dir(path, lib)
lib.save()

9
bts
View file

@ -18,6 +18,7 @@
from optparse import OptionParser
from beets import Library
from beets import autotag
from ConfigParser import SafeConfigParser
import os
@ -86,6 +87,11 @@ def read(lib, config, criteria):
item.store()
lib.save()
def tagalbum(lib, config, paths):
for path in paths:
autotag.tag_album_dir(os.path.expanduser(path), lib)
lib.save()
def bpd(lib, config, opts):
host = opts.pop(0) if opts else None
host = host if host else config.get('bpd', 'host')
@ -99,7 +105,7 @@ def bpd(lib, config, opts):
if __name__ == "__main__":
# parse options
usage = """usage: %prog [options] command
command is one of: add, remove, update, write, list, bpd, help"""
command is one of: add, remove, update, write, list, bpd, tagalbum, help"""
op = OptionParser(usage=usage)
op.add_option('-l', '--library', dest='libpath', metavar='PATH',
default=None,
@ -132,6 +138,7 @@ command is one of: add, remove, update, write, list, bpd, help"""
(imp, ['import', 'im', 'imp']),
(remove, ['remove', 'rm']),
(delete, ['delete', 'del']),
(tagalbum, ['tagalbum', 'tag']),
(read, ['read', 'r']),
#(write, ['write', 'wr', 'w']),