mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 16:42:42 +01:00
added first stab at album autotagging
--HG-- extra : convert_revision : svn%3A41726ec3-264d-0410-9c23-a9f1637257cc/trunk%40211
This commit is contained in:
parent
13f7f57b17
commit
8a0519c914
2 changed files with 131 additions and 8 deletions
|
|
@ -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
9
bts
|
|
@ -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']),
|
||||
|
|
|
|||
Loading…
Reference in a new issue