#!/usr/bin/env python # This file is part of beets. # Copyright 2009, Adrian Sampson. # # Beets 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. # # Beets 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 beets. If not, see . import cmdln from ConfigParser import SafeConfigParser import os import sys from beets import autotag from beets import library from beets import Library from beets.mediafile import FileTypeError CONFIG_DEFAULTS = { # beets 'library': 'library.blb', 'directory': '~/Music', 'path_format': '$artist/$album/$track $title', # bpd 'host': '', 'port': '6600', 'password': '', } CONFIG_FILE = os.path.expanduser('~/.beetsrc') def _print(txt): """Print the text encoded using UTF-8.""" print txt.encode('utf-8') 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].lower() == 'y': return True elif len(resp) > 0 and resp[0].lower() == 'n': return False resp = raw_input("Type 'y' or 'n': ") def tag_album(items, lib): # Infer tags. try: items,(cur_artist,cur_album),info,dist = autotag.tag_album(items) except autotag.AutotagError: print "Untaggable album:", os.path.dirname(items[0].path) return # Show what we're about to do. if cur_artist != info['artist'] or cur_album != info['album']: print "Correcting tags from:" print ' %s - %s' % (cur_artist, cur_album) print "To:" print ' %s - %s' % (info['artist'], info['album']) else: print "Tagging: %s - %s" % (info['artist'], info['album']) for item, track_data in zip(items, info['tracks']): if item.title != track_data['title']: print " * %s -> %s" % (item.title, track_data['title']) # Warn if change is significant. if dist > 0.0: if not _input_yn("Apply change ([y]/n)? "): return # 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 # Change metadata and add to library. autotag.apply_metadata(items, info) for item in items: item.move(True) item.add() item.write() class BeetsApp(cmdln.Cmdln): name = "bts" def get_optparser(self): # Add global options to the command. parser = cmdln.Cmdln.get_optparser(self) parser.add_option('-l', '--library', dest='libpath', help='the library database file to use') return parser def postoptparse(self): # Read defaults from config file. self.config = SafeConfigParser(CONFIG_DEFAULTS) self.config.read(CONFIG_FILE) for sec in ('beets', 'bpd'): if not self.config.has_section(sec): self.config.add_section(sec) # Open library file. libpath = self.options.libpath or self.config.get('beets', 'library') directory = self.config.get('beets', 'directory') path_format = self.config.get('beets', 'path_format') self.lib = Library(os.path.expanduser(libpath), directory, path_format) @cmdln.alias("imp", "im") def do_import(self, subcmd, opts, *paths): """${cmd_name}: import new music ${cmd_usage} ${cmd_option_list} """ for path in paths: for album in autotag.albums_in_dir(os.path.expanduser(path), self.lib): print tag_album(album, self.lib) self.lib.save() @cmdln.alias("ls") @cmdln.option('-a', '--album', action='store_true', help='show matching albums instead of tracks') def do_list(self, subcmd, opts, *criteria): """${cmd_name}: query the library ${cmd_usage} ${cmd_option_list} """ q = ' '.join(criteria) if not q.strip(): q = None # no criteria => match anything if opts.album: for artist, album in self.lib.albums(query=q): _print(artist + ' - ' + album) else: for item in self.lib.items(query=q): _print(item.artist + ' - ' + item.album + ' - ' + item.title) def do_bpd(self, subcmd, opts, host=None, port=None): """${cmd_name}: run an MPD-compatible music player server ${cmd_usage} ${cmd_option_list} """ host = host or self.config.get('bpd', 'host') port = port or self.config.get('bpd', 'port') password = self.config.get('bpd', 'password') from beets.player.bpd import Server Server(self.lib, host, int(port), password).run() if __name__ == '__main__': app = BeetsApp() sys.exit(app.main())