Example #1
0
 def __init__(self, path='library.blb',
                    directory='~/Music',
                    path_formats=None,
                    art_filename='cover',
                    timeout=5.0,
                    item_fields=ITEM_FIELDS,
                    album_fields=ALBUM_FIELDS):
     if path == ':memory:':
         self.path = path
     else:
         self.path = bytestring_path(normpath(path))
     self.directory = bytestring_path(normpath(directory))
     if path_formats is None:
         path_formats = {'default': '$artist/$album/$track $title'}
     elif isinstance(path_formats, basestring):
         path_formats = {'default': path_formats}
     self.path_formats = path_formats
     self.art_filename = bytestring_path(art_filename)
     
     self.timeout = timeout
     self.conn = sqlite3.connect(self.path, timeout)
     self.conn.row_factory = sqlite3.Row
         # this way we can access our SELECT results like dictionaries
     
     self._make_table('items', item_fields)
     self._make_table('albums', album_fields)
Example #2
0
 def __init__(self, path='library.blb',
                    directory='~/Music',
                    path_formats=((PF_KEY_DEFAULT,
                                   '$artist/$album/$track $title'),),
                    art_filename='cover',
                    timeout=5.0,
                    replacements=None,
                    item_fields=ITEM_FIELDS,
                    album_fields=ALBUM_FIELDS):
     if path == ':memory:':
         self.path = path
     else:
         self.path = bytestring_path(normpath(path))
     self.directory = bytestring_path(normpath(directory))
     self.path_formats = path_formats
     self.art_filename = bytestring_path(art_filename)
     self.replacements = replacements
     
     self.timeout = timeout
     self.conn = sqlite3.connect(self.path, timeout)
     self.conn.row_factory = sqlite3.Row
         # this way we can access our SELECT results like dictionaries
     
     self._make_table('items', item_fields)
     self._make_table('albums', album_fields)
Example #3
0
def _record_items(lib, basename, items):
    """Records relative paths to the given items for each feed format
    """
    feedsdir = bytestring_path(config['importfeeds']['dir'].as_filename())
    formats = config['importfeeds']['formats'].as_str_seq()
    relative_to = config['importfeeds']['relative_to'].get() \
            or config['importfeeds']['dir'].as_filename()
    relative_to = bytestring_path(relative_to)

    paths = []
    for item in items:
        paths.append(os.path.relpath(
            item.path, relative_to
        ))

    if 'm3u' in formats:
        basename = bytestring_path(
            config['importfeeds']['m3u_name'].get(unicode)
        )
        m3u_path = os.path.join(feedsdir, basename)
        _write_m3u(m3u_path, paths)

    if 'm3u_multi' in formats:
        m3u_path = _build_m3u_filename(basename)
        _write_m3u(m3u_path, paths)

    if 'link' in formats:
        for path in paths:
            dest = os.path.join(feedsdir, os.path.basename(path))
            if not os.path.exists(syspath(dest)):
                os.symlink(syspath(path), syspath(dest))
Example #4
0
    def convert_on_import(self, lib, item):
        """Transcode a file automatically after it is imported into the
        library.
        """
        fmt = self.config['format'].as_str().lower()
        if should_transcode(item, fmt):
            command, ext = get_format()

            # Create a temporary file for the conversion.
            tmpdir = self.config['tmpdir'].get()
            if tmpdir:
                tmpdir = util.py3_path(util.bytestring_path(tmpdir))
            fd, dest = tempfile.mkstemp(util.py3_path(b'.' + ext), dir=tmpdir)
            os.close(fd)
            dest = util.bytestring_path(dest)
            _temp_files.append(dest)  # Delete the transcode later.

            # Convert.
            try:
                self.encode(command, item.path, dest)
            except subprocess.CalledProcessError:
                return

            # Change the newly-imported database entry to point to the
            # converted file.
            item.path = dest
            item.write()
            item.read()  # Load new audio information data.
            item.store()
Example #5
0
    def create_mediafile_fixture(self, ext='mp3', images=[]):
        """Copies a fixture mediafile with the extension to a temporary
        location and returns the path.

        It keeps track of the created locations and will delete the with
        `remove_mediafile_fixtures()`

        `images` is a subset of 'png', 'jpg', and 'tiff'. For each
        specified extension a cover art image is added to the media
        file.
        """
        src = os.path.join(_common.RSRC, util.bytestring_path('full.' + ext))
        handle, path = mkstemp()
        os.close(handle)
        shutil.copyfile(src, path)

        if images:
            mediafile = MediaFile(path)
            imgs = []
            for img_ext in images:
                file = util.bytestring_path('image-2x3.{0}'.format(img_ext))
                img_path = os.path.join(_common.RSRC, file)
                with open(img_path, 'rb') as f:
                    imgs.append(Image(f.read()))
            mediafile.images = imgs
            mediafile.save()

        if not hasattr(self, '_mediafile_fixtures'):
            self._mediafile_fixtures = []
        self._mediafile_fixtures.append(path)

        return path
Example #6
0
    def __init__(self, field, pattern, fast=True):
        super(PathQuery, self).__init__(field, pattern, fast)

        # Match the path as a single file.
        self.file_path = util.bytestring_path(util.normpath(pattern))
        # As a directory (prefix).
        self.dir_path = util.bytestring_path(os.path.join(self.file_path, ''))
Example #7
0
def permissions(lib, item=None, album=None):
    """Running the permission fixer.
    """
    # Getting the config.
    file_perm = config['permissions']['file'].get()

    # Converts file permissions to oct.
    file_perm = convert_perm(file_perm)

    # Create chmod_queue.
    chmod_queue = []
    if item:
        chmod_queue.append(item.path)
    elif album:
        for album_item in album.items():
            chmod_queue.append(album_item.path)

    # Setting permissions for every path in the queue.
    for path in chmod_queue:
        # Changing permissions on the destination path.
        os.chmod(util.bytestring_path(path), file_perm)

        # Checks if the destination path has the permissions configured.
        if not check_permissions(util.bytestring_path(path), file_perm):
            message = 'There was a problem setting permission on {}'.format(
                path)
            print(message)
Example #8
0
 def _make_test(self, ext='mp3', id3v23=False):
     self.create_temp_dir()
     src = os.path.join(_common.RSRC,
                        bytestring_path('full.{0}'.format(ext)))
     self.path = os.path.join(self.temp_dir,
                              bytestring_path('test.{0}'.format(ext)))
     shutil.copy(src, self.path)
     return beets.mediafile.MediaFile(self.path, id3v23=id3v23)
Example #9
0
def url_to_filename(url):
    url = re.sub(r'https?://|www.', '', url)
    fn = "".join(x for x in url if (x.isalnum() or x == '/'))
    fn = fn.split('/')
    fn = os.path.join(LYRICS_ROOT_DIR,
                      bytestring_path(fn[0]),
                      bytestring_path(fn[-1] + '.txt'))
    return fn
Example #10
0
        def _dup(lib, opts, args):
            self.config.set_args(opts)
            album = self.config['album'].get(bool)
            checksum = self.config['checksum'].get(str)
            copy = bytestring_path(self.config['copy'].as_str())
            count = self.config['count'].get(bool)
            delete = self.config['delete'].get(bool)
            fmt = self.config['format'].get(str)
            full = self.config['full'].get(bool)
            keys = self.config['keys'].as_str_seq()
            merge = self.config['merge'].get(bool)
            move = bytestring_path(self.config['move'].as_str())
            path = self.config['path'].get(bool)
            tiebreak = self.config['tiebreak'].get(dict)
            strict = self.config['strict'].get(bool)
            tag = self.config['tag'].get(str)

            if album:
                if not keys:
                    keys = ['mb_albumid']
                items = lib.albums(decargs(args))
            else:
                if not keys:
                    keys = ['mb_trackid', 'mb_albumid']
                items = lib.items(decargs(args))

            if path:
                fmt = u'$path'

            # Default format string for count mode.
            if count and not fmt:
                if album:
                    fmt = u'$albumartist - $album'
                else:
                    fmt = u'$albumartist - $album - $title'
                fmt += u': {0}'

            if checksum:
                for i in items:
                    k, _ = self._checksum(i, checksum)
                keys = [k]

            for obj_id, obj_count, objs in self._duplicates(items,
                                                            keys=keys,
                                                            full=full,
                                                            strict=strict,
                                                            tiebreak=tiebreak,
                                                            merge=merge):
                if obj_id:  # Skip empty IDs.
                    for o in objs:
                        self._process_item(o,
                                           copy=copy,
                                           move=move,
                                           delete=delete,
                                           tag=tag,
                                           fmt=fmt.format(obj_count))
Example #11
0
    def test_default_config_paths_resolve_relative_to_beetsdir(self):
        os.environ['BEETSDIR'] = util.py3_path(self.beetsdir)

        config.read()
        self.assert_equal_path(
            util.bytestring_path(config['library'].as_filename()),
            os.path.join(self.beetsdir, b'library.db'))
        self.assert_equal_path(
            util.bytestring_path(config['statefile'].as_filename()),
            os.path.join(self.beetsdir, b'state.pickle'))
Example #12
0
    def test_cli_config_paths_resolve_relative_to_user_dir(self):
        cli_config_path = os.path.join(self.temp_dir, b'config.yaml')
        with open(cli_config_path, 'w') as file:
            file.write('library: beets.db\n')
            file.write('statefile: state')

        self.run_command('--config', cli_config_path, 'test', lib=None)
        self.assert_equal_path(
            util.bytestring_path(config['library'].as_filename()),
            os.path.join(self.user_config_dir, b'beets.db'))
        self.assert_equal_path(
            util.bytestring_path(config['statefile'].as_filename()),
            os.path.join(self.user_config_dir, b'state'))
Example #13
0
    def __init__(self, path='library.blb',
                       directory='~/Music',
                       path_formats=((PF_KEY_DEFAULT,
                                      '$artist/$album/$track $title'),),
                       replacements=None):
        if path != ':memory:':
            self.path = bytestring_path(normpath(path))
        super(Library, self).__init__(path)

        self.directory = bytestring_path(normpath(directory))
        self.path_formats = path_formats
        self.replacements = replacements

        self._memotable = {}  # Used for template substitution performance.
Example #14
0
    def create_importer(self, item_count=1, album_count=1):
        """Create files to import and return corresponding session.

        Copies the specified number of files to a subdirectory of
        `self.temp_dir` and creates a `TestImportSession` for this path.
        """
        import_dir = os.path.join(self.temp_dir, b'import')
        if not os.path.isdir(import_dir):
            os.mkdir(import_dir)

        album_no = 0
        while album_count:
            album = util.bytestring_path(u'album {0}'.format(album_no))
            album_dir = os.path.join(import_dir, album)
            if os.path.exists(album_dir):
                album_no += 1
                continue
            os.mkdir(album_dir)
            album_count -= 1

            track_no = 0
            album_item_count = item_count
            while album_item_count:
                title = u'track {0}'.format(track_no)
                src = os.path.join(_common.RSRC, b'full.mp3')
                title_file = util.bytestring_path('{0}.mp3'.format(title))
                dest = os.path.join(album_dir, title_file)
                if os.path.exists(dest):
                    track_no += 1
                    continue
                album_item_count -= 1
                shutil.copy(src, dest)
                mediafile = MediaFile(dest)
                mediafile.update({
                    'artist': 'artist',
                    'albumartist': 'album artist',
                    'title': title,
                    'album': album,
                    'mb_albumid': None,
                    'mb_trackid': None,
                })
                mediafile.save()

        config['import']['quiet'] = True
        config['import']['autotag'] = False
        config['import']['resume'] = False

        return TestImportSession(self.lib, loghandler=None, query=None,
                                 paths=[import_dir])
Example #15
0
    def test_beetsdir_config_paths_resolve_relative_to_beetsdir(self):
        os.environ['BEETSDIR'] = util.py3_path(self.beetsdir)

        env_config_path = os.path.join(self.beetsdir, b'config.yaml')
        with open(env_config_path, 'w') as file:
            file.write('library: beets.db\n')
            file.write('statefile: state')

        config.read()
        self.assert_equal_path(
            util.bytestring_path(config['library'].as_filename()),
            os.path.join(self.beetsdir, b'beets.db'))
        self.assert_equal_path(
            util.bytestring_path(config['statefile'].as_filename()),
            os.path.join(self.beetsdir, b'state'))
Example #16
0
def replace_ext(path, ext):
    """Return the path with its extension replaced by `ext`.

    The new extension must not contain a leading dot.
    """
    ext_dot = util.bytestring_path('.' + ext)
    return os.path.splitext(path)[0] + ext_dot
Example #17
0
    def test_playlist_update(self):
        spl = SmartPlaylistPlugin()

        i = Mock(path=b'/tagada.mp3')
        i.evaluate_template.side_effect = lambda x, _: x
        q = Mock()
        a_q = Mock()
        lib = Mock()
        lib.items.return_value = [i]
        lib.albums.return_value = []
        pl = b'my_playlist.m3u', (q, None), (a_q, None)
        spl._matched_playlists = [pl]

        dir = bytestring_path(mkdtemp())
        config['smartplaylist']['relative_to'] = False
        config['smartplaylist']['playlist_dir'] = dir
        try:
            spl.update_playlists(lib)
        except Exception:
            rmtree(dir)
            raise

        lib.items.assert_called_once_with(q, None)
        lib.albums.assert_called_once_with(a_q, None)

        m3u_filepath = path.join(dir, pl[0])
        self.assertTrue(path.exists(m3u_filepath))
        with open(syspath(m3u_filepath), 'rb') as f:
            content = f.read()
        rmtree(dir)

        self.assertEqual(content, b'/tagada.mp3\n')
Example #18
0
    def find_key(self, items, write=False):
        overwrite = self.config['overwrite'].get(bool)
        bin = util.bytestring_path(self.config['bin'].get(unicode))

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

            try:
                output = util.command_output([bin, '-f', item.path])
            except (subprocess.CalledProcessError, OSError) as exc:
                self._log.error(u'execution failed: {0}', exc)
                continue

            key_raw = output.rsplit(None, 1)[-1]
            try:
                key = key_raw.decode('utf8')
            except UnicodeDecodeError:
                self._log.error(u'output is invalid UTF-8')
                continue

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

            if write:
                item.try_write()
            item.store()
Example #19
0
    def find_key(self, items, write=False):
        overwrite = self.config['overwrite'].get(bool)
        bin = util.bytestring_path(self.config['bin'].as_str())

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

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

            key_raw = output.rsplit(None, 1)[-1]
            try:
                key = util.text_string(key_raw)
            except UnicodeDecodeError:
                self._log.error(u'output is invalid UTF-8')
                continue

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

            if write:
                item.try_write()
            item.store()
Example #20
0
def temp_file_for(path):
    """Return an unused filename with the same extension as the
    specified path.
    """
    ext = os.path.splitext(path)[1]
    with NamedTemporaryFile(suffix=util.py3_path(ext), delete=False) as f:
        return util.bytestring_path(f.name)
Example #21
0
 def _windows_bytestring_path(self, path):
     old_gfse = sys.getfilesystemencoding
     sys.getfilesystemencoding = lambda: 'mbcs'
     try:
         return util.bytestring_path(path, ntpath)
     finally:
         sys.getfilesystemencoding = old_gfse
Example #22
0
    def __init__(self):
        super(ImportFeedsPlugin, self).__init__()

        self.config.add({
            'formats': [],
            'm3u_name': u'imported.m3u',
            'dir': None,
            'relative_to': None,
            'absolute_path': False,
        })

        feeds_dir = self.config['dir'].get()
        if feeds_dir:
            feeds_dir = os.path.expanduser(bytestring_path(feeds_dir))
            self.config['dir'] = feeds_dir
            if not os.path.exists(syspath(feeds_dir)):
                os.makedirs(syspath(feeds_dir))

        relative_to = self.config['relative_to'].get()
        if relative_to:
            self.config['relative_to'] = normpath(relative_to)
        else:
            self.config['relative_to'] = feeds_dir

        self.register_listener('library_opened', self.library_opened)
        self.register_listener('album_imported', self.album_imported)
        self.register_listener('item_imported', self.item_imported)
Example #23
0
def convert_func(lib, opts, args):
    dest = opts.dest if opts.dest is not None else \
            config['convert']['dest'].get()

    if not dest:
        raise ui.UserError('no convert destination set')

    dest = util.bytestring_path(dest)
    threads = opts.threads if opts.threads is not None else \
            config['convert']['threads'].get(int)
    keep_new = opts.keep_new

    if not config['convert']['paths']:
        path_formats = ui.get_path_formats()
    else:
        path_formats = ui.get_path_formats(config['convert']['paths'])

    ui.commands.list_items(lib, ui.decargs(args), opts.album, None)

    if not ui.input_yn("Convert? (Y/n)"):
        return

    if opts.album:
        items = (i for a in lib.albums(ui.decargs(args)) for i in a.items())
    else:
        items = iter(lib.items(ui.decargs(args)))
    convert = [convert_item(lib, dest, keep_new, path_formats) for i in range(threads)]
    pipe = util.pipeline.Pipeline([items, convert])
    pipe.run_parallel()
Example #24
0
    def set_art(self, path, copy=True):
        """Sets the album's cover art to the image at the given path.
        The image is copied (or moved) into place, replacing any
        existing art.
        """
        path = bytestring_path(path)
        oldart = self.artpath
        artdest = self.art_destination(path)

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

        # Normal operation.
        if oldart == artdest:
            util.remove(oldart)
        artdest = util.unique_path(artdest)
        if copy:
            util.copy(path, artdest)
        else:
            util.move(path, artdest)
        self.artpath = artdest
Example #25
0
    def __setattr__(self, key, value):
        """Set the value of an album attribute."""
        if key == 'id':
            raise AttributeError("can't modify album id")

        elif key in ALBUM_KEYS:
            # Make sure paths are bytestrings.
            if key == 'artpath' and isinstance(value, unicode):
                value = bytestring_path(value)

            # Reflect change in this object.
            self._record[key] = value

            # Store art path as a buffer.
            if key == 'artpath' and isinstance(value, str):
                value = buffer(value)

            # Change album table.
            sql = 'UPDATE albums SET %s=? WHERE id=?' % key
            self._library.conn.execute(sql, (value, self.id))

            # Possibly make modification on items as well.
            if key in ALBUM_KEYS_ITEM:
                for item in self.items():
                    setattr(item, key, value)
                    self._library.store(item)

        else:
            object.__setattr__(self, key, value)
Example #26
0
    def __create_import_dir(self, count):
        self.import_dir = os.path.join(self.temp_dir, b'testsrcdir')
        if os.path.isdir(self.import_dir):
            shutil.rmtree(self.import_dir)

        self.artist_path = os.path.join(self.import_dir, b'artist')
        self.album_path = os.path.join(self.artist_path, b'album')
        self.misc_path = os.path.join(self.import_dir, b'misc')
        os.makedirs(self.album_path)
        os.makedirs(self.misc_path)

        metadata = {
            'artist': 'Tag Artist',
            'album':  'Tag Album',
            'albumartist':  None,
            'mb_trackid': None,
            'mb_albumid': None,
            'comp': None,
        }
        self.album_paths = []
        for i in range(count):
            metadata['track'] = i + 1
            metadata['title'] = 'Tag Title Album %d' % (i + 1)
            track_file = bytestring_path('%02d - track.mp3' % (i + 1))
            dest_path = os.path.join(self.album_path, track_file)
            self.__copy_file(dest_path, metadata)
            self.album_paths.append(dest_path)

        self.artist_paths = []
        metadata['album'] = None
        for i in range(count):
            metadata['track'] = i + 10
            metadata['title'] = 'Tag Title Artist %d' % (i + 1)
            track_file = bytestring_path('track_%d.mp3' % (i + 1))
            dest_path = os.path.join(self.artist_path, track_file)
            self.__copy_file(dest_path, metadata)
            self.artist_paths.append(dest_path)

        self.misc_paths = []
        for i in range(count):
            metadata['artist'] = 'Artist %d' % (i + 42)
            metadata['track'] = i + 5
            metadata['title'] = 'Tag Title Misc %d' % (i + 1)
            track_file = bytestring_path('track_%d.mp3' % (i + 1))
            dest_path = os.path.join(self.misc_path, track_file)
            self.__copy_file(dest_path, metadata)
            self.misc_paths.append(dest_path)
Example #27
0
def permissions(lib, item=None, album=None):
    """Running the permission fixer.
    """
    # Getting the config.
    file_perm = config['permissions']['file'].get()
    dir_perm = config['permissions']['dir'].get()

    # Converts permissions to oct.
    file_perm = convert_perm(file_perm)
    dir_perm = convert_perm(dir_perm)

    # Create chmod_queue.
    file_chmod_queue = []
    if item:
        file_chmod_queue.append(item.path)
    elif album:
        for album_item in album.items():
            file_chmod_queue.append(album_item.path)

    # A set of directories to change permissions for.
    dir_chmod_queue = set()

    for path in file_chmod_queue:
        # Changing permissions on the destination file.
        os.chmod(util.bytestring_path(path), file_perm)

        # Checks if the destination path has the permissions configured.
        if not check_permissions(util.bytestring_path(path), file_perm):
            message = u'There was a problem setting permission on {}'.format(
                path)
            print(message)

        # Adding directories to the directory chmod queue.
        dir_chmod_queue.update(
            dirs_in_library(lib.directory,
                            path))

    # Change permissions for the directories.
    for path in dir_chmod_queue:
        # Chaning permissions on the destination directory.
        os.chmod(util.bytestring_path(path), dir_perm)

        # Checks if the destination path has the permissions configured.
        if not check_permissions(util.bytestring_path(path), dir_perm):
            message = u'There was a problem setting permission on {}'.format(
                path)
            print(message)
Example #28
0
    def update_playlists(self, lib):
        self._log.info(u"Updating {0} smart playlists...",
                       len(self._matched_playlists))

        playlist_dir = self.config['playlist_dir'].as_filename()
        playlist_dir = bytestring_path(playlist_dir)
        relative_to = self.config['relative_to'].get()
        if relative_to:
            relative_to = normpath(relative_to)

        # Maps playlist filenames to lists of track filenames.
        m3us = {}

        for playlist in self._matched_playlists:
            name, (query, q_sort), (album_query, a_q_sort) = playlist
            self._log.debug(u"Creating playlist {0}", name)
            items = []

            if query:
                items.extend(lib.items(query, q_sort))
            if album_query:
                for album in lib.albums(album_query, a_q_sort):
                    items.extend(album.items())

            # As we allow tags in the m3u names, we'll need to iterate through
            # the items and generate the correct m3u file names.
            for item in items:
                m3u_name = item.evaluate_template(name, True)
                m3u_name = sanitize_path(m3u_name, lib.replacements)
                if m3u_name not in m3us:
                    m3us[m3u_name] = []
                item_path = item.path
                if relative_to:
                    item_path = os.path.relpath(item.path, relative_to)
                if item_path not in m3us[m3u_name]:
                    m3us[m3u_name].append(item_path)

        # Write all of the accumulated track lists to files.
        for m3u in m3us:
            m3u_path = normpath(os.path.join(playlist_dir,
                                bytestring_path(m3u)))
            mkdirall(m3u_path)
            with open(syspath(m3u_path), 'wb') as f:
                for path in m3us[m3u]:
                    f.write(path + b'\n')

        self._log.info(u"{0} playlists updated", len(self._matched_playlists))
Example #29
0
 def __setitem__(self, key, value):
     """Set the value of an album attribute."""
     if key == 'artpath':
         if isinstance(value, unicode):
             value = bytestring_path(value)
         elif isinstance(value, buffer):
             value = bytes(value)
     super(Album, self).__setitem__(key, value)
Example #30
0
    def test_cli_config_paths_resolve_relative_to_beetsdir(self):
        os.environ['BEETSDIR'] = util.py3_path(self.beetsdir)

        cli_config_path = os.path.join(self.temp_dir, 'config.yaml')
        with open(cli_config_path, 'w') as file:
            file.write('library: beets.db\n')
            file.write('statefile: state')

        ui._raw_main(['--config', cli_config_path, 'test'])
        self.assert_equal_path(
            util.bytestring_path(config['library'].as_filename()),
            os.path.join(self.beetsdir, b'beets.db')
        )
        self.assert_equal_path(
            util.bytestring_path(config['statefile'].as_filename()),
            os.path.join(self.beetsdir, b'state')
        )
Example #31
0
    def test_external(self):
        external_dir = os.path.join(self.mkdtemp(), 'myplayer')
        self.config['convert']['formats'] = {
            'aac': {
                'command': 'bash -c "cp \'$source\' \'$dest\';' +
                           'printf ISAAC >> \'$dest\'"',
                'extension': 'm4a'
            },
        }
        self.config['alternatives'] = {
            'myplayer': {
                'directory': external_dir,
                'paths': {'default': u'$artist/$title'},
                'formats': u'aac mp3',
                'query': u'onplayer:true',
                'removable': True,
            }
        }

        self.add_album(artist='Bach', title='was mp3', format='mp3')
        self.add_album(artist='Bach', title='was m4a', format='m4a')
        self.add_album(artist='Bach', title='was ogg', format='ogg')
        self.add_album(artist='Beethoven', title='was ogg', format='ogg')

        external_from_mp3 = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was mp3.mp3'))
        external_from_m4a = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was m4a.m4a'))
        external_from_ogg = bytestring_path(
            os.path.join(external_dir, 'Bach', 'was ogg.m4a'))
        external_beet = bytestring_path(
            os.path.join(external_dir, 'Beethoven', 'was ogg.m4a'))

        self.runcli('modify', '--yes', 'onplayer=true', 'artist:Bach')
        with control_stdin('y'):
            out = self.runcli('alt', 'update', 'myplayer')
            self.assertIn('Do you want to create the collection?', out)

        self.assertNotFileTag(external_from_mp3, b'ISAAC')
        self.assertNotFileTag(external_from_m4a, b'ISAAC')
        self.assertFileTag(external_from_ogg, b'ISAAC')
        self.assertFalse(os.path.isfile(external_beet))

        self.runcli('modify', '--yes', 'composer=JSB', 'artist:Bach')

        list_output = self.runcli(
            'alt', 'list-tracks', 'myplayer', '--format', '$artist $title')
        self.assertEqual(list_output, '\n'.join(
            ['Bach was mp3', 'Bach was m4a', 'Bach was ogg', '']))

        self.runcli('alt', 'update', 'myplayer')
        mediafile = MediaFile(syspath(external_from_ogg))
        self.assertEqual(mediafile.composer, 'JSB')

        self.runcli('modify', '--yes', 'onplayer!', 'artist:Bach')
        self.runcli('modify', '--album', '--yes',
                    'onplayer=true', 'albumartist:Beethoven')
        self.runcli('alt', 'update', 'myplayer')

        list_output = self.runcli(
            'alt', 'list-tracks', 'myplayer', '--format', '$artist')
        self.assertEqual(list_output, 'Beethoven\n')

        self.assertFalse(os.path.isfile(external_from_mp3))
        self.assertFalse(os.path.isfile(external_from_m4a))
        self.assertFalse(os.path.isfile(external_from_ogg))
        self.assertFileTag(external_beet, b'ISAAC')
Example #32
0
 def test_art_filename_respects_setting(self):
     art = self.ai.art_destination('something.jpg')
     new_art = bytestring_path('%sartimage.jpg' % os.path.sep)
     self.assertTrue(new_art in art)
Example #33
0
# Mangle the search path to include the beets sources.
sys.path.insert(0, '..')
import beets.library  # noqa: E402
from beets import importer, logging  # noqa: E402
from beets.ui import commands  # noqa: E402
from beets import util  # noqa: E402
import beets  # noqa: E402

# Make sure the development versions of the plugins are used
import beetsplug  # noqa: E402
beetsplug.__path__ = [
    os.path.abspath(os.path.join(__file__, '..', '..', 'beetsplug'))
]

# Test resources path.
RSRC = util.bytestring_path(os.path.join(os.path.dirname(__file__), 'rsrc'))
PLUGINPATH = os.path.join(os.path.dirname(__file__), 'rsrc', 'beetsplug')

# Propagate to root logger so the test runner can capture it
log = logging.getLogger('beets')
log.propagate = True
log.setLevel(logging.DEBUG)

# Dummy item creation.
_item_ident = 0

# OS feature test.
HAVE_SYMLINK = sys.platform != 'win32'
HAVE_HARDLINK = sys.platform != 'win32'
HAVE_REFLINK = reflink.supported_at(tempfile.gettempdir())
Example #34
0
 def setUp(self):
     self.temp_dir = bytestring_path(tempfile.mkdtemp())
Example #35
0
 def __init__(self, lib, record):
     # Decode Unicode paths in database.
     if 'artpath' in record and isinstance(record['artpath'], unicode):
         record['artpath'] = bytestring_path(record['artpath'])
     super(Album, self).__init__(lib, record)
Example #36
0
 def create_temp_dir(self):
     """Create a temporary directory and assign it into
     `self.temp_dir`. Call `remove_temp_dir` later to delete it.
     """
     temp_dir = mkdtemp()
     self.temp_dir = util.bytestring_path(temp_dir)
Example #37
0
 def test_unicode_extension_in_fragment(self):
     self.lib.path_formats = [(u'default', u'foo')]
     self.i.path = util.bytestring_path(u'bar.caf\xe9')
     dest = self.i.destination(platform='linux2', fragment=True)
     self.assertEqual(dest, u'foo.caf\xe9')
    def _copy_items(self):
        training_name = self._get_cleaned_training_name()
        target_name = common.get_training_attribute(self.training, "target")

        # The copy_files is only False when it is explicitly declared so
        copy_files = common.get_target_attribute_for_training(
            self.training, "copy_files")
        copy_files = False if copy_files == False else True

        if not copy_files:
            common.say(
                "Copying to target[{0}] was skipped (copy_files=no).".format(
                    target_name))
            return

        increment_play_count = common.get_training_attribute(
            self.training, "increment_play_count")
        dst_path = Path(common.get_destination_path_for_training(
            self.training))

        dst_sub_dir = dst_path.joinpath(training_name)
        if not os.path.isdir(dst_sub_dir):
            os.mkdir(dst_sub_dir)

        common.say("Copying to target[{0}]: {1}".format(
            target_name, dst_sub_dir))

        cnt = 0
        # todo: disable alive bar when running in verbose mode
        # from beets import logging as beetslogging
        # beets_log = beetslogging.getLogger("beets")
        # print(beets_log.getEffectiveLevel())

        with alive_bar(len(self.items)) as bar:
            for item in self.items:
                src = util.displayable_path(item.get("path"))
                if not os.path.isfile(src):
                    # todo: this is bad enough to interrupt! create option
                    # for this
                    common.say("File does not exist: {}".format(src))
                    continue

                fn, ext = os.path.splitext(src)
                gen_filename = "{0}_{1}{2}" \
                    .format(str(cnt).zfill(6), common.get_random_string(), ext)

                dst = dst_sub_dir.joinpath(gen_filename)
                # dst = "{0}/{1}".format(dst_path, gen_filename)

                common.say("Copying[{1}]: {0}".format(src, gen_filename))

                if not self.cfg_dry_run:
                    util.copy(src, dst)

                    # store the file_name for the playlist
                    item["exportpath"] = util.bytestring_path(gen_filename)

                    if increment_play_count:
                        common.increment_play_count_on_item(item)

                cnt += 1
                bar()
class TestHelper(TestCase, Assertions):
    _test_config_dir_ = os.path.join(
        bytestring_path(os.path.dirname(__file__)), b'config')
    _test_fixture_dir = os.path.join(
        bytestring_path(os.path.dirname(__file__)), b'fixtures')
    _test_target_dir = bytestring_path("/tmp/beets-autofix")

    def setUp(self):
        """Setup required for running test. Must be called before running any
        tests.
        """
        self.reset_beets(config_file=b"empty.yml")

    def tearDown(self):
        self.teardown_beets()

    def reset_beets(self, config_file: bytes):
        self.teardown_beets()
        plugins._classes = {structuredcomments.StructuredCommentsPlugin}
        self._setup_beets(config_file)

    def _setup_beets(self, config_file: bytes):
        self.addCleanup(self.teardown_beets)
        os.environ['BEETSDIR'] = self.mkdtemp()

        self.config = beets.config
        self.config.clear()

        # add user configuration
        config_file = format(
            os.path.join(self._test_config_dir_, config_file).decode())
        shutil.copyfile(config_file, self.config.user_config_path())
        self.config.read()

        self.config['plugins'] = []
        self.config['verbose'] = True
        self.config['ui']['color'] = False
        self.config['threaded'] = False
        self.config['import']['copy'] = False

        os.makedirs(self._test_target_dir, exist_ok=True)

        libdir = self.mkdtemp()
        self.config['directory'] = libdir
        self.libdir = bytestring_path(libdir)

        self.lib = beets.library.Library(':memory:', self.libdir)

        # This will initialize (create instance) of the plugins
        plugins.find_plugins()

    def teardown_beets(self):
        self.unload_plugins()

        shutil.rmtree(self._test_target_dir, ignore_errors=True)

        if hasattr(self, '_tempdirs'):
            for tempdir in self._tempdirs:
                if os.path.exists(tempdir):
                    shutil.rmtree(syspath(tempdir), ignore_errors=True)
        self._tempdirs = []

        if hasattr(self, 'lib'):
            if hasattr(self.lib, '_connections'):
                del self.lib._connections

        if 'BEETSDIR' in os.environ:
            del os.environ['BEETSDIR']

        if hasattr(self, 'config'):
            self.config.clear()

        # beets.config.read(user=False, defaults=True)

    def mkdtemp(self):
        # This return a str path, i.e. Unicode on Python 3. We need this in
        # order to put paths into the configuration.
        path = tempfile.mkdtemp()
        self._tempdirs.append(path)
        return path

    @staticmethod
    def unload_plugins():
        for plugin in plugins._classes:
            plugin.listeners = None
            plugins._classes = set()
            plugins._instances = {}

    def runcli(self, *args):
        # TODO mock stdin
        with capture_stdout() as out:
            try:
                ui._raw_main(_convert_args(list(args)), self.lib)
            except ui.UserError as u:
                # TODO remove this and handle exceptions in tests
                print(u.args[0])
        return out.getvalue()

    def lib_path(self, path):
        return os.path.join(self.libdir,
                            path.replace(b'/', bytestring_path(os.sep)))

    @staticmethod
    def _dump_config(cfg: Subview):
        # print(json.dumps(cfg.get(), indent=4, sort_keys=False))
        flat = cfg.flatten()
        print(
            yaml.dump(flat,
                      Dumper=Dumper,
                      default_flow_style=None,
                      indent=2,
                      width=1000))
Example #40
0
 def parse(self, string):
     return normpath(bytestring_path(string))
 def playlist_dir(self, alternative, alt_dir):
     playlist_dir = self.config['playlist_dir'].as_str()
     playlist_dir = bytestring_path(playlist_dir)
     if not os.path.isabs(syspath(playlist_dir)):
         playlist_dir = os.path.join(alt_dir, playlist_dir)
     return playlist_dir
Example #42
0
 def clause(self):
     dir_pat = self.dir_path + '%'
     file_blob = buffer(bytestring_path(self.file_path))
     return '(path = ?) || (path LIKE ?)', (file_blob, dir_pat)
Example #43
0
        def _dup(lib, opts, args):
            self.config.set_args(opts)
            album = self.config['album'].get(bool)
            checksum = self.config['checksum'].get(str)
            copy = bytestring_path(self.config['copy'].as_str())
            count = self.config['count'].get(bool)
            delete = self.config['delete'].get(bool)
            fmt = self.config['format'].get(str)
            full = self.config['full'].get(bool)
            keys = self.config['keys'].as_str_seq()
            merge = self.config['merge'].get(bool)
            move = bytestring_path(self.config['move'].as_str())
            path = self.config['path'].get(bool)
            tiebreak = self.config['tiebreak'].get(dict)
            strict = self.config['strict'].get(bool)
            tag = self.config['tag'].get(str)

            if album:
                if not keys:
                    keys = ['mb_albumid']
                items = lib.albums(decargs(args))
            else:
                if not keys:
                    keys = ['mb_trackid', 'mb_albumid']
                items = lib.items(decargs(args))

            # If there's nothing to do, return early. The code below assumes
            # `items` to be non-empty.
            if not items:
                return

            if path:
                fmt = u'$path'

            # Default format string for count mode.
            if count and not fmt:
                if album:
                    fmt = u'$albumartist - $album'
                else:
                    fmt = u'$albumartist - $album - $title'
                fmt += u': {0}'

            if checksum:
                for i in items:
                    k, _ = self._checksum(i, checksum)
                keys = [k]

            for obj_id, obj_count, objs in self._duplicates(items,
                                                            keys=keys,
                                                            full=full,
                                                            strict=strict,
                                                            tiebreak=tiebreak,
                                                            merge=merge):
                if obj_id:  # Skip empty IDs.
                    for o in objs:
                        self._process_item(o,
                                           copy=copy,
                                           move=move,
                                           delete=delete,
                                           tag=tag,
                                           fmt=fmt.format(obj_count))
Example #44
0
 def get_feeds_dir(self):
     feeds_dir = self.config['dir'].get()
     if feeds_dir:
         return os.path.expanduser(bytestring_path(feeds_dir))
     return config['directory'].as_filename()
Example #45
0
    def fetch_image(self, candidate, plugin):
        """Downloads an image from a URL and checks whether it seems to
        actually be an image. If so, returns a path to the downloaded image.
        Otherwise, returns None.
        """
        if plugin.maxwidth:
            candidate.url = ArtResizer.shared.proxy_url(
                plugin.maxwidth, candidate.url)
        try:
            with closing(
                    self.request(candidate.url,
                                 stream=True,
                                 message=u'downloading image')) as resp:
                ct = resp.headers.get('Content-Type', None)

                # Download the image to a temporary file. As some servers
                # (notably fanart.tv) have proven to return wrong Content-Types
                # when images were uploaded with a bad file extension, do not
                # rely on it. Instead validate the type using the file magic
                # and only then determine the extension.
                data = resp.iter_content(chunk_size=1024)
                header = b''
                for chunk in data:
                    header += chunk
                    if len(header) >= 32:
                        # The imghdr module will only read 32 bytes, and our
                        # own additions in mediafile even less.
                        break
                else:
                    # server didn't return enough data, i.e. corrupt image
                    return

                real_ct = image_mime_type(header)
                if real_ct is None:
                    # detection by file magic failed, fall back to the
                    # server-supplied Content-Type
                    # Is our type detection failsafe enough to drop this?
                    real_ct = ct

                if real_ct not in CONTENT_TYPES:
                    self._log.debug(u'not a supported image: {}', real_ct
                                    or u'unknown content type')
                    return

                ext = b'.' + CONTENT_TYPES[real_ct][0]

                if real_ct != ct:
                    self._log.warning(
                        u'Server specified {}, but returned a '
                        u'{} image. Correcting the extension '
                        u'to {}', ct, real_ct, ext)

                suffix = py3_path(ext)
                with NamedTemporaryFile(suffix=suffix, delete=False) as fh:
                    # write the first already loaded part of the image
                    fh.write(header)
                    # download the remaining part of the image
                    for chunk in data:
                        fh.write(chunk)
                self._log.debug(u'downloaded art to: {0}',
                                util.displayable_path(fh.name))
                candidate.path = util.bytestring_path(fh.name)
                return

        except (IOError, requests.RequestException, TypeError) as exc:
            # Handling TypeError works around a urllib3 bug:
            # https://github.com/shazow/urllib3/issues/556
            self._log.debug(u'error fetching art: {}', exc)
            return
 def lib_path(self, path):
     return os.path.join(self.libdir,
                         path.replace(b'/', bytestring_path(os.sep)))
Example #47
0
 def _mediafile_fixture(self, name, extension='mp3'):
     name = bytestring_path(name + '.' + extension)
     src = os.path.join(_common.RSRC, name)
     target = os.path.join(self.temp_dir, name)
     shutil.copy(src, target)
     return mediafile.MediaFile(target)
Example #48
0
    def destination(self, fragment=False, basedir=None, platform=None,
                    path_formats=None):
        """Returns the path in the library directory designated for the
        item (i.e., where the file ought to be). fragment makes this
        method return just the path fragment underneath the root library
        directory; the path is also returned as Unicode instead of
        encoded as a bytestring. basedir can override the library's base
        directory for the destination.
        """
        self._check_db()
        platform = platform or sys.platform
        basedir = basedir or self._db.directory
        path_formats = path_formats or self._db.path_formats

        # Use a path format based on a query, falling back on the
        # default.
        for query, path_format in path_formats:
            if query == PF_KEY_DEFAULT:
                continue
            query, _ = parse_query_string(query, type(self))
            if query.match(self):
                # The query matches the item! Use the corresponding path
                # format.
                break
        else:
            # No query matched; fall back to default.
            for query, path_format in path_formats:
                if query == PF_KEY_DEFAULT:
                    break
            else:
                assert False, "no default path format"
        if isinstance(path_format, Template):
            subpath_tmpl = path_format
        else:
            subpath_tmpl = Template(path_format)

        # Evaluate the selected template.
        subpath = self.evaluate_template(subpath_tmpl, True)

        # Prepare path for output: normalize Unicode characters.
        if platform == 'darwin':
            subpath = unicodedata.normalize('NFD', subpath)
        else:
            subpath = unicodedata.normalize('NFC', subpath)

        if beets.config['asciify_paths']:
            subpath = unidecode(subpath)

        # Truncate components and remove forbidden characters.
        subpath = util.sanitize_path(subpath, self._db.replacements)

        # Encode for the filesystem.
        if not fragment:
            subpath = bytestring_path(subpath)

        # Preserve extension.
        _, extension = os.path.splitext(self.path)
        if fragment:
            # Outputting Unicode.
            extension = extension.decode('utf8', 'ignore')
        subpath += extension.lower()

        # Truncate too-long components.
        maxlen = beets.config['max_filename_length'].get(int)
        if not maxlen:
            # When zero, try to determine from filesystem.
            maxlen = util.max_filename_length(self._db.directory)
        subpath = util.truncate_path(subpath, maxlen)

        if fragment:
            return subpath
        else:
            return normpath(os.path.join(basedir, subpath))
    def update_playlist(self, lib, alternative, m3u):
        config = self.alternatives.config[alternative]
        if 'directory' in config:
            alt_dir = config['directory'].as_str()
        else:
            alt_dir = alternative
        alt_dir = bytestring_path(alt_dir)
        if not os.path.isabs(syspath(alt_dir)):
            alt_dir = os.path.join(lib.directory, alt_dir)

        playlist_dir = beets.util.bytestring_path(self.playlist.playlist_dir)
        m3uname = os.path.relpath(m3u, playlist_dir)

        outm3u = os.path.join(self.playlist_dir(alternative, alt_dir), m3uname)
        self._log.info('Writing playlist {}'.format(
            beets.util.displayable_path(outm3u)))

        # Gather up the items in the playlist and map to the alternative
        m3ubase, _ = os.path.splitext(m3uname)
        query = playlist.PlaylistQuery(beets.util.as_string(m3ubase))
        pathmap = {}
        for item in lib.items(query):
            alt_path = item.get(u'alt.{}'.format(alternative))
            if alt_path:
                pathmap[beets.util.bytestring_path(
                    item.path)] = beets.util.bytestring_path(alt_path)

        src_base_dir = beets.util.bytestring_path(
            self.playlist.relative_to if self.playlist.relative_to else os.
            path.dirname(m3u))

        if not self.relative_to and self.config['relative_to'] == 'library':
            relative_to = beets.util.bytestring_path(alt_dir)
        else:
            relative_to = self.relative_to

        alt_base_dir = beets.util.bytestring_path(
            relative_to if relative_to else os.path.dirname(outm3u))

        lines = []
        with open(m3u, mode='rb') as m3ufile:
            for line in m3ufile:
                srcpath = line.rstrip(b'\r\n')
                is_relative = not os.path.isabs(srcpath)
                if is_relative:
                    srcpath = os.path.join(src_base_dir, srcpath)
                srcpath = beets.util.normpath(srcpath)
                if not os.path.exists(srcpath):
                    self._log.error('Path {} in playlist {} does not exist',
                                    srcpath, m3uname)
                    continue

                newpath = pathmap.get(srcpath)
                if not newpath:
                    self._log.debug(
                        'Failed to map path {} in playlist {} for alt {}',
                        srcpath, m3uname, alternative)
                    continue

                if is_relative or self.is_relative:
                    newpath = os.path.relpath(newpath, alt_base_dir)

                lines.append(line.replace(srcpath, newpath))

        if lines:
            mkdirall(outm3u)
            with open(outm3u, 'wb') as m3ufile:
                m3ufile.writelines(lines)