def setUp(self): self.setup_beets() # Don't use the BEETSDIR from `helper`. Instead, we point the home # directory there. Some tests will set `BEETSDIR` themselves. del os.environ['BEETSDIR'] self._old_home = os.environ.get('HOME') os.environ['HOME'] = util.py3_path(self.temp_dir) # Also set APPDATA, the Windows equivalent of setting $HOME. self._old_appdata = os.environ.get('APPDATA') os.environ['APPDATA'] = \ util.py3_path(os.path.join(self.temp_dir, b'AppData', b'Roaming')) self._orig_cwd = os.getcwd() self.test_cmd = self._make_test_cmd() commands.default_commands.append(self.test_cmd) # Default user configuration if platform.system() == 'Windows': self.user_config_dir = os.path.join(self.temp_dir, b'AppData', b'Roaming', b'beets') else: self.user_config_dir = os.path.join(self.temp_dir, b'.config', b'beets') os.makedirs(self.user_config_dir) self.user_config_path = os.path.join(self.user_config_dir, b'config.yaml') # Custom BEETSDIR self.beetsdir = os.path.join(self.temp_dir, b'beetsdir') os.makedirs(self.beetsdir) self._reset_config()
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 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. source_path = item.path item.path = dest item.write() item.read() # Load new audio information data. item.store() if self.config['delete_originals']: self._log.info(u'Removing original file {0}', source_path) util.remove(source_path, False)
def run_bpd(self, host='localhost', password=None, do_hello=True, second_client=False): """ Runs BPD in another process, configured with the same library database as we created in the setUp method. Exposes a client that is connected to the server, and kills the server at the end. """ # Create a config file: config = { 'pluginpath': [py3_path(self.temp_dir)], 'plugins': 'bpd', # use port 0 to let the OS choose a free port 'bpd': {'host': host, 'port': 0, 'control_port': 0}, } if password: config['bpd']['password'] = password config_file = tempfile.NamedTemporaryFile( mode='wb', dir=py3_path(self.temp_dir), suffix='.yaml', delete=False) config_file.write( yaml.dump(config, Dumper=confuse.Dumper, encoding='utf-8')) config_file.close() # Fork and launch BPD in the new process: assigned_port = mp.Queue(2) # 2 slots, `control_port` and `port` server = mp.Process(target=start_server, args=([ '--library', self.config['library'].as_filename(), '--directory', py3_path(self.libdir), '--config', py3_path(config_file.name), 'bpd' ], assigned_port)) server.start() try: assigned_port.get(timeout=1) # skip control_port port = assigned_port.get(timeout=0.5) # read port sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((host, port)) if second_client: sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock2.connect((host, port)) yield ( MPCClient(sock, do_hello), MPCClient(sock2, do_hello), ) finally: sock2.close() else: yield MPCClient(sock, do_hello) finally: sock.close() finally: server.terminate() server.join(timeout=0.2)
def pil_resize(maxwidth, path_in, path_out=None, quality=0, max_filesize=0): """Resize using Python Imaging Library (PIL). Return the output path of resized image. """ path_out = path_out or temp_file_for(path_in) from PIL import Image log.debug('artresizer: PIL resizing {0} to {1}', util.displayable_path(path_in), util.displayable_path(path_out)) try: im = Image.open(util.syspath(path_in)) size = maxwidth, maxwidth im.thumbnail(size, Image.ANTIALIAS) if quality == 0: # Use PIL's default quality. quality = -1 # progressive=False only affects JPEGs and is the default, # but we include it here for explicitness. im.save(util.py3_path(path_out), quality=quality, progressive=False) if max_filesize > 0: # If maximum filesize is set, we attempt to lower the quality of # jpeg conversion by a proportional amount, up to 3 attempts # First, set the maximum quality to either provided, or 95 if quality > 0: lower_qual = quality else: lower_qual = 95 for i in range(5): # 5 attempts is an abitrary choice filesize = os.stat(util.syspath(path_out)).st_size log.debug("PIL Pass {0} : Output size: {1}B", i, filesize) if filesize <= max_filesize: return path_out # The relationship between filesize & quality will be # image dependent. lower_qual -= 10 # Restrict quality dropping below 10 if lower_qual < 10: lower_qual = 10 # Use optimize flag to improve filesize decrease im.save(util.py3_path(path_out), quality=lower_qual, optimize=True, progressive=False) log.warning("PIL Failed to resize file to below {0}B", max_filesize) return path_out else: return path_out except OSError: log.error("PIL cannot create thumbnail for '{0}'", util.displayable_path(path_in)) return path_in
def item_file(item_id): item = g.lib.get_item(item_id) response = flask.send_file( util.py3_path(item.path), as_attachment=True, attachment_filename=os.path.basename(util.py3_path(item.path)), ) response.headers['Content-Length'] = os.path.getsize(item.path) return response
def run_bpd(self, host='localhost', port=9876, password=None, do_hello=True, second_client=False): """ Runs BPD in another process, configured with the same library database as we created in the setUp method. Exposes a client that is connected to the server, and kills the server at the end. """ # Create a config file: config = { 'pluginpath': [py3_path(self.temp_dir)], 'plugins': 'bpd', 'bpd': {'host': host, 'port': port, 'control_port': port + 1}, } if password: config['bpd']['password'] = password config_file = tempfile.NamedTemporaryFile( mode='wb', dir=py3_path(self.temp_dir), suffix='.yaml', delete=False) config_file.write( yaml.dump(config, Dumper=confit.Dumper, encoding='utf-8')) config_file.close() # Fork and launch BPD in the new process: args = ( '--library', self.config['library'].as_filename(), '--directory', py3_path(self.libdir), '--config', py3_path(config_file.name), 'bpd' ) server = mp.Process(target=start_beets, args=args) server.start() # Wait until the socket is connected: sock, sock2 = None, None for _ in range(20): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if sock.connect_ex((host, port)) == 0: break else: sock.close() time.sleep(0.01) else: raise RuntimeError('Timed out waiting for the BPD server') try: if second_client: sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock2.connect((host, port)) yield MPCClient(sock, do_hello), MPCClient(sock2, do_hello) else: yield MPCClient(sock, do_hello) finally: sock.close() if sock2: sock2.close() server.terminate() server.join(timeout=0.2)
def setUp(self): self.setup_beets() self.load_plugins('metasync') self.config['metasync']['source'] = 'itunes' if _is_windows(): self.config['metasync']['itunes']['library'] = \ py3_path(self.itunes_library_windows) else: self.config['metasync']['itunes']['library'] = \ py3_path(self.itunes_library_unix) self._set_up_data()
def run_bpd(self, host='localhost', port=9876, password=None, do_hello=True): """ Runs BPD in another process, configured with the same library database as we created in the setUp method. Exposes a client that is connected to the server, and kills the server at the end. """ # Create a config file: config = { 'pluginpath': [py3_path(self.temp_dir)], 'plugins': 'bpd', 'bpd': {'host': host, 'port': port}, } if password: config['bpd']['password'] = password config_file = tempfile.NamedTemporaryFile( mode='wb', dir=py3_path(self.temp_dir), suffix='.yaml', delete=False) config_file.write( yaml.dump(config, Dumper=confit.Dumper, encoding='utf-8')) config_file.close() # Fork and launch BPD in the new process: args = ( '--library', self.config['library'].as_filename(), '--directory', py3_path(self.libdir), '--config', py3_path(config_file.name), 'bpd' ) server = mp.Process(target=start_beets, args=args) server.start() # Wait until the socket is connected: sock = None for _ in range(20): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if sock.connect_ex((host, port)) == 0: break else: sock.close() time.sleep(0.01) else: raise RuntimeError('Timed out waiting for the BPD server') try: yield MPCClient(sock, do_hello) finally: sock.close() server.terminate() server.join(timeout=0.2)
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'] = py3_path(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 item_file(item_id): item = g.lib.get_item(item_id) # On Windows under Python 2, Flask wants a Unicode path. On Python 3, it # *always* wants a Unicode path. if os.name == 'nt': item_path = util.syspath(item.path) else: item_path = util.py3_path(item.path) try: unicode_item_path = util.text_string(item.path) except (UnicodeDecodeError, UnicodeEncodeError): unicode_item_path = util.displayable_path(item.path) base_filename = os.path.basename(unicode_item_path) try: # Imitate http.server behaviour base_filename.encode("latin-1", "strict") except UnicodeEncodeError: safe_filename = unidecode(base_filename) else: safe_filename = base_filename response = flask.send_file( item_path, as_attachment=True, attachment_filename=safe_filename ) response.headers['Content-Length'] = os.path.getsize(item_path) return response
def _set_file(self): """Initialize the filesrc element with the next file to be analyzed. """ # No more files, we're done if len(self._files) == 0: return False self._file = self._files.pop(0) # Ensure the filesrc element received the paused state of the # pipeline in a blocking manner self._src.sync_state_with_parent() self._src.get_state(self.Gst.CLOCK_TIME_NONE) # Ensure the decodebin element receives the paused state of the # pipeline in a blocking manner self._decbin.sync_state_with_parent() self._decbin.get_state(self.Gst.CLOCK_TIME_NONE) # Disconnect the decodebin element from the pipeline, set its # state to READY to to clear it. self._decbin.unlink(self._conv) self._decbin.set_state(self.Gst.State.READY) # Set a new file on the filesrc element, can only be done in the # READY state self._src.set_state(self.Gst.State.READY) self._src.set_property("location", py3_path(syspath(self._file.path))) self._decbin.link(self._conv) self._pipe.set_state(self.Gst.State.READY) return True
def item_file(item_id): item = g.lib.get_item(item_id) # On Windows under Python 2, Flask wants a Unicode path. On Python 3, it # *always* wants a Unicode path. if os.name == 'nt': item_path = util.syspath(item.path) else: item_path = util.py3_path(item.path) try: unicode_item_path = util.text_string(item.path) except (UnicodeDecodeError, UnicodeEncodeError): unicode_item_path = util.displayable_path(item.path) base_filename = os.path.basename(unicode_item_path) try: # Imitate http.server behaviour base_filename.encode("latin-1", "strict") except UnicodeEncodeError: safe_filename = unidecode(base_filename) else: safe_filename = base_filename response = flask.send_file(item_path, as_attachment=True, attachment_filename=safe_filename) response.headers['Content-Length'] = os.path.getsize(item_path) return response
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 _load_plugins(options, config): """Load the plugins specified on the command line or in the configuration. """ paths = config['pluginpath'].as_str_seq(split=False) paths = [util.normpath(p) for p in paths] log.debug('plugin paths: {0}', util.displayable_path(paths)) # On Python 3, the search paths need to be unicode. paths = [util.py3_path(p) for p in paths] # Extend the `beetsplug` package to include the plugin paths. import beetsplug beetsplug.__path__ = paths + list(beetsplug.__path__) # For backwards compatibility, also support plugin paths that # *contain* a `beetsplug` package. sys.path += paths # If we were given any plugins on the command line, use those. if options.plugins is not None: plugin_list = (options.plugins.split(',') if len(options.plugins) > 0 else []) else: plugin_list = config['plugins'].as_str_seq() plugins.load_plugins(plugin_list) plugins.send("pluginload") return plugins
def test_beetsdir_config_does_not_load_default_user_config(self): os.environ['BEETSDIR'] = util.py3_path(self.beetsdir) with open(self.user_config_path, 'w') as file: file.write('anoption: value') config.read() self.assertFalse(config['anoption'].exists())
def item_file(item_id): item = g.lib.get_item(item_id) # On Windows under Python 2, Flask wants a Unicode path. On Python 3, it # *always* wants a Unicode path. if os.name == 'nt': item_path = util.syspath(item.path) else: item_path = util.py3_path(item.path) response = flask.send_file( item_path, as_attachment=True, attachment_filename=os.path.basename(util.py3_path(item.path)), ) response.headers['Content-Length'] = os.path.getsize(item_path) return response
def setup_beets(self, disk=False): """Setup pristine global configuration and library for testing. Sets ``beets.config`` so we can safely use any functionality that uses the global configuration. All paths used are contained in a temporary directory Sets the following properties on itself. - ``temp_dir`` Path to a temporary directory containing all files specific to beets - ``libdir`` Path to a subfolder of ``temp_dir``, containing the library's media files. Same as ``config['directory']``. - ``config`` The global configuration used by beets. - ``lib`` Library instance created with the settings from ``config``. Make sure you call ``teardown_beets()`` afterwards. """ self.create_temp_dir() os.environ['BEETSDIR'] = util.py3_path(self.temp_dir) self.config = beets.config self.config.clear() self.config.read() self.config['plugins'] = [] self.config['verbose'] = 1 self.config['ui']['color'] = False self.config['threaded'] = False self.libdir = os.path.join(self.temp_dir, b'libdir') os.mkdir(self.libdir) self.config['directory'] = util.py3_path(self.libdir) if disk: dbpath = util.bytestring_path( self.config['library'].as_filename() ) else: dbpath = ':memory:' self.lib = Library(dbpath, self.libdir)
def test_beetsdir_config(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('anoption: overwrite') config.read() self.assertEqual(config['anoption'].get(), 'overwrite')
def _set_first_file(self): if len(self._files) == 0: return False self._file = self._files.pop(0) self._pipe.set_state(self.Gst.State.NULL) self._src.set_property("location", py3_path(syspath(self._file.path))) self._pipe.set_state(self.Gst.State.PLAYING) return True
def resource_object(album): """Construct a JSON:API resource object from a beets Album. Args: album: A beets Album object. """ attributes = {} # Use aura => beets attribute name map for aura_attr, beets_attr in ALBUM_ATTR_MAP.items(): a = getattr(album, beets_attr) # Only set attribute if it's not None, 0, "", etc. # NOTE: This could mean required attributes are not set if a: attributes[aura_attr] = a # Get beets Item objects for all tracks in the album sorted by # track number. Sorting is not required but it's nice. query = MatchQuery("album_id", album.id) sort = FixedFieldSort("track", ascending=True) tracks = current_app.config["lib"].items(query, sort) # JSON:API one-to-many relationship to tracks on the album relationships = { "tracks": { "data": [{ "type": "track", "id": str(t.id) } for t in tracks] } } # Add images relationship if album has associated images if album.artpath: path = py3_path(album.artpath) filename = path.split("/")[-1] image_id = f"album-{album.id}-{filename}" relationships["images"] = { "data": [{ "type": "image", "id": image_id }] } # Add artist relationship if artist name is same on tracks # Tracks are used to define artists so don't albumartist # Check for all tracks in case some have featured artists if album.albumartist in [t.artist for t in tracks]: relationships["artists"] = { "data": [{ "type": "artist", "id": album.albumartist }] } return { "type": "album", "id": str(album.id), "attributes": attributes, "relationships": relationships, }
def audio_file(track_id): """Supply an audio file for the specified track. Args: track_id: The id of the track provided in the URL (integer). """ track = current_app.config["lib"].get_item(track_id) if not track: return AURADocument.error( "404 Not Found", "No track with the requested id.", "There is no track with an id of {} in the library.".format( track_id), ) path = py3_path(track.path) if not isfile(path): return AURADocument.error( "404 Not Found", "No audio file for the requested track.", ("There is no audio file for track {} at the expected location" ).format(track_id), ) file_mimetype = guess_type(path)[0] if not file_mimetype: return AURADocument.error( "500 Internal Server Error", "Requested audio file has an unknown mimetype.", ("The audio file for track {} has an unknown mimetype. " "Its file extension is {}.").format(track_id, path.split(".")[-1]), ) # Check that the Accept header contains the file's mimetype # Takes into account */* and audio/* # Adding support for the bitrate parameter would require some effort so I # left it out. This means the client could be sent an error even if the # audio doesn't need transcoding. if not request.accept_mimetypes.best_match([file_mimetype]): return AURADocument.error( "406 Not Acceptable", "Unsupported MIME type or bitrate parameter in Accept header.", ("The audio file for track {} is only available as {} and " "bitrate parameters are not supported.").format( track_id, file_mimetype), ) return send_file( path, mimetype=file_mimetype, # Handles filename in Content-Disposition header as_attachment=True, # Tries to upgrade the stream to support range requests conditional=True, )
def pil_deinterlace(path_in, path_out=None): path_out = path_out or temp_file_for(path_in) from PIL import Image try: im = Image.open(util.syspath(path_in)) im.save(util.py3_path(path_out), progressive=False) return path_out except IOError: return path_in
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 setUp(self): # A "clean" source list including only the defaults. beets.config.sources = [] beets.config.read(user=False, defaults=True) # Direct paths to a temporary directory. Tests can also use this # temporary directory. self.temp_dir = util.bytestring_path(tempfile.mkdtemp()) beets.config["statefile"] = util.py3_path(os.path.join(self.temp_dir, b"state.pickle")) beets.config["library"] = util.py3_path(os.path.join(self.temp_dir, b"library.db")) beets.config["directory"] = util.py3_path(os.path.join(self.temp_dir, b"libdir")) # Set $HOME, which is used by confit's `config_dir()` to create # directories. self._old_home = os.environ.get("HOME") os.environ["HOME"] = util.py3_path(self.temp_dir) # Initialize, but don't install, a DummyIO. self.io = DummyIO()
def test_cli_config_file_overwrites_beetsdir_defaults(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('anoption: value') cli_config_path = os.path.join(self.temp_dir, b'config.yaml') with open(cli_config_path, 'w') as file: file.write('anoption: cli overwrite') self.run_command('--config', cli_config_path, 'test', lib=None) self.assertEqual(config['anoption'].get(), 'cli overwrite')
def convert_format(self, source, target, deinterlaced): from PIL import Image, UnidentifiedImageError try: with Image.open(syspath(source)) as im: im.save(py3_path(target), progressive=not deinterlaced) return target except (ValueError, TypeError, UnidentifiedImageError, FileNotFoundError, OSError): log.exception("failed to convert image {} -> {}", source, target) return source
def deinterlace(self, path_in, path_out=None): path_out = path_out or temp_file_for(path_in) from PIL import Image try: im = Image.open(syspath(path_in)) im.save(py3_path(path_out), progressive=False) return path_out except IOError: # FIXME: Should probably issue a warning? return path_in
def setUp(self): self.setup_beets() self.item = self.add_item() config['smartplaylist']['playlists'].set([ {'name': 'my_playlist.m3u', 'query': self.item.title}, {'name': 'all.m3u', 'query': u''} ]) config['smartplaylist']['playlist_dir'].set(py3_path(self.temp_dir)) self.load_plugins('smartplaylist')
def _bpd_add(self, client, *items, **kwargs): """ Add the given item to the BPD playlist or queue. """ paths = ['/'.join([ item.artist, item.album, py3_path(os.path.basename(item.path))]) for item in items] playlist = kwargs.get('playlist') if playlist: commands = [('playlistadd', playlist, path) for path in paths] else: commands = [('add', path) for path in paths] responses = client.send_commands(*commands) self._assert_ok(*responses)
def setUp(self): self.setup_beets() self.item = self.add_item() config['smartplaylist']['playlists'].set([{ 'name': 'my_playlist.m3u', 'query': self.item.title }, { 'name': 'all.m3u', 'query': u'' }]) config['smartplaylist']['playlist_dir'].set(py3_path(self.temp_dir)) self.load_plugins('smartplaylist')
def setUp(self): # A "clean" source list including only the defaults. beets.config.sources = [] beets.config.read(user=False, defaults=True) # Direct paths to a temporary directory. Tests can also use this # temporary directory. self.temp_dir = util.bytestring_path(tempfile.mkdtemp()) beets.config['statefile'] = \ util.py3_path(os.path.join(self.temp_dir, b'state.pickle')) beets.config['library'] = \ util.py3_path(os.path.join(self.temp_dir, b'library.db')) beets.config['directory'] = \ util.py3_path(os.path.join(self.temp_dir, b'libdir')) # Set $HOME, which is used by Confuse to create directories. self._old_home = os.environ.get('HOME') os.environ['HOME'] = util.py3_path(self.temp_dir) # Initialize, but don't install, a DummyIO. self.io = DummyIO()
def _create_connection(self): """Create a SQLite connection to the underlying database. Makes a new connection every time. If you need to configure the connection settings (e.g., add custom functions), override this method. """ # Make a new connection. The `sqlite3` module can't use # bytestring paths here on Python 3, so we need to # provide a `str` using `py3_path`. conn = sqlite3.connect(py3_path(self.path), timeout=self.timeout) # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row return conn
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, 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.beetsdir, b'beets.db')) self.assert_equal_path( util.bytestring_path(config['statefile'].as_filename()), os.path.join(self.beetsdir, b'state'))
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 _create_connection(self): """Create a SQLite connection to the underlying database. Makes a new connection every time. If you need to configure the connection settings (e.g., add custom functions), override this method. """ # Make a new connection. The `sqlite3` module can't use # bytestring paths here on Python 3, so we need to # provide a `str` using `py3_path`. conn = sqlite3.connect( py3_path(self.path), timeout=self.timeout ) # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row return conn
def pil_resize(maxwidth, path_in, path_out=None): """Resize using Python Imaging Library (PIL). Return the output path of resized image. """ path_out = path_out or temp_file_for(path_in) from PIL import Image log.debug(u'artresizer: PIL resizing {0} to {1}', util.displayable_path(path_in), util.displayable_path(path_out)) try: im = Image.open(util.syspath(path_in)) size = maxwidth, maxwidth im.thumbnail(size, Image.ANTIALIAS) im.save(util.py3_path(path_out)) return path_out except IOError: log.error(u"PIL cannot create thumbnail for '{0}'", util.displayable_path(path_in)) return path_in
def _connection(self): """Get a SQLite connection object to the underlying database. One connection object is created per thread. """ thread_id = threading.current_thread().ident with self._shared_map_lock: if thread_id in self._connections: return self._connections[thread_id] else: # Make a new connection. The `sqlite3` module can't use # bytestring paths here on Python 3, so we need to # provide a `str` using `py3_path`. conn = sqlite3.connect( py3_path(self.path), timeout=self.timeout ) # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row self._connections[thread_id] = conn return conn
def _load_plugins(config): """Load the plugins specified in the configuration. """ paths = config['pluginpath'].as_str_seq(split=False) paths = [util.normpath(p) for p in paths] log.debug(u'plugin paths: {0}', util.displayable_path(paths)) # On Python 3, the search paths need to be unicode. paths = [util.py3_path(p) for p in paths] # Extend the `beetsplug` package to include the plugin paths. import beetsplug beetsplug.__path__ = paths + beetsplug.__path__ # For backwards compatibility, also support plugin paths that # *contain* a `beetsplug` package. sys.path += paths plugins.load_plugins(config['plugins'].as_str_seq()) plugins.send("pluginload") return plugins
def test_playlist_update(self): spl = SmartPlaylistPlugin() i = Mock(path=b'/tagada.mp3') i.evaluate_template.side_effect = \ lambda pl, _: pl.replace(b'$title', b'ta:ga:da').decode() lib = Mock() lib.replacements = CHAR_REPLACE lib.items.return_value = [i] lib.albums.return_value = [] q = Mock() a_q = Mock() pl = b'$title-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'] = py3_path(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, b'ta_ga_da-my_playlist_.m3u') 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 _create_connection(self): """Create a SQLite connection to the underlying database. Makes a new connection every time. If you need to configure the connection settings (e.g., add custom functions), override this method. """ # Make a new connection. The `sqlite3` module can't use # bytestring paths here on Python 3, so we need to # provide a `str` using `py3_path`. conn = sqlite3.connect( py3_path(self.path), timeout=self.timeout ) if self.supports_extensions: conn.enable_load_extension(True) # Load any extension that are already loaded for other connections. for path in self._extensions: conn.load_extension(path) # Access SELECT results like dictionaries. conn.row_factory = sqlite3.Row return conn
def fetch_image(self, candidate, extra): """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 extra['maxwidth']: candidate.url = ArtResizer.shared.proxy_url(extra['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 test_beetsdir_points_to_file_error(self): beetsdir = os.path.join(self.temp_dir, b'beetsfile') open(beetsdir, 'a').close() os.environ['BEETSDIR'] = util.py3_path(beetsdir) self.assertRaises(ConfigError, self.run_command, 'test')