def root_directory(self): if not self._media_dirs: return None elif len(self._media_dirs) == 1: uri = path.path_to_uri(self._media_dirs[0]["path"]) else: uri = "file:root" return models.Ref.directory(name="Files", uri=uri)
def root_directory(self): if not self._media_dirs: return None elif len(self._media_dirs) == 1: uri = path.path_to_uri(self._media_dirs[0]['path']) else: uri = 'file:root' return models.Ref.directory(name='Files', uri=uri)
def scan(self, paths): scanner = scan.Scanner() for path in paths: uri = path_to_uri(path) try: self.result[path] = scanner.scan(uri) except exceptions.ScannerError as error: self.errors[path] = error
def scan(self, paths): scanner = scan.Scanner() for path in paths: uri = path_lib.path_to_uri(path) key = uri[len('file://'):] try: self.result[key] = scanner.scan(uri) except exceptions.ScannerError as error: self.errors[key] = error
class BaseTest(unittest.TestCase): config = { 'audio': { 'buffer_time': None, 'mixer': 'fakemixer track_max_volume=65536', 'mixer_track': None, 'mixer_volume': None, 'output': 'testoutput', 'visualizer': None, } } uris = [ path.path_to_uri(path_to_data_dir('song1.wav')), path.path_to_uri(path_to_data_dir('song2.wav')) ] audio_class = audio.Audio def setUp(self): # noqa: N802 config = { 'audio': { 'buffer_time': None, 'mixer': 'foomixer', 'mixer_volume': None, 'output': 'testoutput', 'visualizer': None, }, 'proxy': { 'hostname': '', }, } self.song_uri = path.path_to_uri(path_to_data_dir('song1.wav')) self.audio = self.audio_class.start(config=config, mixer=None).proxy() def tearDown(self): # noqa pykka.ActorRegistry.stop_all() def possibly_trigger_fake_playback_error(self, uri): pass def possibly_trigger_fake_about_to_finish(self): pass
class BaseTest(unittest.TestCase): config = { "audio": { "buffer_time": None, "mixer": "fakemixer track_max_volume=65536", "mixer_track": None, "mixer_volume": None, "output": "testoutput", "visualizer": None, } } uris = [ path.path_to_uri(path_to_data_dir("song1.wav")), path.path_to_uri(path_to_data_dir("song2.wav")), ] audio_class = audio.Audio def setUp(self): # noqa: N802 config = { "audio": { "buffer_time": None, "mixer": "foomixer", "mixer_volume": None, "output": "testoutput", "visualizer": None, }, "proxy": {"hostname": ""}, } self.song_uri = path.path_to_uri(path_to_data_dir("song1.wav")) self.audio = self.audio_class.start(config=config, mixer=None).proxy() def tearDown(self): # noqa pykka.ActorRegistry.stop_all() def possibly_trigger_fake_playback_error(self, uri): pass def possibly_trigger_fake_about_to_finish(self): pass
def setUp(self): # noqa: N802 config = { "audio": { "buffer_time": None, "mixer": "foomixer", "mixer_volume": None, "output": "testoutput", "visualizer": None, }, "proxy": {"hostname": ""}, } self.song_uri = path.path_to_uri(path_to_data_dir("song1.wav")) self.audio = self.audio_class.start(config=config, mixer=None).proxy()
def browse(self, uri): logger.debug("Browsing files at: %s", uri) result = [] local_path = path.uri_to_path(uri) if str(local_path) == "root": return list(self._get_media_dirs_refs()) if not self._is_in_basedir(local_path): logger.warning( "Rejected attempt to browse path (%s) outside dirs defined " "in file/media_dirs config.", uri, ) return [] if path.uri_to_path(uri).is_file(): logger.error("Rejected attempt to browse file (%s)", uri) return [] for dir_entry in local_path.iterdir(): child_path = dir_entry.resolve() uri = path.path_to_uri(child_path) if not self._show_dotfiles and dir_entry.name.startswith("."): continue if (self._excluded_file_extensions and dir_entry.suffix in self._excluded_file_extensions): continue if child_path.is_symlink() and not self._follow_symlinks: logger.debug("Ignoring symlink: %s", uri) continue if not self._is_in_basedir(child_path): logger.debug("Ignoring symlink to outside base dir: %s", uri) continue if child_path.is_dir(): result.append( models.Ref.directory(name=dir_entry.name, uri=uri)) elif child_path.is_file(): result.append(models.Ref.track(name=dir_entry.name, uri=uri)) def order(item): return (item.type != models.Ref.DIRECTORY, item.name) result.sort(key=order) return result
def setUp(self): # noqa: N802 config = { 'audio': { 'mixer': 'foomixer', 'mixer_volume': None, 'output': 'testoutput', 'visualizer': None, }, 'proxy': { 'hostname': '', }, } self.song_uri = path.path_to_uri(path_to_data_dir('song1.wav')) self.audio = self.audio_class.start(config=config, mixer=None).proxy()
def browse(self, uri): logger.debug('Browsing files at: %s', uri) result = [] local_path = path.uri_to_path(uri) if local_path == 'root': return list(self._get_media_dirs_refs()) if not self._is_in_basedir(os.path.realpath(local_path)): logger.warning( 'Rejected attempt to browse path (%s) outside dirs defined ' 'in file/media_dirs config.', uri) return [] for dir_entry in os.listdir(local_path): child_path = os.path.join(local_path, dir_entry) uri = path.path_to_uri(child_path) if not self._show_dotfiles and dir_entry.startswith(b'.'): continue if (self._excluded_file_extensions and dir_entry.endswith(self._excluded_file_extensions)): continue if os.path.islink(child_path) and not self._follow_symlinks: logger.debug('Ignoring symlink: %s', uri) continue if not self._is_in_basedir(os.path.realpath(child_path)): logger.debug('Ignoring symlink to outside base dir: %s', uri) continue name = dir_entry.decode(FS_ENCODING, 'replace') if os.path.isdir(child_path): result.append(models.Ref.directory(name=name, uri=uri)) elif os.path.isfile(child_path): result.append(models.Ref.track(name=name, uri=uri)) def order(item): return (item.type != models.Ref.DIRECTORY, item.name) result.sort(key=order) return result
def test_space_in_path(self): result = path.path_to_uri("/tmp/test this") assert result == "file:///tmp/test%20this"
def track_uri(): return path.path_to_uri(path_to_data_dir("song1.wav"))
timeout = timeout_ms - (int(time.time() * 1000) - start) raise exceptions.ScannerError('Timeout after %dms' % timeout_ms) if __name__ == '__main__': import os import sys from mopidy.internal import path logging.basicConfig(format='%(asctime)-15s %(levelname)s %(message)s', level=log.TRACE_LOG_LEVEL) scanner = Scanner(5000) for uri in sys.argv[1:]: if not Gst.uri_is_valid(uri): uri = path.path_to_uri(os.path.abspath(uri)) try: result = scanner.scan(uri) for key in ('uri', 'mime', 'duration', 'playable', 'seekable'): print('%-20s %s' % (key, getattr(result, key))) print('tags') for tag, value in result.tags.items(): line = '%-20s %s' % (tag, value) if len(line) > 77: line = line[:77] + '...' print(line) except exceptions.ScannerError as error: print('%s: %s' % (uri, error))
def path_to_file_uri(abspath): """Convert absolute path to file URI.""" # Re-export internal method for use by Mopidy-Local-* extensions. return path.path_to_uri(abspath)
def track_uri(): return path.path_to_uri(path_to_data_dir('song1.wav'))
def test_utf8_in_path(self): result = path.path_to_uri('/tmp/æøå'.encode('utf-8')) self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
def test_space_in_path(self): result = path.path_to_uri('/tmp/test this') self.assertEqual(result, 'file:///tmp/test%20this')
def test_unicode_in_path(self): result = path.path_to_uri("/tmp/æøå") assert result == "file:///tmp/%C3%A6%C3%B8%C3%A5"
def run(self, args, config): media_dir = config['local']['media_dir'] scan_timeout = config['local']['scan_timeout'] flush_threshold = config['local']['scan_flush_threshold'] excluded_file_extensions = config['local']['excluded_file_extensions'] excluded_file_extensions = tuple( bytes(file_ext.lower()) for file_ext in excluded_file_extensions) library = _get_library(args, config) if library is None: return 1 file_mtimes, file_errors = path.find_mtimes( media_dir, follow=config['local']['scan_follow_symlinks']) logger.info('Found %d files in media_dir.', len(file_mtimes)) if file_errors: logger.warning('Encountered %d errors while scanning media_dir.', len(file_errors)) for name in file_errors: logger.debug('Scan error %r for %r', file_errors[name], name) num_tracks = library.load() logger.info('Checking %d tracks from library.', num_tracks) uris_to_update = set() uris_to_remove = set() uris_in_library = set() for track in library.begin(): abspath = translator.local_track_uri_to_path(track.uri, media_dir) mtime = file_mtimes.get(abspath) if mtime is None: logger.debug('Missing file %s', track.uri) uris_to_remove.add(track.uri) elif mtime > track.last_modified or args.force: uris_to_update.add(track.uri) uris_in_library.add(track.uri) logger.info('Removing %d missing tracks.', len(uris_to_remove)) for uri in uris_to_remove: library.remove(uri) for abspath in file_mtimes: relpath = os.path.relpath(abspath, media_dir) uri = translator.path_to_local_track_uri(relpath) if b'/.' in relpath: logger.debug('Skipped %s: Hidden directory/file.', uri) elif relpath.lower().endswith(excluded_file_extensions): logger.debug('Skipped %s: File extension excluded.', uri) elif uri not in uris_in_library: uris_to_update.add(uri) logger.info('Found %d tracks which need to be updated.', len(uris_to_update)) logger.info('Scanning...') uris_to_update = sorted(uris_to_update, key=lambda v: v.lower()) uris_to_update = uris_to_update[:args.limit] scanner = scan.Scanner(scan_timeout) progress = _Progress(flush_threshold, len(uris_to_update)) for uri in uris_to_update: try: relpath = translator.local_track_uri_to_path(uri, media_dir) file_uri = path.path_to_uri(os.path.join(media_dir, relpath)) result = scanner.scan(file_uri) tags, duration = result.tags, result.duration if not result.playable: logger.warning('Failed %s: No audio found in file.', uri) elif duration < MIN_DURATION_MS: logger.warning('Failed %s: Track shorter than %dms', uri, MIN_DURATION_MS) else: mtime = file_mtimes.get(os.path.join(media_dir, relpath)) track = utils.convert_tags_to_track(tags).replace( uri=uri, length=duration, last_modified=mtime) if library.add_supports_tags_and_duration: library.add(track, tags=tags, duration=duration) else: library.add(track) logger.debug('Added %s', track.uri) except exceptions.ScannerError as error: logger.warning('Failed %s: %s', uri, error) if progress.increment(): progress.log() if library.flush(): logger.debug('Progress flushed.') progress.log() library.close() logger.info('Done scanning.') return 0
def test_simple_path(self): result = path.path_to_uri('/etc/fstab') self.assertEqual(result, 'file:///etc/fstab')
def test_lookup_respects_blacklist_globbing(audio, config, track_uri): blacklist_glob = path.path_to_uri(path_to_data_dir('')) + '*' config['stream']['metadata_blacklist'].append(blacklist_glob) backend = actor.StreamBackend(audio=audio, config=config) assert backend.library.lookup(track_uri) == [Track(uri=track_uri)]
def test_unicode_in_path(self): result = path.path_to_uri('/tmp/æøå') self.assertEqual(result, 'file:///tmp/%C3%A6%C3%B8%C3%A5')
import os import tempfile import unittest from mopidy.internal import path from mopidy.m3u import translator from mopidy.models import Track from tests import path_to_data_dir data_dir = path_to_data_dir('') song1_path = path_to_data_dir('song1.mp3') song2_path = path_to_data_dir('song2.mp3') song3_path = path_to_data_dir('φοο.mp3') encoded_path = path_to_data_dir('æøå.mp3') song1_uri = path.path_to_uri(song1_path) song2_uri = path.path_to_uri(song2_path) song3_uri = path.path_to_uri(song3_path) encoded_uri = path.path_to_uri(encoded_path) song1_track = Track(uri=song1_uri) song2_track = Track(uri=song2_uri) song3_track = Track(uri=song3_uri) encoded_track = Track(uri=encoded_uri) song1_ext_track = song1_track.replace(name='song1') song2_ext_track = song2_track.replace(name='song2', length=60000) encoded_ext_track = encoded_track.replace(name='æøå') # FIXME use mock instead of tempfile.NamedTemporaryFile class M3UToUriTest(unittest.TestCase):
def test_latin1_in_path(self): result = path.path_to_uri('/tmp/æøå'.encode('latin-1')) self.assertEqual(result, 'file:///tmp/%E6%F8%E5')
def test_lookup_respects_blacklist_globbing(backend, track_uri): blacklist = [path.path_to_uri(path_to_data_dir('')) + '*'] library = actor.StreamLibraryProvider(backend, blacklist) assert library.lookup(track_uri) == [Track(uri=track_uri)]
timeout = timeout_ms - (int(time.time() * 1000) - start) # workaround for https://bugzilla.gnome.org/show_bug.cgi?id=763553: # if we got what we want then stop playing (and wait for ASYNC_DONE) if tags and duration is not None: pipeline.set_state(Gst.State.PAUSED) raise exceptions.ScannerError('Timeout after %dms' % timeout_ms) if __name__ == '__main__': import os import sys from mopidy.internal import path scanner = Scanner(5000) for uri in sys.argv[1:]: if not Gst.uri_is_valid(uri): uri = path.path_to_uri(os.path.abspath(uri)) try: result = scanner.scan(uri) for key in ('uri', 'mime', 'duration', 'playable', 'seekable'): print('%-20s %s' % (key, getattr(result, key))) print('tags') for tag, value in result.tags.items(): print('%-20s %s' % (tag, value)) except exceptions.ScannerError as error: print('%s: %s' % (uri, error))
def _get_media_dirs_refs(self): for media_dir in self._media_dirs: yield models.Ref.directory( name=media_dir['name'], uri=path.path_to_uri(media_dir['path']))
def _get_media_dirs_refs(self): for media_dir in self._media_dirs: yield models.Ref.directory(name=media_dir['name'], uri=path.path_to_uri(media_dir['path']))
def parse_m3u(file_path, media_dir=None): r""" Convert M3U file list to list of tracks Example M3U data:: # This is a comment Alternative\Band - Song.mp3 Classical\Other Band - New Song.mp3 Stuff.mp3 D:\More Music\Foo.mp3 http://www.example.com:8000/Listen.pls http://www.example.com/~user/Mine.mp3 Example extended M3U data:: #EXTM3U #EXTINF:123, Sample artist - Sample title Sample.mp3 #EXTINF:321,Example Artist - Example title Greatest Hits\Example.ogg #EXTINF:-1,Radio XMP http://mp3stream.example.com:8000/ - Relative paths of songs should be with respect to location of M3U. - Paths are normally platform specific. - Lines starting with # are ignored, except for extended M3U directives. - Track.name and Track.length are set from extended M3U directives. - m3u files are latin-1. - m3u8 files are utf-8 """ # TODO: uris as bytes file_encoding = 'utf-8' if file_path.endswith(b'.m3u8') else 'latin1' tracks = [] try: with codecs.open(file_path, 'rb', file_encoding, 'replace') as m3u: contents = m3u.readlines() except IOError as error: logger.warning('Couldn\'t open m3u: %s', encoding.locale_decode(error)) return tracks if not contents: return tracks # Strip newlines left by codecs contents = [line.strip() for line in contents] extended = contents[0].startswith('#EXTM3U') track = Track() for line in contents: if line.startswith('#'): if extended and line.startswith('#EXTINF'): track = m3u_extinf_to_track(line) continue if urlparse.urlsplit(line).scheme: tracks.append(track.replace(uri=line)) elif os.path.normpath(line) == os.path.abspath(line): uri = path.path_to_uri(line) tracks.append(track.replace(uri=uri)) elif media_dir is not None: uri = path.path_to_uri(os.path.join(media_dir, line)) tracks.append(track.replace(uri=uri)) track = Track() return tracks
def run(self, args, config): media_dir = config['local']['media_dir'] scan_timeout = config['local']['scan_timeout'] flush_threshold = config['local']['scan_flush_threshold'] excluded_file_extensions = config['local']['excluded_file_extensions'] excluded_file_extensions = tuple( bytes(file_ext.lower()) for file_ext in excluded_file_extensions) library = _get_library(args, config) if library is None: return 1 file_mtimes, file_errors = path.find_mtimes( media_dir, follow=config['local']['scan_follow_symlinks']) logger.info('Found %d files in media_dir.', len(file_mtimes)) if file_errors: logger.warning('Encountered %d errors while scanning media_dir.', len(file_errors)) for name in file_errors: logger.debug('Scan error %r for %r', file_errors[name], name) num_tracks = library.load() logger.info('Checking %d tracks from library.', num_tracks) uris_to_update = set() uris_to_remove = set() uris_in_library = set() for track in library.begin(): abspath = translator.local_track_uri_to_path(track.uri, media_dir) mtime = file_mtimes.get(abspath) if mtime is None: logger.debug('Missing file %s', track.uri) uris_to_remove.add(track.uri) elif mtime > track.last_modified or args.force: uris_to_update.add(track.uri) uris_in_library.add(track.uri) logger.info('Removing %d missing tracks.', len(uris_to_remove)) for uri in uris_to_remove: library.remove(uri) for abspath in file_mtimes: relpath = os.path.relpath(abspath, media_dir) uri = translator.path_to_local_track_uri(relpath) if b'/.' in relpath or relpath.startswith(b'.'): logger.debug('Skipped %s: Hidden directory/file.', uri) elif relpath.lower().endswith(excluded_file_extensions): logger.debug('Skipped %s: File extension excluded.', uri) elif uri not in uris_in_library: uris_to_update.add(uri) logger.info( 'Found %d tracks which need to be updated.', len(uris_to_update)) logger.info('Scanning...') uris_to_update = sorted(uris_to_update, key=lambda v: v.lower()) uris_to_update = uris_to_update[:args.limit] scanner = scan.Scanner(scan_timeout) progress = _Progress(flush_threshold, len(uris_to_update)) for uri in uris_to_update: try: relpath = translator.local_track_uri_to_path(uri, media_dir) file_uri = path.path_to_uri(os.path.join(media_dir, relpath)) result = scanner.scan(file_uri) if not result.playable: logger.warning('Failed %s: No audio found in file.', uri) elif result.duration < MIN_DURATION_MS: logger.warning('Failed %s: Track shorter than %dms', uri, MIN_DURATION_MS) else: mtime = file_mtimes.get(os.path.join(media_dir, relpath)) track = tags.convert_tags_to_track(result.tags).replace( uri=uri, length=result.duration, last_modified=mtime) if library.add_supports_tags_and_duration: library.add( track, tags=result.tags, duration=result.duration) else: library.add(track) logger.debug('Added %s', track.uri) except exceptions.ScannerError as error: logger.warning('Failed %s: %s', uri, error) if progress.increment(): progress.log() if library.flush(): logger.debug('Progress flushed.') progress.log() library.close() logger.info('Done scanning.') return 0
def test_simple_path(self): result = path.path_to_uri("/etc/fstab") assert result == "file:///etc/fstab"
def test_lookup_respects_blacklist_globbing(backend, track_uri): blacklist = [path.path_to_uri(path_to_data_dir("")) + "*"] library = actor.StreamLibraryProvider(backend, blacklist) assert library.lookup(track_uri) == [Track(uri=track_uri)]