Example #1
0
    def copy_album_art(self, album, dest_dir, path_formats, pretend=False):
        """Copies the associated cover art of the album. Album must have at
        least one track.
        """
        if not album or not album.artpath:
            return

        album_item = album.items().get()
        # Album shouldn't be empty.
        if not album_item:
            return

        # Get the destination of the first item (track) of the album, we use
        # this function to format the path accordingly to path_formats.
        dest = album_item.destination(basedir=dest_dir, path_formats=path_formats)

        # Remove item from the path.
        dest = os.path.join(*util.components(dest)[:-1])

        dest = album.art_destination(album.artpath, item_dir=dest)
        if album.artpath == dest:
            return

        if not pretend:
            util.mkdirall(dest)

        if os.path.exists(util.syspath(dest)):
            self._log.info("Skipping {0} (target file exists)", util.displayable_path(album.artpath))
            return

        if pretend:
            self._log.info("cp {0} {1}", util.displayable_path(album.artpath), util.displayable_path(dest))
        else:
            self._log.info("Copying cover art to {0}", util.displayable_path(dest))
            util.copy(album.artpath, dest)
Example #2
0
    def move_art(self, copy=False, link=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        new_art = util.unique_path(new_art)
        log.debug(u'moving album art {0} to {1}',
                  util.displayable_path(old_art),
                  util.displayable_path(new_art))
        if copy:
            util.copy(old_art, new_art)
        elif link:
            util.link(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy:
            util.prune_dirs(os.path.dirname(old_art),
                            self._db.directory)
Example #3
0
    def truncate(self, source):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)

        log.info(u'echonest: truncating {0} to {1}',
                 util.displayable_path(source),
                 util.displayable_path(dest))

        opts = []
        for arg in TRUNCATE_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: truncate failed: {0}', exc)
            util.remove(dest)
            return

        log.info(u'echonest: truncate encoding {0}',
                 util.displayable_path(source))
        return dest
Example #4
0
def write_items(lib, query, pretend):
    """Write tag information from the database to the respective files
    in the filesystem.
    """
    items, albums = _do_query(lib, query, False, False)

    for item in items:
        # Item deleted?
        if not os.path.exists(syspath(item.path)):
            log.info(u'missing file: {0}'.format(
                util.displayable_path(item.path)
            ))
            continue

        # Get an Item object reflecting the "clean" (on-disk) state.
        try:
            clean_item = library.Item.from_path(item.path)
        except Exception as exc:
            log.error(u'error reading {0}: {1}'.format(
                displayable_path(item.path), exc
            ))
            continue

        # Check for and display changes.
        changed = ui.show_model_changes(item, clean_item,
                                        library.ITEM_KEYS_META, always=True)
        if changed and not pretend:
            try:
                item.write()
            except Exception as exc:
                log.error(u'could not write {0}: {1}'.format(
                    util.displayable_path(item.path), exc
                ))
                continue
Example #5
0
def encode(source, dest):
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    opts = []
    for arg in command:
        opts.append(Template(arg).safe_substitute({
            'source': source,
            'dest':   dest,
        }))

    log.debug(u'convert: executing: {0}'.format(
        u' '.join(pipes.quote(o.decode('utf8', 'ignore')) for o in opts)
    ))
    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source))
        )
Example #6
0
    def truncate(self, item):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)
        source = item.path

        log.info(u'echonest: truncating {0} to {1}'.format(
            util.displayable_path(source),
            util.displayable_path(dest),
        ))

        command = u'ffmpeg -t 300 -i $source -y -acodec copy $dest'
        opts = []
        for arg in command.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: truncate failed: {0}'.format(exc))
            util.remove(dest)
            return

        log.info(u'echonest: truncate encoding {0}'.format(
            util.displayable_path(source))
        )
        return dest
Example #7
0
def embed_item(item, imagepath, maxwidth=None, itempath=None,
               compare_threshold=0, ifempty=False, as_album=False):
    """Embed an image into the item's media file.
    """
    if compare_threshold:
        if not check_art_similarity(item, imagepath, compare_threshold):
            log.warn(u'Image not similar; skipping.')
            return
    if ifempty:
        art = get_art(item)
        if not art:
            pass
        else:
            log.debug(u'embedart: media file contained art already {0}'.format(
                displayable_path(imagepath)
            ))
            return
    if maxwidth and not as_album:
        imagepath = resize_image(imagepath, maxwidth)

    try:
        log.debug(u'embedart: embedding {0}'.format(
            displayable_path(imagepath)
        ))
        item['images'] = [_mediafile_image(imagepath, maxwidth)]
    except IOError as exc:
        log.error(u'embedart: could not read image file: {0}'.format(exc))
    else:
        # We don't want to store the image in the database.
        item.try_write(itempath)
        del item['images']
Example #8
0
def _embed(path, items, maxwidth=0):
    """Embed an image file, located at `path`, into each item.
    """
    if maxwidth:
        path = ArtResizer.shared.resize(maxwidth, syspath(path))

    data = open(syspath(path), 'rb').read()
    kindstr = imghdr.what(None, data)
    if kindstr is None:
        log.error(u'Could not embed art of unkown type: {0}'.format(
            displayable_path(path)
        ))
        return
    elif kindstr not in ('jpeg', 'png'):
        log.error(u'Image type {0} is not allowed as cover art: {1}'.format(
            kindstr, displayable_path(path)
        ))
        return

    # Add art to each file.
    log.debug('Embedding album art.')

    for item in items:
        try:
            f = mediafile.MediaFile(syspath(item.path))
        except mediafile.UnreadableFileError as exc:
            log.warn('Could not embed art in {0}: {1}'.format(
                displayable_path(item.path), exc
            ))
            continue
        f.art = data
        f.save()
Example #9
0
def check_art_similarity(item, imagepath, compare_threshold):
    """A boolean indicating if an image is similar to embedded item art.
    """
    with NamedTemporaryFile(delete=True) as f:
        art = extract(f.name, item)

        if art:
            # Converting images to grayscale tends to minimize the weight
            # of colors in the diff score
            cmd = 'convert {0} {1} -colorspace gray MIFF:- | ' \
                  'compare -metric PHASH - null:'.format(syspath(imagepath),
                                                         syspath(art))

            proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE,
                                    close_fds=platform.system() != 'Windows',
                                    shell=True)
            stdout, stderr = proc.communicate()
            if proc.returncode:
                if proc.returncode != 1:
                    log.warn(u'embedart: IM phashes compare failed for {0}, \
                   {1}'.format(displayable_path(imagepath),
                               displayable_path(art)))
                    return
                phashDiff = float(stderr)
            else:
                phashDiff = float(stdout)

            log.info(u'embedart: compare PHASH score is {0}'.format(phashDiff))
            if phashDiff > compare_threshold:
                return False

    return True
Example #10
0
def _raw_main(args):
    """A helper function for `main` without top-level exception
    handling.
    """
    # 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

    # Add plugin paths.
    sys.path += get_plugin_paths()
    # Load requested plugins.
    plugins.load_plugins(config["plugins"].as_str_seq())
    plugins.send("pluginload")

    # Construct the root parser.
    commands = list(default_commands)
    commands += plugins.commands()
    commands.append(migrate.migrate_cmd)  # Temporary.
    parser = SubcommandsOptionParser(subcommands=commands)
    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")

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

    # 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)
Example #11
0
def extract(lib, outpath, query):
    item = lib.items(query).get()
    if not item:
        log.error('No item matches query.')
        return

    # Extract the art.
    try:
        mf = mediafile.MediaFile(syspath(item.path))
    except mediafile.UnreadableFileError as exc:
        log.error(u'Could not extract art from {0}: {1}'.format(
            displayable_path(item.path), exc
        ))
        return

    art = mf.art
    if not art:
        log.error('No album art present in %s - %s.' %
                  (item.artist, item.title))
        return

    # Add an extension to the filename.
    ext = imghdr.what(None, h=art)
    if not ext:
        log.error('Unknown image type.')
        return
    outpath += '.' + ext

    log.info(u'Extracting album art from: {0.artist} - {0.title}\n'
             u'To: {1}'.format(item, displayable_path(outpath)))
    with open(syspath(outpath), 'wb') as f:
        f.write(art)
Example #12
0
    def on_play(self, status):
        playlist = self.mpd.playlist()
        path = playlist.get(status['songid'])

        if not path:
            return

        if is_url(path):
            self._log.info(u'playing stream {0}', displayable_path(path))
            return

        played, duration = map(int, status['time'].split(':', 1))
        remaining = duration - played

        if self.now_playing and self.now_playing['path'] != path:
            skipped = self.handle_song_change(self.now_playing)
            # mpd responds twice on a natural new song start
            going_to_happen_twice = not skipped
        else:
            going_to_happen_twice = False

        if not going_to_happen_twice:
            self._log.info(u'playing {0}', displayable_path(path))

            self.now_playing = {
                'started':    time.time(),
                'remaining':  remaining,
                'path':       path,
                'beets_item': self.get_item(path),
            }

            self.update_item(self.now_playing['beets_item'],
                             'last_played', value=int(time.time()))
Example #13
0
def encode(source, dest):
    log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command = get_command()
    opts = []

    for arg in command:
        arg = arg.encode('utf-8')
        opts.append(Template(arg).substitute({
            'source':   source,
            'dest':     dest
        }))

    encode = Popen(opts, close_fds=True, stderr=DEVNULL)
    encode.wait()

    if encode.returncode != 0:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'
                 .format(util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        return

    log.info(u'Finished encoding {0}'.format(util.displayable_path(source)))
Example #14
0
def migrate_db(replace=False):
    """Copy the beets library database file to the new location (e.g.,
    from ~/.beetsmusic.blb to ~/.config/beets/library.db).
    """
    _, srcfn = default_paths()
    destfn = beets.config['library'].as_filename()

    if not os.path.exists(srcfn) or srcfn == destfn:
        # Old DB does not exist or we're configured to point to the same
        # database. Do nothing.
        return
    
    if os.path.exists(destfn):
        if replace:
            log.debug(u'moving old database aside: {0}'.format(
                util.displayable_path(destfn)
            ))
            _displace(destfn)
        else:
            return

    log.debug(u'copying database from {0} to {1}'.format(
        util.displayable_path(srcfn), util.displayable_path(destfn)
    ))
    util.copy(srcfn, destfn)
    return destfn
Example #15
0
    def check_bad(self, lib, opts, args):
        for item in lib.items(ui.decargs(args)):

            # First, check whether the path exists. If not, the user
            # should probably run `beet update` to cleanup your library.
            dpath = displayable_path(item.path)
            self._log.debug("checking path: {}", dpath)
            if not os.path.exists(item.path):
                ui.print_("{}: file does not exist".format(
                    ui.colorize('text_error', dpath)))

            # Run the checker against the file if one is found
            ext = os.path.splitext(item.path)[1][1:]
            checker = self.get_checker(ext)
            if not checker:
                continue
            path = item.path
            if not isinstance(path, unicode):
                path = item.path.decode(sys.getfilesystemencoding())
            status, errors, output = checker(path)
            if status > 0:
                ui.print_("{}: checker exited withs status {}"
                          .format(ui.colorize('text_error', dpath), status))
                for line in output:
                    ui.print_("  {}".format(displayable_path(line)))
            elif errors > 0:
                ui.print_("{}: checker found {} errors or warnings"
                          .format(ui.colorize('text_warning', dpath), errors))
                for line in output:
                    ui.print_("  {}".format(displayable_path(line)))
            else:
                ui.print_("{}: ok".format(ui.colorize('text_success', dpath)))
Example #16
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)
Example #17
0
File: convert.py Project: flz/beets
def convert_item(lib, dest_dir):
    while True:
        item = yield

        dest = os.path.join(dest_dir, lib.destination(item, fragment=True))
        dest = os.path.splitext(dest)[0] + '.mp3'

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

        # 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)

        maxbr = config['convert']['max_bitrate'].get(int)
        if item.format == 'MP3' and item.bitrate < 1000 * maxbr:
            log.info(u'Copying {0}'.format(util.displayable_path(item.path)))
            util.copy(item.path, dest)
        else:
            encode(item.path, dest)

        item.path = dest
        item.write()

        if config['convert']['embed']:
            album = lib.get_album(item)
            if album:
                artpath = album.artpath
                if artpath:
                    _embed(artpath, [item])
Example #18
0
    def convert(self, source):
        """Converts an item in an unsupported media format to ogg.  Config
        pending.
        This is stolen from Jakob Schnitzers convert plugin.
        """
        fd, dest = tempfile.mkstemp(b'.ogg')
        os.close(fd)

        self._log.info(u'encoding {0} to {1}',
                       util.displayable_path(source),
                       util.displayable_path(dest))

        opts = []
        for arg in CONVERT_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            self._log.debug(u'encode failed: {0}', exc)
            util.remove(dest)
            return

        self._log.info(u'finished encoding {0}', util.displayable_path(source))
        return dest
Example #19
0
def art_in_path(path):
    """Look for album art files in a specified directory."""
    if not os.path.isdir(path):
        return

    # Find all files that look like images in the directory.
    images = []
    for fn in os.listdir(path):
        for ext in IMAGE_EXTENSIONS:
            if fn.lower().endswith('.' + ext):
                images.append(fn)

    # Look for "preferred" filenames.
    for fn in images:
        for name in COVER_NAMES:
            if fn.lower().startswith(name):
                log.debug(u'fetchart: using well-named art file {0}'.format(
                    util.displayable_path(fn)
                ))
                return os.path.join(path, fn)

    # Fall back to any image in the folder.
    if images:
        log.debug(u'fetchart: using fallback art file {0}'.format(
            util.displayable_path(images[0])
        ))
        return os.path.join(path, images[0])
Example #20
0
def print_data(data, item=None, fmt=None):
    """Print, with optional formatting, the fields of a single element.

    If no format string `fmt` is passed, the entries on `data` are printed one
    in each line, with the format 'field: value'. If `fmt` is not `None`, the
    `item` is printed according to `fmt`, using the `Item.__format__`
    machinery.
    """
    if fmt:
        # use fmt specified by the user
        ui.print_(format(item, fmt))
        return

    path = displayable_path(item.path) if item else None
    formatted = {}
    for key, value in data.iteritems():
        if isinstance(value, list):
            formatted[key] = u'; '.join(value)
        if value is not None:
            formatted[key] = value

    if len(formatted) == 0:
        return

    maxwidth = max(len(key) for key in formatted)
    lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)

    if path:
        ui.print_(displayable_path(path))

    for field in sorted(formatted):
        value = formatted[field]
        if isinstance(value, list):
            value = u'; '.join(value)
        ui.print_(lineformat.format(field, value))
Example #21
0
def art_in_path(path, cover_names, cautious):
    """Look for album art files in a specified directory."""
    if not os.path.isdir(path):
        return

    # Find all files that look like images in the directory.
    images = []
    for fn in os.listdir(path):
        for ext in IMAGE_EXTENSIONS:
            if fn.lower().endswith('.' + ext):
                images.append(fn)

    # Look for "preferred" filenames.
    cover_pat = r"(\b|_)({0})(\b|_)".format('|'.join(cover_names))
    for fn in images:
        if re.search(cover_pat, os.path.splitext(fn)[0], re.I):
            log.debug(u'fetchart: using well-named art file {0}'.format(
                util.displayable_path(fn)
            ))
            return os.path.join(path, fn)

    # Fall back to any image in the folder.
    if images and not cautious:
        log.debug(u'fetchart: using fallback art file {0}'.format(
            util.displayable_path(images[0])
        ))
        return os.path.join(path, images[0])
Example #22
0
    def on_play(self, status):
        playlist = self.mpd.playlist()
        path = playlist.get(status["songid"])

        if not path:
            return

        if is_url(path):
            log.info(u"mpdstats: playing stream {0}".format(displayable_path(path)))
            return

        played, duration = map(int, status["time"].split(":", 1))
        remaining = duration - played

        if self.now_playing and self.now_playing["path"] != path:
            self.handle_song_change(self.now_playing)

        log.info(u"mpdstats: playing {0}".format(displayable_path(path)))

        self.now_playing = {
            "started": time.time(),
            "remaining": remaining,
            "path": path,
            "beets_item": self.get_item(path),
        }
Example #23
0
def _configure(options):
    """Amend the global configuration object with command line options.
    """
    # Add any additional config files specified with --config. This
    # special handling lets specified plugins get loaded before we
    # finish parsing the command line.
    if getattr(options, 'config', None) is not None:
        config_path = options.config
        del options.config
        config.set_file(config_path)
    config.set_args(options)

    # Configure the logger.
    if config['verbose'].get(bool):
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)

    config_path = config.user_config_path()
    if os.path.isfile(config_path):
        log.debug('user configuration: {0}'.format(
            util.displayable_path(config_path)))
    else:
        log.debug('no user configuration found at {0}'.format(
            util.displayable_path(config_path)))

    log.debug(u'data directory: {0}'
              .format(util.displayable_path(config.config_dir())))
    return config
Example #24
0
    def get(self, path, cover_names, cautious):
        """Look for album art files in a specified directory.
        """
        if not os.path.isdir(path):
            return

        # Find all files that look like images in the directory.
        images = []
        for fn in os.listdir(path):
            for ext in IMAGE_EXTENSIONS:
                if fn.lower().endswith(b'.' + ext.encode('utf8')) and \
                   os.path.isfile(os.path.join(path, fn)):
                    images.append(fn)

        # Look for "preferred" filenames.
        images = sorted(images,
                        key=lambda x: self.filename_priority(x, cover_names))
        cover_pat = br"(\b|_)({0})(\b|_)".format(b'|'.join(cover_names))
        for fn in images:
            if re.search(cover_pat, os.path.splitext(fn)[0], re.I):
                self._log.debug(u'using well-named art file {0}',
                                util.displayable_path(fn))
                return os.path.join(path, fn)

        # Fall back to any image in the folder.
        if images and not cautious:
            self._log.debug(u'using fallback art file {0}',
                            util.displayable_path(images[0]))
            return os.path.join(path, images[0])
Example #25
0
File: chroma.py Project: jck/beets
def fingerprint_item(log, item, write=False):
    """Get the fingerprint for an Item. If the item already has a
    fingerprint, it is not regenerated. If fingerprint generation fails,
    return None. If the items are associated with a library, they are
    saved to the database. If `write` is set, then the new fingerprints
    are also written to files' metadata.
    """
    # Get a fingerprint and length for this track.
    if not item.length:
        log.info(u'{0}: no duration available',
                 util.displayable_path(item.path))
    elif item.acoustid_fingerprint:
        if write:
            log.info(u'{0}: fingerprint exists, skipping',
                     util.displayable_path(item.path))
        else:
            log.info(u'{0}: using existing fingerprint',
                     util.displayable_path(item.path))
            return item.acoustid_fingerprint
    else:
        log.info(u'{0}: fingerprinting',
                 util.displayable_path(item.path))
        try:
            _, fp = acoustid.fingerprint_file(item.path)
            item.acoustid_fingerprint = fp
            if write:
                log.info(u'{0}: writing fingerprint',
                         util.displayable_path(item.path))
                item.try_write()
            if item._db:
                item.store()
            return item.acoustid_fingerprint
        except acoustid.FingerprintGenerationError as exc:
            log.info(u'fingerprint generation failed: {0}', exc)
Example #26
0
    def convert(self, item):
        """Converts an item in an unsupported media format to ogg.  Config
        pending.
        This is stolen from Jakob Schnitzers convert plugin.
        """
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)
        source = item.path

        log.info(u'echonest: encoding {0} to {1}'.format(
            util.displayable_path(source),
            util.displayable_path(dest),
        ))

        # Build up the FFmpeg command line.
        # FIXME: use avconv?
        command = u'ffmpeg -i $source -y -acodec libvorbis -vn -aq 2 $dest'
        opts = []
        for arg in command.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            subprocess.check_call(opts, close_fds=True, stderr=DEVNULL)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: encode failed: {0}'.format(exc))
            util.remove(dest)
            return

        log.info(u'echonest: finished encoding {0}'.format(
            util.displayable_path(source))
        )
        return dest
Example #27
0
def _configure(args):
    """Parse the command line, load configuration files (including
    loading any indicated plugins), and return the invoked subcomand,
    the subcommand options, and the subcommand arguments.
    """
    # 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

    # Construct the root parser.
    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')

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

    # Add any additional config files specified with --config. This
    # special handling lets specified plugins get loaded before we
    # finish parsing the command line.
    if getattr(options, 'config', None) is not None:
        config_path = options.config
        del options.config
        config.set_file(config_path)
    config.set_args(options)

    # Configure the logger.
    if config['verbose'].get(bool):
        log.setLevel(logging.DEBUG)
    else:
        log.setLevel(logging.INFO)

    config_path = config.user_config_path()
    if os.path.isfile(config_path):
        log.debug('user configuration: {0}'.format(
            util.displayable_path(config_path)))
    else:
        log.debug('no user configuration found at {0}'.format(
            util.displayable_path(config_path)))

    # Add builtin subcommands
    parser.add_subcommand(*default_commands)
    parser.add_subcommand(migrate.migrate_cmd)

    # Now add the plugin commands to the parser.
    _load_plugins()
    for cmd in plugins.commands():
        parser.add_subcommand(cmd)

    # Parse the remainder of the command line with loaded plugins.
    return parser.parse_subcommand(subargs)
Example #28
0
    def on_play(self, status):
        playlist = self.mpd.playlist()
        path = playlist.get(status['songid'])

        if not path:
            return

        if is_url(path):
            log.info(u'mpdstats: playing stream {0}'.format(
                displayable_path(path)
            ))
            return

        played, duration = map(int, status['time'].split(':', 1))
        remaining = duration - played

        if self.now_playing and self.now_playing['path'] != path:
            self.handle_song_change(self.now_playing)

        log.info(u'mpdstats: playing {0}'.format(
            displayable_path(path)
        ))

        self.now_playing = {
            'started':    time.time(),
            'remaining':  remaining,
            'path':       path,
            'beets_item': self.get_item(path),
        }
Example #29
0
 def _checksum(self, item, prog):
     """Run external `prog` on file path associated with `item`, cache
     output as flexattr on a key that is the name of the program, and
     return the key, checksum tuple.
     """
     args = [p.format(file=item.path) for p in shlex.split(prog)]
     key = args[0]
     checksum = getattr(item, key, False)
     if not checksum:
         self._log.debug(u'key {0} on item {1} not cached:'
                         u'computing checksum',
                         key, displayable_path(item.path))
         try:
             checksum = command_output(args)
             setattr(item, key, checksum)
             item.store()
             self._log.debug(u'computed checksum for {0} using {1}',
                             item.title, key)
         except subprocess.CalledProcessError as e:
             self._log.debug(u'failed to checksum {0}: {1}',
                             displayable_path(item.path), e)
     else:
         self._log.debug(u'key {0} on item {1} cached:'
                         u'not computing checksum',
                         key, displayable_path(item.path))
     return key, checksum
Example #30
0
def im_resize(maxwidth, path_in, path_out=None):
    """Resize using ImageMagick's ``convert`` tool.
    Return the output path of resized image.
    """
    path_out = path_out or temp_file_for(path_in)
    log.debug(
        u"artresizer: ImageMagick resizing {0} to {1}", util.displayable_path(path_in), util.displayable_path(path_out)
    )

    # "-resize widthxheight>" shrinks images with dimension(s) larger
    # than the corresponding width and/or height dimension(s). The >
    # "only shrink" flag is prefixed by ^ escape char for Windows
    # compatibility.
    try:
        util.command_output(
            [
                b"convert",
                util.syspath(path_in, prefix=False),
                b"-resize",
                b"{0}x^>".format(maxwidth),
                util.syspath(path_out, prefix=False),
            ]
        )
    except subprocess.CalledProcessError:
        log.warn(u"artresizer: IM convert failed for {0}", util.displayable_path(path_in))
        return path_in
    return path_out
Example #31
0
    def truncate(self, source):
        """Truncates an item to a size less than UPLOAD_MAX_SIZE."""
        fd, dest = tempfile.mkstemp(u'.ogg')
        os.close(fd)

        log.info(u'echonest: truncating {0} to {1}',
                 util.displayable_path(source), util.displayable_path(dest))

        opts = []
        for arg in TRUNCATE_COMMAND.split():
            arg = arg.encode('utf-8')
            opts.append(Template(arg).substitute(source=source, dest=dest))

        # Run the command.
        try:
            util.command_output(opts)
        except (OSError, subprocess.CalledProcessError) as exc:
            log.debug(u'echonest: truncate failed: {0}', exc)
            util.remove(dest)
            return

        log.info(u'echonest: truncate encoding {0}',
                 util.displayable_path(source))
        return dest
Example #32
0
        def _convert(item):
            dest = self.destination(item)
            with fs_lock:
                util.mkdirall(dest)

            if self.should_transcode(item):
                self._encode(self.convert_cmd, item.path, dest)
                # Don't rely on the converter to write correct/complete tags.
                item.write(path=dest)
            else:
                self._log.debug(u'copying {0}'.format(displayable_path(dest)))
                util.copy(item.path, dest, replace=True)
            if self._embed:
                self.sync_art(item, dest)
            return item, dest
Example #33
0
    def _group_by(self, objs, keys, strict):
        """Return a dictionary with keys arbitrary concatenations of attributes
        and values lists of objects (Albums or Items) with those keys.

        If strict, all attributes must be defined for a duplicate match.
        """
        import collections
        counts = collections.defaultdict(list)
        for obj in objs:
            values = [getattr(obj, k, None) for k in keys]
            values = [v for v in values if v not in (None, '')]
            if strict and len(values) < len(keys):
                self._log.debug(u'some keys {0} on item {1} are null or empty:'
                                u' skipping',
                                keys, displayable_path(obj.path))
            elif (not strict and not len(values)):
                self._log.debug(u'all keys {0} on item {1} are null or empty:'
                                u' skipping',
                                keys, displayable_path(obj.path))
            else:
                key = tuple(values)
                counts[key].append(obj)

        return counts
Example #34
0
    def move_art(self, copy=False):
        """Move or copy any existing album art so that it remains in the
        same directory as the items.
        """
        old_art = self.artpath
        if not old_art:
            return

        new_art = self.art_destination(old_art)
        if new_art == old_art:
            return

        new_art = util.unique_path(new_art)
        log.debug(u'moving album art {0} to {1}'.format(
            util.displayable_path(old_art), util.displayable_path(new_art)))
        if copy:
            util.copy(old_art, new_art)
        else:
            util.move(old_art, new_art)
        self.artpath = new_art

        # Prune old path when moving.
        if not copy:
            util.prune_dirs(os.path.dirname(old_art), self._db.directory)
Example #35
0
def _checksum(item, prog):
    """Run external `prog` on file path associated with `item`, cache
    output as flexattr on a key that is the name of the program, and
    return the key, checksum tuple.
    """
    args = shlex.split(prog.format(file=item.path))
    key = args[0]
    checksum = getattr(item, key, False)
    if not checksum:
        log.debug('%s: key %s on item %s not cached: computing checksum',
                  PLUGIN, key, displayable_path(item.path))
        try:
            checksum = command_output(args)
            setattr(item, key, checksum)
            item.store()
            log.debug('%s: computed checksum for %s using %s', PLUGIN,
                      item.title, key)
        except Exception as e:
            log.debug('%s: failed to checksum %s: %s', PLUGIN,
                      displayable_path(item.path), e)
    else:
        log.debug('%s: key %s on item %s cached: not computing checksum',
                  PLUGIN, key, displayable_path(item.path))
    return key, checksum
Example #36
0
def info(paths):
    # Set up fields to output.
    fields = []
    for name, _, _, mffield in library.ITEM_FIELDS:
        if mffield:
            fields.append(name)

    # Line format.
    other_fields = ['album art']
    maxwidth = max(len(name) for name in fields + other_fields)
    lineformat = u'{{0:>{0}}}: {{1}}'.format(maxwidth)

    first = True
    for path in paths:
        if not first:
            ui.print_()

        path = util.normpath(path)
        if not os.path.isfile(path):
            ui.print_(u'not a file: {0}'.format(util.displayable_path(path)))
            continue
        ui.print_(path)
        try:
            mf = mediafile.MediaFile(path)
        except mediafile.UnreadableFileError:
            ui.print_('cannot read file: {0}'.format(
                util.displayable_path(path)))
            continue

        # Basic fields.
        for name in fields:
            ui.print_(lineformat.format(name, getattr(mf, name)))
        # Extra stuff.
        ui.print_(lineformat.format('album art', mf.art is not None))

        first = False
Example #37
0
def _checksum(item, prog, log):
    """Run external `prog` on file path associated with `item`, cache
    output as flexattr on a key that is the name of the program, and
    return the key, checksum tuple.
    """
    args = [p.format(file=item.path) for p in shlex.split(prog)]
    key = args[0]
    checksum = getattr(item, key, False)
    if not checksum:
        log.debug(u'{0}: key {1} on item {2} not cached: computing checksum',
                  PLUGIN, key, displayable_path(item.path))
        try:
            checksum = command_output(args)
            setattr(item, key, checksum)
            item.store()
            log.debug(u'{0}: computed checksum for {1} using {2}',
                      PLUGIN, item.title, key)
        except subprocess.CalledProcessError as e:
            log.debug(u'{0}: failed to checksum {1}: {2}',
                      PLUGIN, displayable_path(item.path), e)
    else:
        log.debug(u'{0}: key {1} on item {2} cached: not computing checksum',
                  PLUGIN, key, displayable_path(item.path))
    return key, checksum
Example #38
0
def encode(source, dest):
    """Encode ``source`` to ``dest`` using the command from ``get_format()``.

    Raises an ``ui.UserError`` if the command was not found and a
    ``subprocess.CalledProcessError`` if the command exited with a
    non-zero status code.
    """
    quiet = config['convert']['quiet'].get()

    if not quiet:
        log.info(u'Started encoding {0}'.format(util.displayable_path(source)))

    command, _ = get_format()
    command = Template(command).safe_substitute({
        'source': pipes.quote(source),
        'dest': pipes.quote(dest),
    })

    log.debug(u'convert: executing: {0}'.format(
        util.displayable_path(command)))

    try:
        util.command_output(command, shell=True)
    except subprocess.CalledProcessError:
        # Something went wrong (probably Ctrl+C), remove temporary files
        log.info(u'Encoding {0} failed. Cleaning up...'.format(
            util.displayable_path(source)))
        util.remove(dest)
        util.prune_dirs(os.path.dirname(dest))
        raise
    except OSError as exc:
        raise ui.UserError(u'convert: could invoke ffmpeg: {0}'.format(exc))

    if not quiet:
        log.info(u'Finished encoding {0}'.format(
            util.displayable_path(source)))
Example #39
0
def migrate_db(replace=False):
    """Copy the beets library database file to the new location (e.g.,
    from ~/.beetsmusic.blb to ~/.config/beets/library.db).
    """
    _, srcfn = default_paths()
    destfn = beets.config['library'].as_filename()

    if not os.path.exists(srcfn) or srcfn == destfn:
        # Old DB does not exist or we're configured to point to the same
        # database. Do nothing.
        return

    if os.path.exists(destfn):
        if replace:
            log.debug(u'moving old database aside: {0}'.format(
                util.displayable_path(destfn)))
            _displace(destfn)
        else:
            return

    log.debug(u'copying database from {0} to {1}'.format(
        util.displayable_path(srcfn), util.displayable_path(destfn)))
    util.copy(srcfn, destfn)
    return destfn
Example #40
0
 def run_command(self, cmd):
     self._log.debug(u"running command: {}",
                     displayable_path(list2cmdline(cmd)))
     try:
         output = check_output(cmd, stderr=STDOUT)
         errors = 0
         status = 0
     except CalledProcessError as e:
         output = e.output
         errors = 1
         status = e.returncode
     except OSError as e:
         raise CheckerCommandException(cmd, e)
     output = output.decode(sys.getdefaultencoding(), 'replace')
     return status, errors, [line for line in output.split("\n") if line]
Example #41
0
def im_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0):
    """Resize using ImageMagick.

    Use the ``magick`` program or ``convert`` on older versions. Return
    the output path of resized image.
    """
    path_out = path_out or temp_file_for(path_in)
    log.debug(u'artresizer: ImageMagick resizing {0} to {1}',
              util.displayable_path(path_in), util.displayable_path(path_out))

    # "-resize WIDTHx>" shrinks images with the width larger
    # than the given width while maintaining the aspect ratio
    # with regards to the height.
    cmd = ArtResizer.shared.im_convert_cmd + [
        util.syspath(path_in, prefix=False),
        '-resize', '{0}x>'.format(maxwidth),
    ]

    if quality > 0:
        cmd += ['-quality', '{0}'.format(quality)]

    # "-define jpeg:extent=SIZEb" sets the target filesize for imagemagick to
    # SIZE in bytes.
    if max_filesize > 0:
        cmd += ['-define', 'jpeg:extent={0}b'.format(max_filesize)]

    cmd.append(util.syspath(path_out, prefix=False))

    try:
        util.command_output(cmd)
    except subprocess.CalledProcessError:
        log.warning(u'artresizer: IM convert failed for {0}',
                    util.displayable_path(path_in))
        return path_in

    return path_out
Example #42
0
def assert_permissions(path, permission, log):
    """Check whether the file's permissions are as expected, otherwise,
    log a warning message. Return a boolean indicating the match, like
    `check_permissions`.
    """
    if not check_permissions(util.syspath(path), permission):
        log.warning(
            u'could not set permissions on {}',
            util.displayable_path(path),
        )
        log.debug(
            u'set permissions to {}, but permissions are now {}',
            permission,
            os.stat(util.syspath(path)).st_mode & 0o777,
        )
Example #43
0
    def clear(self, lib, query):
        id3v23 = config['id3v23'].get(bool)

        items = lib.items(query)
        self._log.info(u'Clearing album art from {0} items', len(items))
        for item in items:
            self._log.debug(u'Clearing art for {0}', item)
            try:
                mf = mediafile.MediaFile(syspath(item.path), id3v23)
            except mediafile.UnreadableFileError as exc:
                self._log.warning(u'Could not read file {0}: {1}',
                                  displayable_path(item.path), exc)
            else:
                del mf.art
                mf.save()
Example #44
0
def _load_plugins(config):
    """Load the plugins specified in the configuration.
    """
    paths = config['pluginpath'].get(confit.StrSeq(split=False))
    paths = map(util.normpath, paths)
    log.debug('plugin paths: {0}', util.displayable_path(paths))

    import beetsplug
    beetsplug.__path__ = paths + beetsplug.__path__
    # For backwards compatibility.
    sys.path += paths

    plugins.load_plugins(config['plugins'].as_str_seq())
    plugins.send("pluginload")
    return plugins
Example #45
0
def read_items(paths):
    """Return a list of items created from each path.

    If an item could not be read it skips the item and logs an error.
    """
    # TODO remove this method. Should be handled in ImportTask creation.
    items = []
    for path in paths:
        try:
            items.append(library.Item.from_path(path))
        except library.ReadError as exc:
            if isinstance(exc.reason, mediafile.FileTypeError):
                # Silently ignore non-music files.
                pass
            elif isinstance(exc.reason, mediafile.UnreadableFileError):
                log.warn(u'unreadable file: {0}'.format(
                    displayable_path(path))
                )
            else:
                log.error(u'error reading {0}: {1}'.format(
                    displayable_path(path),
                    exc,
                ))
    return items
Example #46
0
def im_resize(maxwidth, path_in, path_out=None):
    """Resize using ImageMagick's ``convert`` tool.
    Return the output path of resized image.
    """
    path_out = path_out or temp_file_for(path_in)
    log.debug(u'artresizer: ImageMagick resizing {0} to {1}',
              util.displayable_path(path_in), util.displayable_path(path_out))

    # "-resize WIDTHx>" shrinks images with the width larger
    # than the given width while maintaining the aspect ratio
    # with regards to the height.
    try:
        util.command_output([
            'convert',
            util.syspath(path_in, prefix=False),
            '-resize',
            '{0}x>'.format(maxwidth),
            util.syspath(path_out, prefix=False),
        ])
    except subprocess.CalledProcessError:
        log.warning(u'artresizer: IM convert failed for {0}',
                    util.displayable_path(path_in))
        return path_in
    return path_out
Example #47
0
    def choose_match(self, task):
        """Given an initial autotagging of items, go through an interactive
        dance with the user to ask for a choice of metadata. Returns an
        AlbumMatch object, ASIS, or SKIP.
        """
        # Show what we're tagging.
        print_()
        print_(displayable_path(task.paths, u'\n'))

        # Take immediate action if appropriate.
        action = _summary_judment(task.rec)
        if action == importer.action.APPLY:
            match = task.candidates[0]
            show_change(task.cur_artist, task.cur_album, match)
            return match
        elif action is not None:
            return action

        # Loop until we have a choice.
        candidates, rec = task.candidates, task.rec
        while True:
            # Ask for a choice from the user.
            choice = choose_candidate(candidates, False, rec, task.cur_artist,
                                    task.cur_album, itemcount=len(task.items))

            # Choose which tags to use.
            if choice in (importer.action.SKIP, importer.action.ASIS,
                        importer.action.TRACKS):
                # Pass selection to main control flow.
                return choice
            elif choice is importer.action.MANUAL:
                # Try again with manual search terms.
                search_artist, search_album = manual_search(False)
                _, _, candidates, rec = autotag.tag_album(
                    task.items, search_artist, search_album
                )
            elif choice is importer.action.MANUAL_ID:
                # Try a manually-entered ID.
                search_id = manual_id(False)
                if search_id:
                    _, _, candidates, rec = autotag.tag_album(
                        task.items, search_id=search_id
                    )
            else:
                # We have a candidate! Finish tagging. Here, choice is an
                # AlbumMatch object.
                assert isinstance(choice, autotag.AlbumMatch)
                return choice
Example #48
0
    def check_item(self, item):
        # First, check whether the path exists. If not, the user
        # should probably run `beet update` to cleanup your library.
        dpath = displayable_path(item.path)
        self._log.debug(u"checking path: {}", dpath)
        if not os.path.exists(item.path):
            ui.print_(u"{}: file does not exist".format(
                ui.colorize('text_error', dpath)))

        # Run the checker against the file if one is found
        ext = os.path.splitext(item.path)[1][1:].decode('utf8', 'ignore')
        checker = self.get_checker(ext)
        if not checker:
            self._log.error(u"no checker specified in the config for {}", ext)
            return []
        path = item.path
        if not isinstance(path, six.text_type):
            path = item.path.decode(sys.getfilesystemencoding())
        try:
            status, errors, output = checker(path)
        except CheckerCommandException as e:
            if e.errno == errno.ENOENT:
                self._log.error(
                    u"command not found: {} when validating file: {}",
                    e.checker, e.path)
            else:
                self._log.error(u"error invoking {}: {}", e.checker, e.msg)
            return []

        error_lines = []

        if status > 0:
            error_lines.append(u"{}: checker exited with status {}".format(
                ui.colorize('text_error', dpath), status))
            for line in output:
                error_lines.append(u"  {}".format(line))

        elif errors > 0:
            error_lines.append(
                u"{}: checker found {} errors or warnings".format(
                    ui.colorize('text_warning', dpath), errors))
            for line in output:
                error_lines.append(u"  {}".format(line))
        elif self.verbose:
            error_lines.append(u"{}: ok".format(
                ui.colorize('text_success', dpath)))

        return error_lines
Example #49
0
def show_progress(session):
    """This stage replaces the initial_lookup and user_query stages
    when the importer is run without autotagging. It displays the album
    name and artist as the files are added.
    """
    task = None
    while True:
        task = yield task
        if task.sentinel:
            continue

        log.info(displayable_path(task.paths))

        # Behave as if ASIS were selected.
        task.set_null_candidates()
        task.set_choice(action.ASIS)
Example #50
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))
Example #51
0
    def remove_replaced(self, lib):
        """Removes all the items from the library that have the same
        path as an item from this task.

        Records the replaced items in the `replaced_items` dictionary
        """
        self.replaced_items = defaultdict(list)
        for item in self.imported_items():
            dup_items = lib.items(dbcore.query.BytesQuery('path', item.path))
            self.replaced_items[item] = dup_items
            for dup_item in dup_items:
                log.debug('replacing item %i: %s' %
                          (dup_item.id, displayable_path(item.path)))
                dup_item.remove()
        log.debug('%i of %i items replaced' % (len(self.replaced_items),
                                               len(self.imported_items())))
Example #52
0
    def ask_resume(self, toppath):
        """If import of `toppath` was aborted in an earlier session, ask
        user if she wants to resume the import.

        Determines the return value of `is_resuming(toppath)`.
        """
        if self.want_resume and has_progress(toppath):
            # Either accept immediately or prompt for input to decide.
            if self.want_resume is True or \
               self.should_resume(toppath):
                log.warn(u'Resuming interrupted import of {0}'.format(
                    util.displayable_path(toppath)))
                self._is_resuming[toppath] = True
            else:
                # Clear progress; we're starting from the top.
                progress_reset(toppath)
Example #53
0
        def scrub_func(lib, opts, args):
            # This is a little bit hacky, but we set a global flag to
            # avoid autoscrubbing when we're also explicitly scrubbing.
            global scrubbing
            scrubbing = True

            # Walk through matching files and remove tags.
            for item in lib.items(ui.decargs(args)):
                log.info(u'scrubbing: %s' % util.displayable_path(item.path))
                _scrub(item.path)

                if opts.write:
                    log.debug(u'writing new tags after scrub')
                    item.write()

            scrubbing = False
Example #54
0
def set_titles_no_junk(task, session):
    items = task.items if task.is_album else [task.item]

    for item in items:
        if item.title:
            continue
        item_file_path = Path(displayable_path(item.path))
        youtube_title = frompath.get_title(item_file_path)
        album_name = frompath.get_album_name(item_file_path)
        artist_name = frompath.get_artist_name(item_file_path)
        artist_album_junk = [
            '(?i)(?P<junk>\\({0}\\))'.format(re.escape(album_name)),
            '(?i)(?P<junk>\\(?{0}\\)?)'.format(re.escape(artist_name))
        ]
        item.title = remove_junk(youtube_title, artist_album_junk,
                                 YOUTUBE_TITLE_JUNK)
Example #55
0
    def get_extractor_data(self, item):
        with tempfile.NamedTemporaryFile() as tmpfile:
            tmpfile.close()

            args = [self.extractor, util.syspath(item.path), tmpfile.name]
            try:
                util.command_output(args)
            except subprocess.CalledProcessError as exc:
                self._log.warning(u'{} "{}" "{}" exited with status {}',
                                  self.extractor,
                                  util.displayable_path(item.path),
                                  tmpfile.name, exc.returncode)
                return

            with open(tmpfile.name, 'rb') as tmp_file:
                return json.load(tmp_file)
Example #56
0
def _group_by(objs, keys, log):
    """Return a dictionary with keys arbitrary concatenations of attributes and
    values lists of objects (Albums or Items) with those keys.
    """
    import collections
    counts = collections.defaultdict(list)
    for obj in objs:
        values = [getattr(obj, k, None) for k in keys]
        values = [v for v in values if v not in (None, '')]
        if values:
            key = '\001'.join(values)
            counts[key].append(obj)
        else:
            log.debug(u'{0}: all keys {1} on item {2} are null: skipping',
                      PLUGIN, keys, displayable_path(obj.path))

    return counts
Example #57
0
    def find_key(self, items, write=False):
        overwrite = self.config['overwrite'].get(bool)
        command = [self.config['bin'].as_str()]
        # The KeyFinder GUI program needs the -f flag before the path.
        # keyfinder-cli is similar, but just wants the path with no flag.
        if 'keyfinder-cli' not in os.path.basename(command[0]).lower():
            command.append('-f')

        for item in items:
            if item['initial_key'] and not overwrite:
                continue

            try:
                output = util.command_output(command +
                                             [util.syspath(item.path)]).stdout
            except (subprocess.CalledProcessError, OSError) as exc:
                self._log.error('execution failed: {0}', exc)
                continue
            except UnicodeEncodeError:
                # Workaround for Python 2 Windows bug.
                # https://bugs.python.org/issue1759845
                self._log.error('execution failed for Unicode path: {0!r}',
                                item.path)
                continue

            try:
                key_raw = output.rsplit(None, 1)[-1]
            except IndexError:
                # Sometimes keyfinder-cli returns 0 but with no key, usually
                # when the file is silent or corrupt, so we log and skip.
                self._log.error('no key returned for path: {0}', item.path)
                continue

            try:
                key = util.text_string(key_raw)
            except UnicodeDecodeError:
                self._log.error('output is invalid UTF-8')
                continue

            item['initial_key'] = key
            self._log.info('added computed initial key {0} for {1}', key,
                           util.displayable_path(item.path))

            if write:
                item.try_write()
            item.store()
Example #58
0
 def test_import_global(self):
     config['filefilter']['path'] = '.*track_1.*\.mp3'
     self.__run([
         'Album: %s' % displayable_path(self.artist_path),
         '  %s' % displayable_path(self.artist_paths[0]),
         'Album: %s' % displayable_path(self.misc_path),
         '  %s' % displayable_path(self.misc_paths[0]),
     ])
     self.__run([
         'Singleton: %s' % displayable_path(self.artist_paths[0]),
         'Singleton: %s' % displayable_path(self.misc_paths[0])
     ], singletons=True)
Example #59
0
    def run(self, lib, opts, args):
        file_path = opts.output
        file_mode = 'a' if opts.append else 'w'
        file_format = opts.format or self.config['default_format'].get(str)
        file_format_is_line_based = (file_format == 'jsonlines')
        format_options = self.config[file_format]['formatting'].get(dict)

        export_format = ExportFormat.factory(file_type=file_format,
                                             **{
                                                 'file_path': file_path,
                                                 'file_mode': file_mode
                                             })

        if opts.library or opts.album:
            data_collector = library_data
        else:
            data_collector = tag_data

        included_keys = []
        for keys in opts.included_keys:
            included_keys.extend(keys.split(','))

        items = []
        for data_emitter in data_collector(
                lib,
                ui.decargs(args),
                album=opts.album,
        ):
            try:
                data, item = data_emitter(included_keys or '*')
            except (mediafile.UnreadableFileError, OSError) as ex:
                self._log.error('cannot read file: {0}', ex)
                continue

            for key, value in data.items():
                if isinstance(value, bytes):
                    data[key] = util.displayable_path(value)

            if file_format_is_line_based:
                export_format.export(data, **format_options)
            else:
                items += [data]

        if not file_format_is_line_based:
            export_format.export(items, **format_options)
Example #60
0
    def update_item(self, item, attribute, value=None, increment=None):
        """Update the beets item. Set attribute to value or increment the value
        of attribute. If the increment argument is used the value is cast to
        the corresponding type.
        """
        if item is None:
            return

        if increment is not None:
            item.load()
            value = type(increment)(item.get(attribute, 0)) + increment

        if value is not None:
            item[attribute] = value
            item.store()

            self._log.debug(u'updated: {0} = {1} [{2}]', attribute,
                            item[attribute], displayable_path(item.path))