Beispiel #1
0
    def manipulate_files(self, move=False, copy=False, write=False,
                         link=False, session=None):
        items = self.imported_items()
        # Save the original paths of all items for deletion and pruning
        # in the next step (finalization).
        self.old_paths = [item.path for item in items]
        for item in items:
            if move or copy or link:
                # In copy and link modes, treat re-imports specially:
                # move in-library files. (Out-of-library files are
                # copied/moved as usual).
                old_path = item.path
                if (copy or link) and self.replaced_items[item] and \
                   session.lib.directory in util.ancestry(old_path):
                    item.move()
                    # We moved the item, so remove the
                    # now-nonexistent file from old_paths.
                    self.old_paths.remove(old_path)
                else:
                    # A normal import. Just copy files and keep track of
                    # old paths.
                    item.move(copy, link)

            if write and self.apply:
                item.try_write()

        with session.lib.transaction():
            for item in self.imported_items():
                item.store()

        plugins.send('import_task_files', session=session, task=self)
Beispiel #2
0
    def _run_importer(self):
        """
        Create an instance of the plugin, run the importer, and
        remove/unregister the plugin instance so a new instance can
        be created when this method is run again.
        This is a convenience method that can be called to setup, exercise
        and teardown the system under test after setting any config options
        and before assertions are made regarding changes to the filesystem.
        """
        # Setup
        # Create an instance of the plugin
        plugins.find_plugins()

        # Exercise
        # Run the importer
        self.importer.run()
        # Fake the occurence of the cli_exit event
        plugins.send('cli_exit', lib=self.lib)

        # Teardown
        if plugins._instances:
            classes = list(plugins._classes)

            # Unregister listners
            for event in classes[0].listeners:
                del classes[0].listeners[event][0]

            # Delete the plugin instance so a new one gets created for each test
            del plugins._instances[classes[0]]

        log.debug("--- library structure")
        self._list_files(self.lib_dir)
Beispiel #3
0
def album_candidates(items, artist, album, va_likely):
    """Search for album matches. ``items`` is a list of Item objects
    that make up the album. ``artist`` and ``album`` are the respective
    names (strings), which may be derived from the item list or may be
    entered by the user. ``va_likely`` is a boolean indicating whether
    the album is likely to be a "various artists" release.
    """
    out = []

    # Base candidates if we have album and artist to match.
    if artist and album:
        try:
            out.extend(mb.match_album(artist, album, len(items)))
        except mb.MusicBrainzAPIError as exc:
            exc.log(log)

    # Also add VA matches from MusicBrainz where appropriate.
    if va_likely and album:
        try:
            out.extend(mb.match_album(None, album, len(items)))
        except mb.MusicBrainzAPIError as exc:
            exc.log(log)

    # Candidates from plugins.
    out.extend(plugins.candidates(items, artist, album, va_likely))

    # Notify subscribed plugins about fetched album info
    for a in out:
        plugins.send('albuminfo_received', info=a)

    return out
Beispiel #4
0
    def write(self, path=None):
        """Write the item's metadata to a media file.

        Updates the mediafile with properties from itself.

        Can raise either a `ReadError` or a `WriteError`.
        """
        if path is None:
            path = self.path
        else:
            path = normpath(path)
        try:
            mediafile = MediaFile(path)
        except (OSError, IOError) as exc:
            raise ReadError(self.path, exc)

        plugins.send('write', item=self, path=path)

        try:
            mediafile.update(self, id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        if path == self.path:
            self.mtime = self.current_mtime()
        plugins.send('after_write', item=self, path=path)
Beispiel #5
0
 def _emit_imported(self, lib):
     # FIXME This shouldn't be here. Skipped tasks should be removed from
     # the pipeline.
     if self.skip:
         return
     for item in self.imported_items():
         plugins.send("item_imported", lib=lib, item=item)
Beispiel #6
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_option('-l', '--library', dest='library',
                      help='library database file to use')
    parser.add_option('-d', '--directory', dest='directory',
                      help="destination music directory")
    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                      help='print debugging information')
    parser.add_option('-c', '--config', dest='config',
                      help='path to configuration file')
    parser.add_option('-h', '--help', dest='help', action='store_true',
                      help='how this help message and exit')
    parser.add_option('--version', dest='version', action='store_true',
                      help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)
    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
Beispiel #7
0
    def write(self, path=None):
        """Write the item's metadata to a media file.

        ``path`` defaults to the item's path property.

        Can raise either a `ReadError` or a `WriteError`.
        """
        if path is None:
            path = self.path
        else:
            path = normpath(path)
        try:
            f = MediaFile(syspath(path))
        except (OSError, IOError) as exc:
            raise ReadError(self.path, exc)

        plugins.send('write', item=self, path=path)

        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, self[key])
        try:
            f.save(id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        self.mtime = self.current_mtime()
        plugins.send('after_write', item=self)
Beispiel #8
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_format_option(flags=("--format-item",), target=library.Item)
    parser.add_format_option(flags=("--format-album",), target=library.Album)
    parser.add_option("-l", "--library", dest="library", help="library database file to use")
    parser.add_option("-d", "--directory", dest="directory", help="destination music directory")
    parser.add_option("-v", "--verbose", dest="verbose", action="count", help="print debugging information")
    parser.add_option("-c", "--config", dest="config", help="path to configuration file")
    parser.add_option("-h", "--help", dest="help", action="store_true", help="how this help message and exit")
    parser.add_option("--version", dest="version", action="store_true", help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)

    # Special case for the `config --edit` command: bypass _setup so
    # that an invalid configuration does not prevent the editor from
    # starting.
    if subargs and subargs[0] == "config" and ("-e" in subargs or "--edit" in subargs):
        from beets.ui.commands import config_edit

        return config_edit()

    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send("cli_exit", lib=lib)
Beispiel #9
0
    def write(self, path=None):
        """Write the item's metadata to a media file.

        All fields in `_media_fields` are written to disk according to
        the values on this object.

        Can raise either a `ReadError` or a `WriteError`.
        """
        if path is None:
            path = self.path
        else:
            path = normpath(path)

        tags = dict(self)
        plugins.send('write', item=self, path=path, tags=tags)

        try:
            mediafile = MediaFile(syspath(path),
                                  id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, UnreadableFileError) as exc:
            raise ReadError(self.path, exc)

        mediafile.update(tags)
        try:
            mediafile.save()
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        if path == self.path:
            self.mtime = self.current_mtime()
        plugins.send('after_write', item=self, path=path)
Beispiel #10
0
def _setup(options, lib=None):
    """Prepare and global state and updates it with command line options.

    Returns a list of subcommands, a list of plugins, and a library instance.
    """
    # Configure the MusicBrainz API.
    mb.configure()

    config = _configure(options)

    plugins = _load_plugins(config)

    # Get the default subcommands.
    from beets.ui.commands import default_commands

    subcommands = list(default_commands)
    subcommands.extend(plugins.commands())

    if lib is None:
        lib = _open_library(config)
        plugins.send("library_opened", lib=lib)
    library.Item._types.update(plugins.types(library.Item))
    library.Album._types.update(plugins.types(library.Album))

    return subcommands, plugins, lib
Beispiel #11
0
def user_query(session):
    """A coroutine for interfacing with the user about the tagging
    process.

    The coroutine accepts an ImportTask objects. It uses the
    session's ``choose_match`` method to determine the ``action`` for
    this task. Depending on the action additional stages are exectuted
    and the processed task is yielded.

    It emits the ``import_task_choice`` event for plugins. Plugins have
    acces to the choice via the ``taks.choice_flag`` property and may
    choose to change it.
    """
    recent = set()
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        # Ask the user for a choice.
        choice = session.choose_match(task)
        task.set_choice(choice)
        session.log_choice(task)
        plugins.send("import_task_choice", session=session, task=task)

        # As-tracks: transition to singleton workflow.
        if task.choice_flag is action.TRACKS:
            # Set up a little pipeline for dealing with the singletons.
            def emitter(task):
                for item in task.items:
                    yield ImportTask.item_task(item)
                yield ImportTask.progress_sentinel(task.toppath, task.paths)

            ipl = pipeline.Pipeline([emitter(task), item_lookup(session), item_query(session)])
            task = pipeline.multiple(ipl.pull())
            continue

        # As albums: group items by albums and create task for each album
        if task.choice_flag is action.ALBUMS:

            def emitter(task):
                yield task

            ipl = pipeline.Pipeline(
                [emitter(task), group_albums(session), initial_lookup(session), user_query(session)]
            )
            task = pipeline.multiple(ipl.pull())
            continue

        # Check for duplicates if we have a match (or ASIS).
        if task.choice_flag in (action.ASIS, action.APPLY):
            ident = task.chosen_ident()
            # The "recent" set keeps track of identifiers for recently
            # imported albums -- those that haven't reached the database
            # yet.
            if ident in recent or _duplicate_check(session.lib, task):
                session.resolve_duplicate(task)
                session.log_choice(task, True)
            recent.add(ident)
Beispiel #12
0
    def set_art(self, path, copy=True):
        """Sets the album's cover art to the image at the given path.
        The image is copied (or moved) into place, replacing any
        existing art.

        Sends an 'art_set' event with `self` as the sole argument.
        """
        path = bytestring_path(path)
        oldart = self.artpath
        artdest = self.art_destination(path)

        if oldart and samefile(path, oldart):
            # Art already set.
            return
        elif samefile(path, artdest):
            # Art already in place.
            self.artpath = path
            return

        # Normal operation.
        if oldart == artdest:
            util.remove(oldart)
        artdest = util.unique_path(artdest)
        if copy:
            util.copy(path, artdest)
        else:
            util.move(path, artdest)
        self.artpath = artdest

        plugins.send('art_set', album=self)
Beispiel #13
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    subcommand, suboptions, subargs = _configure(args)

    if lib is None:
        # Open library file.
        dbpath = config["library"].as_filename()
        try:
            lib = library.Library(dbpath, config["directory"].as_filename(), get_path_formats(), get_replacements())
        except sqlite3.OperationalError:
            raise UserError(u"database file {0} could not be opened".format(util.displayable_path(dbpath)))
        plugins.send("library_opened", lib=lib)

    log.debug(
        u"data directory: {0}\n"
        u"library database: {1}\n"
        u"library directory: {2}".format(
            util.displayable_path(config.config_dir()),
            util.displayable_path(lib.path),
            util.displayable_path(lib.directory),
        )
    )

    # Configure the MusicBrainz API.
    mb.configure()

    # Invoke the subcommand.
    subcommand.func(lib, suboptions, subargs)
    plugins.send("cli_exit", lib=lib)
Beispiel #14
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_option('-l', '--library', dest='library',
                      help='library database file to use')
    parser.add_option('-d', '--directory', dest='directory',
                      help="destination music directory")
    parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                      help='print debugging information')
    parser.add_option('-c', '--config', dest='config',
                      help='path to configuration file')
    parser.add_option('-h', '--help', dest='help', action='store_true',
                      help='how this help message and exit')
    parser.add_option('--version', dest='version', action='store_true',
                      help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)

    # Special case for the `config --edit` command: bypass _setup so
    # that an invalid configuration does not prevent the editor from
    # starting.
    if subargs[0] == 'config' and ('-e' in subargs or '--edit' in subargs):
        from beets.ui.commands import config_edit
        return config_edit()

    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
Beispiel #15
0
 def save(self, event=True):
     """Writes the library to disk (completing an sqlite
     transaction).
     """
     self.conn.commit()
     if event:
         plugins.send('save', lib=self)
Beispiel #16
0
def _setup(options, lib=None):
    """Prepare and global state and updates it with command line options.

    Returns a list of subcommands, a list of plugins, and a library instance.
    """
    # Configure the MusicBrainz API.
    mb.configure()

    config = _configure(options)

    plugins = _load_plugins(config)

    # Temporary: Migrate from 1.0-style configuration.
    from beets.ui import migrate
    migrate.automigrate()

    # Get the default subcommands.
    from beets.ui.commands import default_commands

    subcommands = list(default_commands)
    subcommands.append(migrate.migrate_cmd)
    subcommands.extend(plugins.commands())

    if lib is None:
        lib = _open_library(config)
        plugins.send("library_opened", lib=lib)

    return subcommands, plugins, lib
Beispiel #17
0
 def test_listener_level2(self):
     self.config['verbose'] = 2
     with helper.capture_log() as logs:
         plugins.send('dummy_event')
     self.assertIn(u'dummy: warning listener', logs)
     self.assertIn(u'dummy: info listener', logs)
     self.assertIn(u'dummy: debug listener', logs)
Beispiel #18
0
def convert_item(dest_dir, keep_new, path_formats, command, ext):
    while True:
        item = yield
        dest = item.destination(basedir=dest_dir, path_formats=path_formats)

        # When keeping the new file in the library, we first move the
        # current (pristine) file to the destination. We'll then copy it
        # back to its old path or transcode it to a new path.
        if keep_new:
            original = dest
            converted = replace_ext(item.path, ext)
        else:
            original = item.path
            dest = replace_ext(dest, ext)
            converted = dest

        # Ensure that only one thread tries to create directories at a
        # time. (The existence check is not atomic with the directory
        # creation inside this function.)
        with _fs_lock:
            util.mkdirall(dest)

        if os.path.exists(util.syspath(dest)):
            log.info(u'Skipping {0} (target file exists)'.format(
                util.displayable_path(item.path)
            ))
            continue

        if keep_new:
            log.info(u'Moving to {0}'.
                     format(util.displayable_path(original)))
            util.move(item.path, original)

        if not should_transcode(item):
            # No transcoding necessary.
            log.info(u'Copying {0}'.format(util.displayable_path(item.path)))
            util.copy(original, converted)
        else:
            try:
                encode(command, original, converted)
            except subprocess.CalledProcessError:
                continue

        # Write tags from the database to the converted file.
        item.write(path=converted)

        if keep_new:
            # If we're keeping the transcoded file, read it again (after
            # writing) to get new bitrate, duration, etc.
            item.path = converted
            item.read()
            item.store()  # Store new path and audio data.

        if config['convert']['embed']:
            album = item.get_album()
            if album and album.artpath:
                embed_item(item, album.artpath, itempath=converted)

        plugins.send('after_convert', item=item, dest=dest, keepnew=keep_new)
Beispiel #19
0
def user_query(session):
    """A coroutine for interfacing with the user about the tagging
    process. lib is the Library to import into and logfile may be
    a file-like object for logging the import process. The coroutine
    accepts and yields ImportTask objects.
    """
    recent = set()
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        # Ask the user for a choice.
        choice = session.choose_match(task)
        task.set_choice(choice)
        session.log_choice(task)
        plugins.send('import_task_choice', session=session, task=task)

        # As-tracks: transition to singleton workflow.
        if choice is action.TRACKS:
            # Set up a little pipeline for dealing with the singletons.
            def emitter(task):
                for item in task.items:
                    yield ImportTask.item_task(item)
                yield ImportTask.progress_sentinel(task.toppath, task.paths)

            ipl = pipeline.Pipeline([
                emitter(task),
                item_lookup(session),
                item_query(session),
            ])
            task = pipeline.multiple(ipl.pull())
            continue

        # As albums: group items by albums and create task for each album
        if choice is action.ALBUMS:
            def emitter(task):
                yield task

            ipl = pipeline.Pipeline([
                emitter(task),
                group_albums(session),
                initial_lookup(session),
                user_query(session)
            ])
            task = pipeline.multiple(ipl.pull())
            continue

        # Check for duplicates if we have a match (or ASIS).
        if task.choice_flag in (action.ASIS, action.APPLY):
            ident = task.chosen_ident()
            # The "recent" set keeps track of identifiers for recently
            # imported albums -- those that haven't reached the database
            # yet.
            if ident in recent or _duplicate_check(session.lib, task):
                session.resolve_duplicate(task)
                session.log_choice(task, True)
            recent.add(ident)
Beispiel #20
0
def albums_for_id(album_id):
    """Get a list of albums for an ID."""
    candidates = [album_for_mbid(album_id)]
    plugin_albums = plugins.album_for_id(album_id)
    for a in plugin_albums:
        plugins.send('albuminfo_received', info=a)
    candidates.extend(plugin_albums)
    return filter(None, candidates)
Beispiel #21
0
def tracks_for_id(track_id):
    """Get a list of tracks for an ID."""
    candidates = [track_for_mbid(track_id)]
    plugin_tracks = plugins.track_for_id(track_id)
    for t in plugin_tracks:
        plugins.send('trackinfo_received', info=t)
    candidates.extend(plugin_tracks)
    return filter(None, candidates)
Beispiel #22
0
def manipulate_files(session):
    """A coroutine (pipeline stage) that performs necessary file
    manipulations *after* items have been added to the library.
    """
    task = None
    while True:
        task = yield task
        if task.should_skip():
            continue

        # Remove duplicate files marked for deletion.
        if task.remove_duplicates:
            for duplicate_path in task.duplicate_paths:
                log.debug(u'deleting replaced duplicate %s' %
                          util.displayable_path(duplicate_path))
                util.remove(duplicate_path)
                util.prune_dirs(os.path.dirname(duplicate_path),
                                session.lib.directory)

        # Move/copy/write files.
        items = task.imported_items()
        # Save the original paths of all items for deletion and pruning
        # in the next step (finalization).
        task.old_paths = [item.path for item in items]
        for item in items:
            if config['import']['move']:
                # Just move the file.
                item.move(False)
            elif config['import']['copy']:
                # If it's a reimport, move in-library files and copy
                # out-of-library files. Otherwise, copy and keep track
                # of the old path.
                old_path = item.path
                if task.replaced_items[item]:
                    # This is a reimport. Move in-library files and copy
                    # out-of-library files.
                    if session.lib.directory in util.ancestry(old_path):
                        item.move(False)
                        # We moved the item, so remove the
                        # now-nonexistent file from old_paths.
                        task.old_paths.remove(old_path)
                    else:
                        item.move(True)
                else:
                    # A normal import. Just copy files and keep track of
                    # old paths.
                    item.move(True)

            if config['import']['write'] and task.should_write_tags():
                item.try_write()

        # Save new paths.
        with session.lib.transaction():
            for item in items:
                item.store()

        # Plugin event.
        plugins.send('import_task_files', session=session, task=task)
Beispiel #23
0
def import_files(lib, paths, copy, write, autot, logpath, art, threaded,
                 color, delete, quiet, resume, quiet_fallback):
    """Import the files in the given list of paths, tagging each leaf
    directory as an album. If copy, then the files are copied into
    the library folder. If write, then new metadata is written to the
    files themselves. If not autot, then just import the files
    without attempting to tag. If logpath is provided, then untaggable
    albums will be logged there. If art, then attempt to download
    cover art for each album. If threaded, then accelerate autotagging
    imports by running them in multiple threads. If color, then
    ANSI-colorize some terminal output. If delete, then old files are
    deleted when they are copied. If quiet, then the user is
    never prompted for input; instead, the tagger just skips anything
    it is not confident about. resume indicates whether interrupted
    imports can be resumed and is either a boolean or None.
    quiet_fallback should be either CHOICE_ASIS or CHOICE_SKIP and
    indicates what should happen in quiet mode when the recommendation
    is not strong.
    """
    # Open the log.
    if logpath:
        logfile = open(logpath, 'w')
    else:
        logfile = None

    # Never ask for input in quiet mode.
    if resume is None and quiet:
        resume = False
    
    # Perform the import.
    if autot:
        # Autotag. Set up the pipeline.
        pl = pipeline.Pipeline([
            read_albums(paths, resume),
            initial_lookup(),
            user_query(lib, logfile, color, quiet, quiet_fallback),
            apply_choices(lib, copy, write, art, delete, resume is not False),
        ])

        # Run the pipeline.
        try:
            if threaded:
                pl.run_parallel(QUEUE_SIZE)
            else:
                pl.run_sequential()
        except ImportAbort:
            # User aborted operation. Silently stop.
            pass
    else:
        # Simple import without autotagging. Always sequential.
        simple_import(lib, paths, copy, delete, resume)
    
    # If we were logging, close the file.
    if logfile:
        logfile.close()

    # Emit event.
    plugins.send('import', lib=lib, paths=paths)
Beispiel #24
0
def tracks_for_id(track_id):
    """Get a list of tracks for an ID."""
    t = track_for_mbid(track_id)
    if t:
        yield t
    for t in plugins.track_for_id(track_id):
        if t:
            plugins.send(u'trackinfo_received', info=t)
            yield t
Beispiel #25
0
def albums_for_id(album_id):
    """Get a list of albums for an ID."""
    a = album_for_mbid(album_id)
    if a:
        yield a
    for a in plugins.album_for_id(album_id):
        if a:
            plugins.send(u'albuminfo_received', info=a)
            yield a
Beispiel #26
0
def track_for_mbid(recording_id):
    """Get a TrackInfo object for a MusicBrainz recording ID. Return None
    if the ID is not found.
    """
    try:
        track = mb.track_for_id(recording_id)
        if track:
            plugins.send('trackinfo_received', info=track)
        return track
    except mb.MusicBrainzAPIError as exc:
        exc.log(log)
Beispiel #27
0
def album_for_mbid(release_id):
    """Get an AlbumInfo object for a MusicBrainz release ID. Return None
    if the ID is not found.
    """
    try:
        album = mb.album_for_id(release_id)
        if album:
            plugins.send('albuminfo_received', info=album)
        return album
    except mb.MusicBrainzAPIError as exc:
        exc.log(log)
Beispiel #28
0
    def write(self):
        """Writes the item's metadata to the associated file.
        """
        f = MediaFile(syspath(self.path))
        plugins.send('write', item=self, mf=f)
        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, getattr(self, key))
        f.save()

        # The file has a new mtime.
        self.mtime = self.current_mtime()
Beispiel #29
0
def user_query(session):
    """A coroutine for interfacing with the user about the tagging
    process. lib is the Library to import into and logfile may be
    a file-like object for logging the import process. The coroutine
    accepts and yields ImportTask objects.
    """
    recent = set()
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        # Ask the user for a choice.
        choice = session.choose_match(task)
        task.set_choice(choice)
        session.log_choice(task)
        plugins.send('import_task_choice', session=session, task=task)

        # As-tracks: transition to singleton workflow.
        if choice is action.TRACKS:
            # Set up a little pipeline for dealing with the singletons.
            item_tasks = []
            def emitter():
                for item in task.items:
                    yield ImportTask.item_task(item)
                yield ImportTask.progress_sentinel(task.toppath, task.paths)
            def collector():
                while True:
                    item_task = yield
                    item_tasks.append(item_task)
            ipl = pipeline.Pipeline((emitter(), item_lookup(session),
                                     item_query(session), collector()))
            ipl.run_sequential()
            task = pipeline.multiple(item_tasks)
            continue

        # Check for duplicates if we have a match (or ASIS).
        if task.choice_flag in (action.ASIS, action.APPLY):
            ident = task.chosen_ident()
            # The "recent" set keeps track of identifiers for recently
            # imported albums -- those that haven't reached the database
            # yet.
            task.duplicates = _duplicate_check(session.lib, task)
            if ident in recent:
                # TODO: Somehow manage duplicate hooks for recents
                pass
            plugins.send('import_task_duplicate', session=session, task=task)

            if task.duplicates:
                session.resolve_duplicate(task)
                session.log_choice(task, True)
            recent.add(ident)
Beispiel #30
0
def item_lookup(session):
    """A coroutine used to perform the initial MusicBrainz lookup for
    an item task.
    """
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        plugins.send('import_task_start', session=session, task=task)

        task.set_item_candidates(*autotag.tag_item(task.item))
Beispiel #31
0
def initial_lookup(session):
    """A coroutine for performing the initial MusicBrainz lookup for an
    album. It accepts lists of Items and yields
    (items, cur_artist, cur_album, candidates, rec) tuples. If no match
    is found, all of the yielded parameters (except items) are None.
    """
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        plugins.send('import_task_start', session=session, task=task)

        log.debug('Looking up: %s' % displayable_path(task.paths))
        task.set_candidates(*autotag.tag_album(
            task.items, config['import']['timid'].get(bool)))
Beispiel #32
0
    def test_hook_bytes_interpolation(self):
        temporary_paths = [
            get_temporary_path().encode('utf-8')
            for i in range(self.TEST_HOOK_COUNT)
        ]

        for index, path in enumerate(temporary_paths):
            self._add_hook(f'test_bytes_event_{index}', 'touch "{path}"')

        self.load_plugins('hook')

        for index, path in enumerate(temporary_paths):
            plugins.send(f'test_bytes_event_{index}', path=path)

        for path in temporary_paths:
            self.assertTrue(os.path.isfile(path))
            os.remove(path)
Beispiel #33
0
    def test_hook_no_arguments(self):
        temporary_paths = [
            get_temporary_path() for i in range(self.TEST_HOOK_COUNT)
        ]

        for index, path in enumerate(temporary_paths):
            self._add_hook(f'test_no_argument_event_{index}',
                           f'touch "{path}"')

        self.load_plugins('hook')

        for index in range(len(temporary_paths)):
            plugins.send(f'test_no_argument_event_{index}')

        for path in temporary_paths:
            self.assertTrue(os.path.isfile(path))
            os.remove(path)
Beispiel #34
0
def finalize(session):
    """A coroutine that finishes up importer tasks. In particular, the
    coroutine sends plugin events, deletes old files, and saves
    progress. This is a "terminal" coroutine (it yields None).
    """
    while True:
        task = yield
        if task.should_skip():
            if _resume():
                task.save_progress()
            if config['import']['incremental']:
                task.save_history()
            continue

        items = task.imported_items()

        # Announce that we've added an album.
        if task.is_album:
            album = session.lib.get_album(task.album_id)
            plugins.send('album_imported',
                         lib=session.lib, album=album)
        else:
            for item in items:
                plugins.send('item_imported',
                             lib=session.lib, item=item)

        # When copying and deleting originals, delete old files.
        if config['import']['copy'] and config['import']['delete']:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in task.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    util.remove(syspath(old_path), False)
                    task.prune(old_path)

        # When moving, prune empty directories containing the original
        # files.
        elif config['import']['move']:
            for old_path in task.old_paths:
                task.prune(old_path)

        # Update progress.
        if _resume():
            task.save_progress()
        if config['import']['incremental']:
            task.save_history()
Beispiel #35
0
def import_files(lib, paths, query):
    """Import the files in the given list of paths or matching the
    query.
    """
    # Check the user-specified directories.
    for path in paths:
        fullpath = syspath(normpath(path))
        if not config['import']['singletons'] and not os.path.isdir(fullpath):
            raise ui.UserError(u'not a directory: {0}'.format(
                displayable_path(path)))
        elif config['import']['singletons'] and not os.path.exists(fullpath):
            raise ui.UserError(u'no such file: {0}'.format(
                displayable_path(path)))

    # Check parameter consistency.
    if config['import']['quiet'] and config['import']['timid']:
        raise ui.UserError("can't be both quiet and timid")

    # Open the log.
    if config['import']['log'].get() is not None:
        logpath = config['import']['log'].as_filename()
        try:
            logfile = codecs.open(syspath(logpath), 'a', 'utf8')
        except IOError:
            raise ui.UserError(u"could not open log file for writing: %s" %
                               displayable_path(logpath))
        print(u'import started', time.asctime(), file=logfile)
    else:
        logfile = None

    # Never ask for input in quiet mode.
    if config['import']['resume'].get() == 'ask' and \
            config['import']['quiet']:
        config['import']['resume'] = False

    session = TerminalImportSession(lib, logfile, paths, query)
    try:
        session.run()
    finally:
        # If we were logging, close the file.
        if logfile:
            print(u'', file=logfile)
            logfile.close()

    # Emit event.
    plugins.send('import', lib=lib, paths=paths)
Beispiel #36
0
    def test_hook_argument_substitution(self):
        temporary_paths = [
            get_temporary_path() for i in range(self.TEST_HOOK_COUNT)
        ]

        for index, path in enumerate(temporary_paths):
            self._add_hook('test_argument_event_{0}'.format(index),
                           'touch "{path}"')

        self.load_plugins('hook')

        for index, path in enumerate(temporary_paths):
            plugins.send('test_argument_event_{0}'.format(index), path=path)

        for path in temporary_paths:
            self.assertTrue(os.path.isfile(path))
            os.remove(path)
Beispiel #37
0
def apply_choices(session, task):
    """A coroutine for applying changes to albums and singletons during
    the autotag process.
    """
    if task.skip:
        return

    # Change metadata.
    if task.apply:
        task.apply_metadata()
        plugins.send('import_task_apply', session=session, task=task)

    # Infer album-level fields.
    if task.is_album:
        task.infer_album_fields()

    task.add(session.lib)
Beispiel #38
0
def finalize(config):
    """A coroutine that finishes up importer tasks. In particular, the
    coroutine sends plugin events, deletes old files, and saves
    progress. This is a "terminal" coroutine (it yields None).
    """
    lib = _reopen_lib(config.lib)
    while True:
        task = yield
        if task.should_skip():
            if config.resume is not False:
                task.save_progress()
            if config.incremental:
                task.save_history()
            continue

        items = [i for i in task.items if i] if task.is_album else [task.item]

        # Announce that we've added an album.
        if task.is_album:
            album = lib.get_album(task.album_id)
            plugins.send('album_imported', lib=lib, album=album, config=config)
        else:
            for item in items:
                plugins.send('item_imported',
                             lib=lib,
                             item=item,
                             config=config)

        # Finally, delete old files.
        if config.copy and config.delete:
            new_paths = [os.path.realpath(item.path) for item in items]
            for old_path in task.old_paths:
                # Only delete files that were actually copied.
                if old_path not in new_paths:
                    os.remove(syspath(old_path))
                    # Clean up directory if it is emptied.
                    if task.toppath:
                        util.prune_dirs(os.path.dirname(old_path),
                                        task.toppath)

        # Update progress.
        if config.resume is not False:
            task.save_progress()
        if config.incremental:
            task.save_history()
Beispiel #39
0
    def write(self, path=None, tags=None):
        """Write the item's metadata to a media file.

        All fields in `_media_fields` are written to disk according to
        the values on this object.

        `path` is the path of the mediafile to write the data to. It
        defaults to the item's path.

        `tags` is a dictionary of additional metadata the should be
        written to the file. (These tags need not be in `_media_fields`.)

        Can raise either a `ReadError` or a `WriteError`.
        """
        if path is None:
            path = self.path
        else:
            path = normpath(path)

        # Get the data to write to the file.
        item_tags = dict(self)
        item_tags = {k: v for k, v in item_tags.items()
                     if k in self._media_fields}  # Only write media fields.
        if tags is not None:
            item_tags.update(tags)
        plugins.send('write', item=self, path=path, tags=item_tags)

        # Open the file.
        try:
            mediafile = MediaFile(syspath(path),
                                  id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, UnreadableFileError) as exc:
            raise ReadError(self.path, exc)

        # Write the tags to the file.
        mediafile.update(item_tags)
        try:
            mediafile.save()
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        if path == self.path:
            self.mtime = self.current_mtime()
        plugins.send('after_write', item=self, path=path)
 def explicits(lib, opts, args):
     args = ui.decargs(args)
     items = lib.items(args)
     results = self.spotify._match_library_tracks(lib, args)
     if results:
         for item, track in zip(items, results):
             if track['explicit']:
                 title = track['name']
                 album = track['album']['name']
                 artist = track['artists'][0]['name']
                 tracknum = track['track_number']
                 url = track['external_urls']['spotify']
                 plugins.send("spotify_explicit_track",
                              lib=lib,
                              track=track,
                              item=item)
                 print('{} - {} - {} - {} - {}'.format(
                     album, tracknum, artist, title, url))
Beispiel #41
0
    def move_file(self, dest, copy=False):
        """Moves or copies the item's file, updating the path value if
        the move succeeds. If a file exists at ``dest``, then it is
        slightly modified to be unique.
        """
        if not util.samefile(self.path, dest):
            dest = util.unique_path(dest)
        if copy:
            util.copy(self.path, dest)
            plugins.send("item_copied", item=self, source=self.path,
                         destination=dest)
        else:
            util.move(self.path, dest)
            plugins.send("item_moved", item=self, source=self.path,
                         destination=dest)

        # Either copying or moving succeeded, so update the stored path.
        self.path = dest
Beispiel #42
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_format_option(flags=('--format-item',), target=library.Item)
    parser.add_format_option(flags=('--format-album',), target=library.Album)
    parser.add_option('-l', '--library', dest='library',
                      help=u'library database file to use')
    parser.add_option('-d', '--directory', dest='directory',
                      help=u"destination music directory")
    parser.add_option('-v', '--verbose', dest='verbose', action='count',
                      help=u'log more details (use twice for even more)')
    parser.add_option('-c', '--config', dest='config',
                      help=u'path to configuration file')
    parser.add_option('-p', '--plugins', dest='plugins',
                      help=u'a comma-separated list of plugins to load')
    parser.add_option('-h', '--help', dest='help', action='store_true',
                      help=u'show this help message and exit')
    parser.add_option('--version', dest='version', action='store_true',
                      help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)

    # Special case for the `config --edit` command: bypass _setup so
    # that an invalid configuration does not prevent the editor from
    # starting.
    if subargs and subargs[0] == 'config' \
       and ('-e' in subargs or '--edit' in subargs):
        from beets.ui.commands import config_edit
        return config_edit()

    test_lib = bool(lib)
    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
    if not test_lib:
        # Clean up the library unless it came from the test harness.
        lib._close()
Beispiel #43
0
def initial_lookup(config):
    """A coroutine for performing the initial MusicBrainz lookup for an
    album. It accepts lists of Items and yields
    (items, cur_artist, cur_album, candidates, rec) tuples. If no match
    is found, all of the yielded parameters (except items) are None.
    """
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        plugins.send('import_task_start', task=task, config=config)

        log.debug('Looking up: %s' % task.path)
        try:
            task.set_match(*autotag.tag_album(task.items, config.timid))
        except autotag.AutotagError:
            task.set_null_match()
Beispiel #44
0
    def test_hook_event_substitution(self):
        temporary_directory = tempfile._get_default_tempdir()
        event_names = ['test_event_event_{0}'.format(i) for i in
                       range(self.TEST_HOOK_COUNT)]

        for event in event_names:
            self._add_hook(event,
                           'touch "{0}/{{event}}"'.format(temporary_directory))

        self.load_plugins('hook')

        for event in event_names:
            plugins.send(event)

        for event in event_names:
            path = os.path.join(temporary_directory, event)

            self.assertTrue(os.path.isfile(path))
            os.remove(path)
Beispiel #45
0
    def run(self):
        """Run the import task.
        """
        self.set_config(config['import'])

        # Set up the pipeline.
        if self.query is None:
            stages = [read_tasks(self)]
        else:
            stages = [query_tasks(self)]

        if self.config['pretend']:
            # Only log the imported files and end the pipeline
            stages += [log_files(self)]
        else:
            if self.config['group_albums'] and \
               not self.config['singletons']:
                # Split directory tasks into one task for each album
                stages += [group_albums(self)]
            if self.config['autotag']:
                # FIXME We should also resolve duplicates when not
                # autotagging. This is currently handled in `user_query`
                stages += [lookup_candidates(self), user_query(self)]
            else:
                stages += [import_asis(self)]
            stages += [apply_choices(self)]

            for stage_func in plugins.import_stages():
                stages.append(plugin_stage(self, stage_func))
            stages += [manipulate_files(self)]
        pl = pipeline.Pipeline(stages)

        # Run the pipeline.
        plugins.send('import_begin', session=self)
        try:
            if config['threaded']:
                pl.run_parallel(QUEUE_SIZE)
            else:
                pl.run_sequential()
        except ImportAbort:
            # User aborted operation. Silently stop.
            pass
Beispiel #46
0
def _raw_main(args):
    """A helper function for `main` without top-level exception
    handling.
    """
    subcommand, suboptions, subargs = _configure(args)

    # Open library file.
    dbpath = config['library'].as_filename()
    try:
        lib = library.Library(
            dbpath,
            config['directory'].as_filename(),
            get_path_formats(),
            get_replacements(),
        )
    except sqlite3.OperationalError:
        raise UserError(u"database file {0} could not be opened".format(
            util.displayable_path(dbpath)
        ))
    plugins.send("library_opened", lib=lib)

    # Configure the logger.
    if config['verbose'].get(bool):
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)
    log.debug(u'data directory: {0}\n'
              u'library database: {1}\n'
              u'library directory: {2}'
        .format(
            util.displayable_path(config.config_dir()),
            util.displayable_path(lib.path),
            util.displayable_path(lib.directory),
        )
    )

    # Configure the MusicBrainz API.
    mb.configure()

    # Invoke the subcommand.
    subcommand.func(lib, suboptions, subargs)
    plugins.send('cli_exit', lib=lib)
Beispiel #47
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """

    parser = SubcommandsOptionParser()
    options, subargs = parser.parse_global_options(args)

    subcommands, plugins, lib = _setup(options, lib)

    parser.add_subcommand(*subcommands)

    if options.version:
        from beets.ui import commands
        commands.version_cmd.func(lib, None, None)
    else:
        subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
        subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
Beispiel #48
0
    def manipulate_files(self,
                         move=False,
                         copy=False,
                         write=False,
                         session=None):
        items = self.imported_items()
        # Save the original paths of all items for deletion and pruning
        # in the next step (finalization).
        self.old_paths = [item.path for item in items]
        for item in items:
            if session.config['move']:
                # Just move the file.
                item.move(False)
            elif session.config['copy']:
                # If it's a reimport, move in-library files and copy
                # out-of-library files. Otherwise, copy and keep track
                # of the old path.
                old_path = item.path
                if self.replaced_items[item]:
                    # This is a reimport. Move in-library files and copy
                    # out-of-library files.
                    if session.lib.directory in util.ancestry(old_path):
                        item.move(False)
                        # We moved the item, so remove the
                        # now-nonexistent file from old_paths.
                        self.old_paths.remove(old_path)
                    else:
                        item.move(True)
                else:
                    # A normal import. Just copy files and keep track of
                    # old paths.
                    item.move(True)

            if session.config['write'] and self.apply:
                item.try_write()

        with session.lib.transaction():
            for item in self.imported_items():
                item.store()

        plugins.send('import_task_files', session=session, task=self)
Beispiel #49
0
def _load_plugins(config):
    """Load the plugins specified in the configuration.
    """
    paths = config['pluginpath'].as_str_seq(split=False)
    paths = [util.normpath(p) for p in paths]
    log.debug(u'plugin paths: {0}', util.displayable_path(paths))

    # On Python 3, the search paths need to be unicode.
    paths = [util.py3_path(p) for p in paths]

    # Extend the `beetsplug` package to include the plugin paths.
    import beetsplug
    beetsplug.__path__ = paths + beetsplug.__path__

    # For backwards compatibility, also support plugin paths that
    # *contain* a `beetsplug` package.
    sys.path += paths

    plugins.load_plugins(config['plugins'].as_str_seq())
    plugins.send("pluginload")
    return plugins
Beispiel #50
0
    def test_events_called(self, mock_inspect, mock_find_plugins):
        mock_inspect.getargspec.return_value = None

        class DummyPlugin(plugins.BeetsPlugin):
            def __init__(self):
                super(DummyPlugin, self).__init__()
                self.foo = Mock(__name__=b'foo')
                self.register_listener('event_foo', self.foo)
                self.bar = Mock(__name__=b'bar')
                self.register_listener('event_bar', self.bar)

        d = DummyPlugin()
        mock_find_plugins.return_value = d,

        plugins.send('event')
        d.foo.assert_has_calls([])
        d.bar.assert_has_calls([])

        plugins.send('event_foo', var="tagada")
        d.foo.assert_called_once_with(var="tagada")
        d.bar.assert_has_calls([])
Beispiel #51
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_option('-l',
                      '--library',
                      dest='library',
                      help='library database file to use')
    parser.add_option('-d',
                      '--directory',
                      dest='directory',
                      help="destination music directory")
    parser.add_option('-v',
                      '--verbose',
                      dest='verbose',
                      action='store_true',
                      help='print debugging information')
    parser.add_option('-c',
                      '--config',
                      dest='config',
                      help='path to configuration file')
    parser.add_option('-h',
                      '--help',
                      dest='help',
                      action='store_true',
                      help='how this help message and exit')
    parser.add_option('--version',
                      dest='version',
                      action='store_true',
                      help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)
    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
Beispiel #52
0
def item_candidates(item, artist, title):
    """Search for item matches. ``item`` is the Item to be matched.
    ``artist`` and ``title`` are strings and either reflect the item or
    are specified by the user.
    """
    out = []

    # MusicBrainz candidates.
    if artist and title:
        try:
            out.extend(mb.match_track(artist, title))
        except mb.MusicBrainzAPIError as exc:
            exc.log(log)

    # Plugin candidates.
    out.extend(plugins.item_candidates(item, artist, title))

    # Notify subscribed plugins about fetched track info
    for i in out:
        plugins.send('trackinfo_received', info=i)

    return out
Beispiel #53
0
    def write(self):
        """Write the item's metadata to the associated file.

        Can raise either a `ReadError` or a `WriteError`.
        """
        try:
            f = MediaFile(syspath(self.path))
        except (OSError, IOError) as exc:
            raise ReadError(self.path, exc)

        plugins.send('write', item=self)

        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, self[key])
        try:
            f.save(id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError, MutagenError) as exc:
            raise WriteError(self.path, exc)

        # The file has a new mtime.
        self.mtime = self.current_mtime()
        plugins.send('after_write', item=self)
Beispiel #54
0
    def write(self):
        """Writes the item's metadata to the associated file.
        """
        plugins.send('write', item=self)

        try:
            f = MediaFile(syspath(self.path))
        except (OSError, IOError) as exc:
            raise util.FilesystemError(exc, 'read', (self.path, ),
                                       traceback.format_exc())

        for key in ITEM_KEYS_WRITABLE:
            setattr(f, key, self[key])

        try:
            f.save(id3v23=beets.config['id3v23'].get(bool))
        except (OSError, IOError) as exc:
            raise util.FilesystemError(exc, 'write', (self.path, ),
                                       traceback.format_exc())

        # The file has a new mtime.
        self.mtime = self.current_mtime()
Beispiel #55
0
    def remove(self, delete=False, with_album=True):
        """Removes the item. If `delete`, then the associated file is
        removed from disk. If `with_album`, then the item's album (if
        any) is removed if it the item was the last in the album.
        """
        super(Item, self).remove()

        # Remove the album if it is empty.
        if with_album:
            album = self.get_album()
            if album and not album.items():
                album.remove(delete, False)

        # Send a 'item_removed' signal to plugins
        plugins.send('item_removed', item=self)

        # Delete the associated file.
        if delete:
            util.remove(self.path)
            util.prune_dirs(os.path.dirname(self.path), self._db.directory)

        self._db._memotable = {}
Beispiel #56
0
def item_candidates(item, artist, title):
    """Search for item matches. ``item`` is the Item to be matched.
    ``artist`` and ``title`` are strings and either reflect the item or
    are specified by the user.
    """

    # MusicBrainz candidates.
    if artist and title:
        try:
            for candidate in mb.match_track(artist, title):
                yield TrackAlbumTuple(candidate, None)
        except mb.MusicBrainzAPIError as exc:
            exc.log(log)

    # Plugin candidates.
    for candidate in plugins.item_candidates(item, artist, title):
        # allow (track_info, album_info) tuples
        plugins.send(u'trackinfo_received', info=candidate)
        if isinstance(candidate, TrackAlbumTuple):
            yield candidate
        else:

            yield TrackAlbumTuple(candidate, None)
Beispiel #57
0
def item_query(session):
    """A coroutine that queries the user for input on single-item
    lookups.
    """
    task = None
    recent = set()
    while True:
        task = yield task
        if task.sentinel:
            continue

        choice = session.choose_item(task)
        task.set_choice(choice)
        session.log_choice(task)
        plugins.send('import_task_choice', session=session, task=task)

        # Duplicate check.
        if task.choice_flag in (action.ASIS, action.APPLY):
            ident = task.chosen_ident()
            if ident in recent or _item_duplicate_check(session.lib, task):
                session.resolve_duplicate(task)
                session.log_choice(task, True)
            recent.add(ident)
Beispiel #58
0
def match_benchmark(lib, prof, query=None, album_id=None):
    # If no album ID is provided, we'll match against a suitably huge
    # album.
    if not album_id:
        album_id = '9c5c043e-bc69-4edb-81a4-1aaf9c81e6dc'

    # Get an album from the library to use as the source for the match.
    items = lib.albums(query).get().items()

    # Ensure fingerprinting is invoked (if enabled).
    plugins.send('import_task_start',
                 task=importer.ImportTask(None, None, items),
                 session=importer.ImportSession(lib, None, None, None))

    # Run the match.
    def _run_match():
        match.tag_album(items, search_id=album_id)
    if prof:
        cProfile.runctx('_run_match()', {}, {'_run_match': _run_match},
                        'match.prof')
    else:
        interval = timeit.timeit(_run_match, number=1)
        print('match duration:', interval)
Beispiel #59
0
def _raw_main(args, lib=None):
    """A helper function for `main` without top-level exception
    handling.
    """
    parser = SubcommandsOptionParser()
    parser.add_format_option(flags=('--format-item',), target=library.Item)
    parser.add_format_option(flags=('--format-album',), target=library.Album)
    parser.add_option('-l', '--library', dest='library',
                      help='library database file to use')
    parser.add_option('-d', '--directory', dest='directory',
                      help="destination music directory")
    parser.add_option('-v', '--verbose', dest='verbose', action='count',
                      help='print debugging information')
    parser.add_option('-c', '--config', dest='config',
                      help='path to configuration file')
    parser.add_option('-h', '--help', dest='help', action='store_true',
                      help='how this help message and exit')
    parser.add_option('--version', dest='version', action='store_true',
                      help=optparse.SUPPRESS_HELP)

    options, subargs = parser.parse_global_options(args)

    # Special case for the `config --edit` command: bypass _setup so
    # that an invalid configuration does not prevent the editor from
    # starting.
    if subargs and subargs[0] == 'config' \
       and ('-e' in subargs or '--edit' in subargs):
        from beets.ui.commands import config_edit
        return config_edit()

    subcommands, plugins, lib = _setup(options, lib)
    parser.add_subcommand(*subcommands)

    subcommand, suboptions, subargs = parser.parse_subcommand(subargs)
    subcommand.func(lib, suboptions, subargs)

    plugins.send('cli_exit', lib=lib)
Beispiel #60
0
def _setup(options, lib=None):
    """Prepare and global state and updates it with command line options.

    Returns a list of subcommands, a list of plugins, and a library instance.
    """
    # Configure the MusicBrainz API.
    mb.configure()

    config = _configure(options)

    plugins = _load_plugins(options, config)

    # Add types and queries defined by plugins.
    plugin_types_album = plugins.types(library.Album)
    library.Album._types.update(plugin_types_album)
    item_types = plugin_types_album.copy()
    item_types.update(library.Item._types)
    item_types.update(plugins.types(library.Item))
    library.Item._types = item_types

    library.Item._queries.update(plugins.named_queries(library.Item))
    library.Album._queries.update(plugins.named_queries(library.Album))

    plugins.send("pluginload")

    # Get the default subcommands.
    from beets.ui.commands import default_commands

    subcommands = list(default_commands)
    subcommands.extend(plugins.commands())

    if lib is None:
        lib = _open_library(config)
        plugins.send("library_opened", lib=lib)

    return subcommands, plugins, lib