mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 13:07:09 +01:00
235 lines
6.9 KiB
Python
Executable file
235 lines
6.9 KiB
Python
Executable file
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
from optparse import OptionParser
|
|
from beets import Library
|
|
from ConfigParser import SafeConfigParser
|
|
import os
|
|
|
|
from beets import autotag
|
|
from beets import library
|
|
from beets.mediafile import FileTypeError
|
|
|
|
CONFIG_DEFAULTS = {
|
|
# beets
|
|
'library': 'library.blb',
|
|
|
|
# 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_dir(path, lib):
|
|
# Read items from directory.
|
|
items = []
|
|
for filename in os.listdir(path):
|
|
filepath = os.path.join(path, filename)
|
|
try:
|
|
i = library.Item.from_path(filepath, lib)
|
|
except FileTypeError:
|
|
continue
|
|
items.append(i)
|
|
|
|
# Infer tags.
|
|
try:
|
|
items, (cur_artist, cur_album), info, dist = autotag.tag_album(items)
|
|
except autotag.UnorderedTracksError:
|
|
print "Tracks could not be ordered."
|
|
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()
|
|
|
|
|
|
def add(lib, config, paths):
|
|
for path in paths:
|
|
lib.add(path)
|
|
lib.save()
|
|
|
|
def ls(lib, config, criteria):
|
|
q = ' '.join(criteria)
|
|
if not q.strip():
|
|
q = None # no criteria => match anything
|
|
for item in lib.get(q):
|
|
_print(item.artist + ' - ' + item.album + ' - ' + item.title)
|
|
|
|
def imp(lib, config, paths):
|
|
for path in paths:
|
|
lib.add(path, copy=True)
|
|
lib.save()
|
|
|
|
def option(lib, config, options):
|
|
if len(options) == 1: # Get.
|
|
(key,) = options
|
|
_print(lib.options[key])
|
|
elif len(options) == 2: # Set.
|
|
(key, value) = options
|
|
lib.options[key] = value
|
|
lib.save()
|
|
elif len(options) == 0: # List.
|
|
for key in lib.options:
|
|
print key
|
|
|
|
def remove(lib, config, criteria):
|
|
q = ' '.join(criteria)
|
|
if not q.strip():
|
|
raise ValueError('must provide some criteria for removing')
|
|
for item in lib.get(q):
|
|
_print("removing " + item.path)
|
|
item.remove()
|
|
lib.save()
|
|
|
|
def delete(lib, config, criteria):
|
|
q = ' '.join(criteria)
|
|
if not q.strip():
|
|
raise ValueError('must provide some criteria for deleting')
|
|
for item in lib.get(q):
|
|
_print("deleting " + item.path)
|
|
item.delete()
|
|
lib.save()
|
|
|
|
def read(lib, config, criteria):
|
|
q = ' '.join(criteria)
|
|
if not q.strip():
|
|
q = None
|
|
for item in lib.get(q):
|
|
item.read()
|
|
item.store()
|
|
lib.save()
|
|
|
|
def tagalbum(lib, config, paths):
|
|
for path in paths:
|
|
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')
|
|
port = int(opts.pop(0)) if opts else None
|
|
port = port if port else config.getint('bpd', 'port')
|
|
password = config.get('bpd', 'password')
|
|
|
|
from beets.player.bpd import Server
|
|
Server(lib, host, port, password).run()
|
|
|
|
if __name__ == "__main__":
|
|
# parse options
|
|
usage = """usage: %prog [options] command
|
|
command is one of: add, remove, update, write, list, set, bpd, tagalbum, help"""
|
|
op = OptionParser(usage=usage)
|
|
op.add_option('-l', '--library', dest='libpath', metavar='PATH',
|
|
default=None,
|
|
help='work on the specified library file')
|
|
op.remove_option('--help')
|
|
opts, args = op.parse_args()
|
|
|
|
# make sure we have a command
|
|
if len(args) < 1:
|
|
op.error('no command specified')
|
|
cmd = args.pop(0)
|
|
|
|
# read defaults from config file
|
|
config = SafeConfigParser(CONFIG_DEFAULTS)
|
|
config.read(CONFIG_FILE)
|
|
for sec in ('beets', 'bpd'):
|
|
if not config.has_section(sec):
|
|
config.add_section(sec)
|
|
if not opts.libpath:
|
|
opts.libpath = config.get('beets', 'library')
|
|
|
|
lib = Library(os.path.expanduser(opts.libpath))
|
|
|
|
# make a "help" command
|
|
def help(*args): op.print_help()
|
|
|
|
# choose which command to invoke
|
|
avail_commands = [
|
|
(add, ['add']),
|
|
(imp, ['import', 'im', 'imp']),
|
|
(remove, ['remove', 'rm']),
|
|
(delete, ['delete', 'del']),
|
|
(tagalbum, ['tagalbum', 'tag']),
|
|
|
|
(read, ['read', 'r']),
|
|
#(write, ['write', 'wr', 'w']),
|
|
|
|
(ls, ['list', 'ls']),
|
|
|
|
(option, ['set']),
|
|
(help, ['help', 'h']),
|
|
|
|
(bpd, ['bpd']),
|
|
]
|
|
for test_command in avail_commands:
|
|
if cmd in test_command[1]:
|
|
(test_command[0])(lib, config, args)
|
|
op.exit()
|
|
|
|
# no command matched
|
|
op.error('invalid command "' + cmd + '"')
|