Пример #1
0
    def move(self, copy=False):
        """Moves (or copies) all items to their destination. Any
        album art moves along with them.
        """
        # Move items.
        items = list(self.items())
        for item in items:
            item.move(self._library, copy)
        newdir = os.path.dirname(items[0].path)

        # Move art.
        old_art = self.artpath
        if old_art:
            new_art = self.art_destination(old_art, newdir)
            if new_art != old_art:
                if copy:
                    shutil.copy(syspath(old_art), syspath(new_art))
                else:
                    shutil.move(syspath(old_art), syspath(new_art))
                self.artpath = new_art

        # Store new item paths. We do this at the end to avoid
        # locking the database for too long while files are copied.
        for item in items:
            self._library.store(item)
Пример #2
0
 def set_art(self, path):
     """Sets the album's cover art to the image at the given path.
     The image is copied into place, replacing any existing art.
     """
     path = bytestring_path(path)
     oldart = self.artpath
     artdest = self.art_destination(path)
     if oldart == artdest:
         util.soft_remove(oldart)
     
     shutil.copyfile(syspath(path), syspath(artdest))
     self.artpath = artdest
Пример #3
0
 def write(self):
     """Writes the item's metadata to the associated file.
     """
     f = MediaFile(syspath(self.path))
     for key in ITEM_KEYS_WRITABLE:
         setattr(f, key, getattr(self, key))
     f.save()
Пример #4
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()
            continue

        items = task.items 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)
        else:
            for item in items:
                plugins.send('item_imported', lib=lib, item=item)

        # 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 moved.
                if old_path not in new_paths:
                    os.remove(syspath(old_path))

        # Update progress.
        if config.resume is not False:
            task.save_progress()
Пример #5
0
 def write(self):
     """Writes the item's metadata to the associated file.
     """
     f = MediaFile(syspath(self.path))
     for key in ITEM_KEYS_WRITABLE:
         setattr(f, key, getattr(self, key))
     f.save()
Пример #6
0
 def write(self):
     """Writes the item's metadata to the associated file.
     """
     f = MediaFile(syspath(self.path))
     for key in ITEM_KEYS_WRITABLE:
         if getattr(self, key):  #make sure it has a value before we set it and create blank tags with wrong types
             setattr(f, key, getattr(self, key))
     f.save()
Пример #7
0
def read_tasks(config):
    """A generator yielding all the albums (as ImportTask objects) found
    in the user-specified list of paths. In the case of a singleton
    import, yields single-item tasks instead.
    """
    # Look for saved progress.
    progress = config.resume is not False
    if progress:
        resume_dirs = {}
        for path in config.paths:
            resume_dir = progress_get(path)
            if resume_dir:

                # Either accept immediately or prompt for input to decide.
                if config.resume:
                    do_resume = True
                    log.warn('Resuming interrupted import of %s' % path)
                else:
                    do_resume = config.should_resume_func(config, path)

                if do_resume:
                    resume_dirs[path] = resume_dir
                else:
                    # Clear progress; we're starting from the top.
                    progress_set(path, None)
    
    for toppath in config.paths:
        # Check whether the path is to a file.
        if config.singletons and not os.path.isdir(syspath(toppath)):
            item = library.Item.from_path(toppath)
            yield ImportTask.item_task(item)
            continue
        
        # Produce paths under this directory.
        if progress:
            resume_dir = resume_dirs.get(toppath)
        for path, items in autotag.albums_in_dir(toppath):
            if progress and resume_dir:
                # We're fast-forwarding to resume a previous tagging.
                if path == resume_dir:
                    # We've hit the last good path! Turn off the
                    # fast-forwarding.
                    resume_dir = None
                continue

            # Yield all the necessary tasks.
            if config.singletons:
                for item in items:
                    yield ImportTask.item_task(item)
                yield ImportTask.progress_sentinel(toppath, path)
            else:
                yield ImportTask(toppath, path, items)

        # Indicate the directory is finished.
        yield ImportTask.done_sentinel(toppath)
Пример #8
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()
Пример #9
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()
Пример #10
0
    def read(self, read_path=None):
        """Read the metadata from the associated file. If read_path is
        specified, read metadata from that file instead.
        """
        if read_path is None:
            read_path = self.path
        else:
            read_path = normpath(read_path)
        f = MediaFile(syspath(read_path))

        for key in ITEM_KEYS_META:
            setattr(self, key, getattr(f, key))
        self.path = read_path
Пример #11
0
    def read(self, read_path=None):
        """Read the metadata from the associated file. If read_path is
        specified, read metadata from that file instead.
        """
        if read_path is None:
            read_path = self.path
        else:
            read_path = normpath(read_path)
        f = MediaFile(syspath(read_path))

        for key in ITEM_KEYS_META:
            setattr(self, key, getattr(f, key))
        self.path = read_path
Пример #12
0
    def read(self, read_path=None):
        """Read the metadata from the associated file. If read_path is
        specified, read metadata from that file instead.
        """
        if read_path is None:
            read_path = self.path
        else:
            read_path = normpath(read_path)
        f = MediaFile(syspath(read_path))

        for key in ITEM_KEYS_META:
            setattr(self, key, getattr(f, key))
        self.path = read_path

        # Database's mtime should now reflect the on-disk value.
        if read_path == self.path:
            self.mtime = self.current_mtime()
Пример #13
0
    def read(self, read_path=None):
        """Read the metadata from the associated file. If read_path is
        specified, read metadata from that file instead.
        """
        if read_path is None:
            read_path = self.path
        else:
            read_path = normpath(read_path)
        f = MediaFile(syspath(read_path))

        for key in ITEM_KEYS_META:
            setattr(self, key, getattr(f, key))
        self.path = read_path

        # Database's mtime should now reflect the on-disk value.
        if read_path == self.path:
            self.mtime = self.current_mtime()
Пример #14
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()
Пример #15
0
def apply_choices(config):
    """A coroutine for applying changes to albums during the autotag
    process.
    """
    lib = _reopen_lib(config.lib)
    task = None
    while True:    
        task = yield task
        if task.should_skip():
            continue

        # Change metadata, move, and copy.
        if task.should_write_tags():
            if task.is_album:
                autotag.apply_metadata(task.items, task.info)
            else:
                autotag.apply_item_metadata(task.item, task.info)
        items = task.items if task.is_album else [task.item]
        if config.copy and config.delete:
            task.old_paths = [os.path.realpath(syspath(item.path))
                              for item in items]
        for item in items:
            if config.copy:
                item.move(lib, True, task.is_album)
            if config.write and task.should_write_tags():
                item.write()

        # Add items to library. We consolidate this at the end to avoid
        # locking while we do the copying and tag updates.
        try:
            if task.is_album:
                # Add an album.
                album = lib.add_album(task.items,
                                      infer_aa = task.should_infer_aa())
                task.album_id = album.id
            else:
                # Add tracks.
                for item in items:
                    lib.add(item)
        finally:
            lib.save()
Пример #16
0
    def move(self, library, copy=False, in_album=False):
        """Move the item to its designated location within the library
        directory (provided by destination()). Subdirectories are
        created as needed. If the operation succeeds, the item's path
        field is updated to reflect the new location.
        
        If copy is True, moving the file is copied rather than moved.
        
        If in_album is True, then the track is treated as part of an
        album even if it does not yet have an album_id associated with
        it. (This allows items to be moved before they are added to the
        database, a performance optimization.)

        Passes on appropriate exceptions if directories cannot be created
        or moving/copying fails.
        
        Note that one should almost certainly call store() and
        library.save() after this method in order to keep on-disk data
        consistent.
        """
        dest = library.destination(self, in_album=in_album)
        
        # Create necessary ancestry for the move.
        util.mkdirall(dest)
        
        if not shutil._samefile(syspath(self.path), syspath(dest)):
            if copy:
                # copyfile rather than copy will not copy permissions
                # bits, thus possibly making the copy writable even when
                # the original is read-only.
                shutil.copyfile(syspath(self.path), syspath(dest))
            else:
                shutil.move(syspath(self.path), syspath(dest))
            
        # Either copying or moving succeeded, so update the stored path.
        self.path = dest
Пример #17
0
 def current_mtime(self):
     """Returns the current mtime of the file, rounded to the nearest
     integer.
     """
     return int(os.path.getmtime(syspath(self.path)))
Пример #18
0
def main(args=None, configfh=None):
    """Run the main command-line interface for beets."""
    # Get the default subcommands.
    from beets.ui.commands import default_commands

    # Read defaults from config file.
    config = ConfigParser.SafeConfigParser()
    if configfh:
        configpath = None
    elif CONFIG_PATH_VAR in os.environ:
        configpath = os.path.expanduser(os.environ[CONFIG_PATH_VAR])
    else:
        configpath = DEFAULT_CONFIG_FILE
    if configpath:
        configpath = util.syspath(configpath)
        if os.path.exists(util.syspath(configpath)):
            configfh = open(configpath)
        else:
            configfh = None
    if configfh:
        config.readfp(configfh)

    # Add plugin paths.
    plugpaths = config_val(config, 'beets', 'pluginpath', '')
    for plugpath in plugpaths.split(':'):
        sys.path.append(os.path.expanduser(plugpath))
    # Load requested plugins.
    plugnames = config_val(config, 'beets', 'plugins', '')
    plugins.load_plugins(plugnames.split())
    plugins.load_listeners()
    plugins.send("pluginload")
    plugins.configure(config)

    # Construct the root parser.
    commands = list(default_commands)
    commands += plugins.commands()
    parser = SubcommandsOptionParser(subcommands=commands)
    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('-v',
                      '--verbose',
                      dest='verbose',
                      action='store_true',
                      help='print debugging information')

    # Parse the command-line!
    options, subcommand, suboptions, subargs = parser.parse_args(args)

    # Open library file.
    libpath = options.libpath or \
        config_val(config, 'beets', 'library', DEFAULT_LIBRARY)
    directory = options.directory or \
        config_val(config, 'beets', 'directory', DEFAULT_DIRECTORY)
    legacy_path_format = config_val(config, 'beets', 'path_format', None)
    if options.path_format:
        # If given, -p overrides all path format settings
        path_formats = {'default': options.path_format}
    else:
        if legacy_path_format:
            # Old path formats override the default values.
            path_formats = {'default': legacy_path_format}
        else:
            # If no legacy path format, use the defaults instead.
            path_formats = DEFAULT_PATH_FORMATS
        if config.has_section('paths'):
            path_formats.update(config.items('paths'))
    art_filename = \
        config_val(config, 'beets', 'art_filename', DEFAULT_ART_FILENAME)
    db_path = os.path.expanduser(libpath)
    try:
        lib = library.Library(db_path, directory, path_formats, art_filename)
    except sqlite3.OperationalError:
        raise UserError("database file %s could not be opened" % db_path)

    # Configure the logger.
    log = logging.getLogger('beets')
    if options.verbose:
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)

    # Invoke the subcommand.
    try:
        subcommand.func(lib, config, suboptions, subargs)
    except UserError, exc:
        message = exc.args[0] if exc.args else None
        subcommand.parser.error(message)
Пример #19
0
def update_items(lib, query, album, move, color, pretend):
    """For all the items matched by the query, update the library to
    reflect the item's embedded tags.
    """
    items, _ = _do_query(lib, query, album)

    # Walk through the items and pick up their changes.
    affected_albums = set()
    for item in items:
        # Item deleted?
        if not os.path.exists(syspath(item.path)):
            print_(u'X %s - %s' % (item.artist, item.title))
            if not pretend:
                lib.remove(item, True)
            affected_albums.add(item.album_id)
            continue

        # Did the item change since last checked?
        if item.current_mtime() <= item.mtime:
            log.debug(u'skipping %s because mtime is up to date (%i)' %
                      (displayable_path(item.path), item.mtime))
            continue

        # Read new data.
        old_data = dict(item.record)
        item.read()

        # Special-case album artist when it matches track artist. (Hacky
        # but necessary for preserving album-level metadata for non-
        # autotagged imports.)
        if not item.albumartist and \
                old_data['albumartist'] == old_data['artist'] == item.artist:
            item.albumartist = old_data['albumartist']
            item.dirty['albumartist'] = False

        # Get and save metadata changes.
        changes = {}
        for key in library.ITEM_KEYS_META:
            if item.dirty[key]:
                changes[key] = old_data[key], getattr(item, key)
        if changes:
            # Something changed.
            print_(u'* %s - %s' % (item.artist, item.title))
            for key, (oldval, newval) in changes.iteritems():
                _showdiff(key, oldval, newval, color)

            # If we're just pretending, then don't move or save.
            if pretend:
                continue

            # Move the item if it's in the library.
            if move and lib.directory in ancestry(item.path):
                lib.move(item)

            lib.store(item)
            affected_albums.add(item.album_id)
        elif not pretend:
            # The file's mtime was different, but there were no changes
            # to the metadata. Store the new mtime, which is set in the
            # call to read(), so we don't check this again in the
            # future.
            lib.store(item)

    # Skip album changes while pretending.
    if pretend:
        return

    # Modify affected albums to reflect changes in their items.
    for album_id in affected_albums:
        if album_id is None: # Singletons.
            continue
        album = lib.get_album(album_id)
        if not album: # Empty albums have already been removed.
            log.debug('emptied album %i' % album_id)
            continue
        al_items = list(album.items())

        # Update album structure to reflect an item in it.
        for key in library.ALBUM_KEYS_ITEM:
            setattr(album, key, getattr(al_items[0], key))

        # Move album art (and any inconsistent items).
        if move and lib.directory in ancestry(al_items[0].path):
            log.debug('moving album %i' % album_id)
            album.move()

    lib.save()
Пример #20
0
def main(args=None, configfh=None):
    """Run the main command-line interface for beets."""
    # Get the default subcommands.
    from lib.beets.ui.commands import default_commands

    # Get default file paths.
    default_config, default_libpath, default_dir = default_paths()

    # Read defaults from config file.
    config = ConfigParser.SafeConfigParser()
    if configfh:
        configpath = None
    elif CONFIG_PATH_VAR in os.environ:
        configpath = os.path.expanduser(os.environ[CONFIG_PATH_VAR])
    else:
        configpath = default_config
    if configpath:
        configpath = util.syspath(configpath)
        if os.path.exists(util.syspath(configpath)):
            configfh = open(configpath)
        else:
            configfh = None
    if configfh:
        config.readfp(configfh)

    # Add plugin paths.
    plugpaths = config_val(config, 'beets', 'pluginpath', '')
    for plugpath in plugpaths.split(':'):
        sys.path.append(os.path.expanduser(plugpath))
    # Load requested plugins.
    plugnames = config_val(config, 'beets', 'plugins', '')
    plugins.load_plugins(plugnames.split())
    plugins.load_listeners()
    plugins.send("pluginload")
    plugins.configure(config)

    # Construct the root parser.
    commands = list(default_commands)
    commands += plugins.commands()
    parser = SubcommandsOptionParser(subcommands=commands)
    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('-v', '--verbose', dest='verbose', action='store_true',
                      help='print debugging information')
    
    # Parse the command-line!
    options, subcommand, suboptions, subargs = parser.parse_args(args)
    
    # Open library file.
    libpath = options.libpath or \
        config_val(config, 'beets', 'library', default_libpath)
    directory = options.directory or \
        config_val(config, 'beets', 'directory', default_dir)
    path_formats = _get_path_formats(config)
    art_filename = \
        config_val(config, 'beets', 'art_filename', DEFAULT_ART_FILENAME)
    lib_timeout = config_val(config, 'beets', 'timeout', DEFAULT_TIMEOUT)
    replacements = _get_replacements(config)
    try:
        lib_timeout = float(lib_timeout)
    except ValueError:
        lib_timeout = DEFAULT_TIMEOUT
    db_path = os.path.expanduser(libpath)
    try:
        lib = library.Library(db_path,
                              directory,
                              path_formats,
                              art_filename,
                              lib_timeout,
                              replacements)
    except sqlite3.OperationalError:
        raise UserError("database file %s could not be opened" % db_path)
    
    # Configure the logger.
    log = logging.getLogger('beets')
    if options.verbose:
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)
    log.debug(u'config file: %s' % util.displayable_path(configpath))
    log.debug(u'library database: %s' % util.displayable_path(lib.path))
    log.debug(u'library directory: %s' % util.displayable_path(lib.directory))
    
    # Invoke the subcommand.
    try:
        subcommand.func(lib, config, suboptions, subargs)
    except UserError, exc:
        message = exc.args[0] if exc.args else None
        subcommand.parser.error(message)
Пример #21
0
def main(args=None, configfh=None):
    """Run the main command-line interface for beets."""
    # Get the default subcommands.
    from lib.beets.ui.commands import default_commands

    # Get default file paths.
    default_config, default_libpath, default_dir = default_paths()

    # Read defaults from config file.
    config = ConfigParser.SafeConfigParser()
    if configfh:
        configpath = None
    elif CONFIG_PATH_VAR in os.environ:
        configpath = os.path.expanduser(os.environ[CONFIG_PATH_VAR])
    else:
        configpath = default_config
    if configpath:
        configpath = util.syspath(configpath)
        if os.path.exists(util.syspath(configpath)):
            configfh = codecs.open(configpath, 'r', encoding='utf-8')
        else:
            configfh = None
    if configfh:
        config.readfp(configfh)

    # Add plugin paths.
    plugpaths = config_val(config, 'beets', 'pluginpath', '')
    for plugpath in plugpaths.split(':'):
        sys.path.append(os.path.expanduser(plugpath))
    # Load requested plugins.
    plugnames = config_val(config, 'beets', 'plugins', '')
    plugins.load_plugins(plugnames.split())
    plugins.send("pluginload")
    plugins.configure(config)

    # Construct the root parser.
    commands = list(default_commands)
    commands += plugins.commands()
    parser = SubcommandsOptionParser(subcommands=commands)
    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('-v',
                      '--verbose',
                      dest='verbose',
                      action='store_true',
                      help='print debugging information')

    # Parse the command-line!
    options, subcommand, suboptions, subargs = parser.parse_args(args)

    # Open library file.
    libpath = options.libpath or \
        config_val(config, 'beets', 'library', default_libpath)
    directory = options.directory or \
        config_val(config, 'beets', 'directory', default_dir)
    path_formats = _get_path_formats(config)
    art_filename = \
        config_val(config, 'beets', 'art_filename', DEFAULT_ART_FILENAME)
    lib_timeout = config_val(config, 'beets', 'timeout', DEFAULT_TIMEOUT)
    replacements = _get_replacements(config)
    try:
        lib_timeout = float(lib_timeout)
    except ValueError:
        lib_timeout = DEFAULT_TIMEOUT
    db_path = os.path.expanduser(libpath)
    try:
        lib = library.Library(db_path, directory, path_formats, art_filename,
                              lib_timeout, replacements)
    except sqlite3.OperationalError:
        raise UserError("database file %s could not be opened" % db_path)
    plugins.send("library_opened", lib=lib)

    # Configure the logger.
    log = logging.getLogger('beets')
    if options.verbose:
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)
    log.debug(u'config file: %s' % util.displayable_path(configpath))
    log.debug(u'library database: %s' % util.displayable_path(lib.path))
    log.debug(u'library directory: %s' % util.displayable_path(lib.directory))

    # Invoke the subcommand.
    try:
        subcommand.func(lib, config, suboptions, subargs)
    except UserError as exc:
        message = exc.args[0] if exc.args else None
        subcommand.parser.error(message)
    except util.HumanReadableException as exc:
        exc.log(log)
        sys.exit(1)
    except IOError as exc:
        if exc.errno == errno.EPIPE:
            # "Broken pipe". End silently.
            pass
        else:
            raise
Пример #22
0
 def current_mtime(self):
     """Returns the current mtime of the file, rounded to the nearest
     integer.
     """
     return int(os.path.getmtime(syspath(self.path)))
Пример #23
0
def import_files(lib, paths, copy, write, autot, logpath, art, threaded,
                 color, delete, quiet, resume, quiet_fallback, singletons,
                 timid, query, incremental, ignore):
    """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 ASIS or SKIP and indicates what
    should happen in quiet mode when the recommendation is not strong.
    """
    # Check the user-specified directories.
    for path in paths:
        if not singletons and not os.path.isdir(syspath(path)):
            raise ui.UserError('not a directory: ' + path)
        elif singletons and not os.path.exists(syspath(path)):
            raise ui.UserError('no such file: ' + path)

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

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

    # Never ask for input in quiet mode.
    if resume is None and quiet:
        resume = False

    try:
        # Perform the import.
        importer.run_import(
            lib = lib,
            paths = paths,
            resume = resume,
            logfile = logfile,
            color = color,
            quiet = quiet,
            quiet_fallback = quiet_fallback,
            copy = copy,
            write = write,
            art = art,
            delete = delete,
            threaded = threaded,
            autot = autot,
            choose_match_func = choose_match,
            should_resume_func = should_resume,
            singletons = singletons,
            timid = timid,
            choose_item_func = choose_item,
            query = query,
            incremental = incremental,
            ignore = ignore,
            resolve_duplicate_func = resolve_duplicate,
        )
    
    finally:
        # If we were logging, close the file.
        if logfile:
            print >>logfile, ''
            logfile.close()

    # Emit event.
    plugins.send('import', lib=lib, paths=paths)
Пример #24
0
def read_tasks(config):
    """A generator yielding all the albums (as ImportTask objects) found
    in the user-specified list of paths. In the case of a singleton
    import, yields single-item tasks instead.
    """
    # Look for saved progress.
    progress = config.resume is not False
    if progress:
        resume_dirs = {}
        for path in config.paths:
            resume_dir = progress_get(path)
            if resume_dir:

                # Either accept immediately or prompt for input to decide.
                if config.resume:
                    do_resume = True
                    log.warn('Resuming interrupted import of %s' % path)
                else:
                    do_resume = config.should_resume_func(config, path)

                if do_resume:
                    resume_dirs[path] = resume_dir
                else:
                    # Clear progress; we're starting from the top.
                    progress_set(path, None)

    # Look for saved incremental directories.
    if config.incremental:
        incremental_skipped = 0
        history_dirs = history_get()

    for toppath in config.paths:
        # Check whether the path is to a file.
        if config.singletons and not os.path.isdir(syspath(toppath)):
            item = library.Item.from_path(toppath)
            yield ImportTask.item_task(item)
            continue

        # Produce paths under this directory.
        if progress:
            resume_dir = resume_dirs.get(toppath)
        for path, items in autotag.albums_in_dir(toppath, config.ignore):
            # Skip according to progress.
            if progress and resume_dir:
                # We're fast-forwarding to resume a previous tagging.
                if path == resume_dir:
                    # We've hit the last good path! Turn off the
                    # fast-forwarding.
                    resume_dir = None
                continue

            # When incremental, skip paths in the history.
            if config.incremental and path in history_dirs:
                log.debug(u'Skipping previously-imported path: %s' %
                          displayable_path(path))
                incremental_skipped += 1
                continue

            # Yield all the necessary tasks.
            if config.singletons:
                for item in items:
                    yield ImportTask.item_task(item)
                yield ImportTask.progress_sentinel(toppath, path)
            else:
                yield ImportTask(toppath, path, items)

        # Indicate the directory is finished.
        yield ImportTask.done_sentinel(toppath)

    # Show skipped directories.
    if config.incremental and incremental_skipped:
        log.info(u'Incremental import: skipped %i directories.' %
                 incremental_skipped)
Пример #25
0
def main(args=None, configfh=None):
    """Run the main command-line interface for beets."""
    # Get the default subcommands.
    from beets.ui.commands import default_commands

    # Read defaults from config file.
    config = ConfigParser.SafeConfigParser()
    if configfh:
        configpath = None
    elif CONFIG_PATH_VAR in os.environ:
        configpath = os.path.expanduser(os.environ[CONFIG_PATH_VAR])
    else:
        configpath = DEFAULT_CONFIG_FILE
    if configpath:
        configpath = util.syspath(configpath)
        if os.path.exists(util.syspath(configpath)):
            configfh = open(configpath)
        else:
            configfh = None
    if configfh:
        config.readfp(configfh)

    # Add plugin paths.
    plugpaths = config_val(config, 'beets', 'pluginpath', '')
    for plugpath in plugpaths.split(':'):
        sys.path.append(os.path.expanduser(plugpath))
    # Load requested plugins.
    plugnames = config_val(config, 'beets', 'plugins', '')
    plugins.load_plugins(plugnames.split())
    plugins.load_listeners()
    plugins.send("pluginload")
    plugins.configure(config)

    # Construct the root parser.
    commands = list(default_commands)
    commands += plugins.commands()
    parser = SubcommandsOptionParser(subcommands=commands)
    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('-v', '--verbose', dest='verbose', action='store_true',
                      help='print debugging information')
    
    # Parse the command-line!
    options, subcommand, suboptions, subargs = parser.parse_args(args)
    
    # Open library file.
    libpath = options.libpath or \
        config_val(config, 'beets', 'library', DEFAULT_LIBRARY)
    directory = options.directory or \
        config_val(config, 'beets', 'directory', DEFAULT_DIRECTORY)
    legacy_path_format = config_val(config, 'beets', 'path_format', None)
    if options.path_format:
        # If given, -p overrides all path format settings
        path_formats = {'default': options.path_format}
    else:
        if legacy_path_format:
            # Old path formats override the default values.
            path_formats = {'default': legacy_path_format}
        else:
            # If no legacy path format, use the defaults instead.
            path_formats = DEFAULT_PATH_FORMATS
        if config.has_section('paths'):
            path_formats.update(config.items('paths'))
    art_filename = \
        config_val(config, 'beets', 'art_filename', DEFAULT_ART_FILENAME)
    db_path = os.path.expanduser(libpath)
    try:
        lib = library.Library(db_path,
                              directory,
                              path_formats,
                              art_filename)
    except sqlite3.OperationalError:
        raise UserError("database file %s could not be opened" % db_path)
    
    # Configure the logger.
    log = logging.getLogger('beets')
    if options.verbose:
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)
    
    # Invoke the subcommand.
    try:
        subcommand.func(lib, config, suboptions, subargs)
    except UserError, exc:
        message = exc.args[0] if exc.args else None
        subcommand.parser.error(message)