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)
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)
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))
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()
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
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, ''))
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)
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)
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
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))
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'))
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'))
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.
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])
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'))
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
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')
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()
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()
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)
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
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)
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()
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
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)
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)
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)
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))
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)
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') )
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')
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)
# 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())
def setUp(self): self.temp_dir = bytestring_path(tempfile.mkdtemp())
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)
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)
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))
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
def clause(self): dir_pat = self.dir_path + '%' file_blob = buffer(bytestring_path(self.file_path)) return '(path = ?) || (path LIKE ?)', (file_blob, dir_pat)
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))
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()
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)))
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)
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)