1. Adrian Sampson
  2. beets


beets / beet

#!/usr/bin/env python

# This file is part of beets.
# Copyright 2010, Adrian Sampson.
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

import cmdln
import ConfigParser
import os
import sys

from beets import ui
from beets import Library

    'beets': {
        'library': '~/.beetsmusic.blb',
        'directory': '~/Music',
        'path_format': '$artist/$album/$track $title',
        'import_copy': True,
        'import_write': True,

    'bpd': {
        'host': '',
        'port': '6600',
        'password': '',

CONFIG_FILE = os.path.expanduser('~/.beetsconfig')

def make_query(criteria):
    """Make  query string for the list of criteria."""
    return ' '.join(criteria).strip() or None
class BeetsApp(cmdln.Cmdln):
    name = "beet"
    def get_optparser(self):
        # Add global options to the command.
        parser = cmdln.Cmdln.get_optparser(self)
        parser.add_option('-l', '--library', dest='libpath',
                          help='library database file to use')
        parser.add_option('-d', '--directory', dest='directory',
                          help="destination music directory")
        parser.add_option('-p', '--pathformat', dest='path_format',
                          help="destination path format string")
        parser.add_option('-i', '--device', dest='device',
                          help="name of the device library to use")
        return parser
    def postoptparse(self):
        # Read defaults from config file.
        self.config = ConfigParser.SafeConfigParser()
        for sec in CONFIG_DEFAULTS:
            if not self.config.has_section(sec):
        # Open library file.
        if self.options.device:
            from beets.device import PodLibrary
            self.lib = PodLibrary.by_name(self.options.device)
            libpath = self.options.libpath or \
                      self._cfg_get('beets', 'library')
            directory = self.options.directory or \
                        self._cfg_get('beets', 'directory')
            path_format = self.options.path_format or \
                          self._cfg_get('beets', 'path_format')
            self.lib = Library(os.path.expanduser(libpath),
    def _cfg_get(self, section, name, vtype=None):
            if vtype is bool:
                return self.config.getboolean(section, name)
                return self.config.get(section, name)
        except ConfigParser.NoOptionError:
            return CONFIG_DEFAULTS[section][name]
    @cmdln.alias("imp", "im")
    @cmdln.option('-c', '--copy', action='store_true', default=None,
                  help="copy tracks into library directory (default)")
    @cmdln.option('-C', '--nocopy', action='store_false', dest='copy',
                  help="don't copy tracks (opposite of -c)")
    @cmdln.option('-w', '--write', action='store_true', default=None,
                  help="write new metadata to files' tags (default)")
    @cmdln.option('-W', '--nowrite', action='store_false', dest='write',
                  help="don't write metadata (opposite of -s)")
    @cmdln.option('-a', '--autotag', action='store_true', dest='autotag',
                  help="infer tags for imported files (default)")
    @cmdln.option('-A', '--noautotag', action='store_false', dest='autotag',
                  help="don't infer tags for imported files (opposite of -a)")
    def do_import(self, subcmd, opts, *paths):
        """${cmd_name}: import new music
        copy  = opts.copy  if opts.copy  is not None else \
                self._cfg_get('beets', 'import_copy', bool)
        write = opts.write if opts.write is not None else \
                self._cfg_get('beets', 'import_write', bool)
        autot = opts.autotag if opts.autotag is not None else True
        ui.import_files(self.lib, paths, copy, write, autot)
    @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
        ui.list_items(self.lib, make_query(criteria), opts.album)
    @cmdln.option("-d", "--delete", action="store_true",
                  help="also remove files from disk")
    @cmdln.option('-a', '--album', action='store_true',
                  help='match albums instead of tracks')
    def do_remove(self, subcmd, opts, *criteria):
        """${cmd_name}: remove matching items from the library

        q = make_query(criteria)
        ui.remove_items(self.lib, make_query(criteria),
                        opts.album, opts.delete)

    @cmdln.option('-d', '--debug', action='store_true',
                  help='dump all MPD traffic to stdout')
    def do_bpd(self, subcmd, opts, host=None, port=None):
        """${cmd_name}: run an MPD-compatible music player server
        host = host or self._cfg_get('bpd', 'host')
        port = port or self._cfg_get('bpd', 'port')
        password = self._cfg_get('bpd', 'password')
        debug = opts.debug or False
        ui.start_bpd(self.lib, host, int(port), password, debug)

    def do_dadd(self, subcmd, opts, name, *criteria):
        """${cmd_name}: add files to a device
        ui.device_add(self.lib, make_query(criteria), name)

    def do_stats(self, subcmd, opts, *criteria):
        """${cmd_name}: show statistics about the library or a query

        ui.show_stats(self.lib, make_query(criteria))

if __name__ == '__main__':
    app = BeetsApp()