def lookup(self, uri): logger.debug('Looking up file URI: %s', uri) local_path = path.uri_to_path(uri) try: result = self._scanner.scan(uri) track = utils.convert_tags_to_track(result.tags).copy( uri=uri, length=result.duration) except exceptions.ScannerError as e: logger.warning('Failed looking up %s: %s', uri, e) track = models.Track(uri=uri) if not track.name: filename = os.path.basename(local_path) name = urllib2.unquote(filename).decode(FS_ENCODING, 'replace') track = track.copy(name=name) return [track]
def lookupTrack(self, trackId, uri): if self.trackCache[uri]: return [self.trackCache[uri]] trackJSON = self.appleMusicClient.user_get_song(trackId) trackData = trackJSON['data'] if len(trackData) != 1: logger.error("Could not load track %s", trackId) return [] name = trackJSON['attributes']['name'] length = trackJSON['attributes']['durationInMillis'] track = models.Track(uri=uri, name=name, album=albumRef, length=length) self.trackCache[uri] = track return [track]
def trackRelationshipsToRefs(self, relationships, albumRef): tracks = [] if not relationships['tracks']: return [] for trackJSON in relationships['tracks']['data']: uri = TRACK_PREFIX + trackJSON['id'] name = trackJSON['attributes']['name'] artists = albumRef.artists length = trackJSON['attributes']['durationInMillis'] # track = models.Track(uri=uri, name=name, artists=artists, album=albumRef, length=length) track = models.Track(uri=uri, name=name, album=albumRef, length=length) tracks.append(track) return tracks
def create_track(self, track): """Create track from Jellyfin API track dict. :param track: Track from Jellyfin API :type track: dict :returns: Track :rtype: mopidy.models.Track """ # TODO: add more metadata return models.Track( uri='jellyfin:track:{}'.format( track.get('Id') ), name=track.get('Name'), track_no=track.get('IndexNumber'), disc_no=track.get('ParentIndexNumber'), genre=track.get('Genre'), artists=self.create_artists(track), album=self.create_album(track), length=self.ticks_to_milliseconds(track.get('RunTimeTicks')) )
def setUp(self): self.config = { 'scrobbler': { 'lastfm_username': '******', 'lastfm_password': '******', 'librefm_username': '', 'librefm_password': '', } } self.frontend = frontend_lib.ScrobblerFrontend(self.config, mock.sentinel.core) self.frontend.lastfm = mock.Mock(spec=pylast.LastFMNetwork) self.frontend.networks['Last.fm'] = self.frontend.lastfm self.artists = [models.Artist(name='ABC'), models.Artist(name='XYZ')] self.track = models.Track(name='One Two Three', artists=self.artists, album=models.Album(name='The Collection'), track_no=3, length=180432, musicbrainz_id='123-456')
def test_track_playback_started_updates_now_playing(self, pylast_mock): self.frontend.lastfm = mock.Mock(spec=pylast.LastFMNetwork) artists = [models.Artist(name='ABC'), models.Artist(name='XYZ')] album = models.Album(name='The Collection') track = models.Track( name='One Two Three', artists=artists, album=album, track_no=3, length=180432, musicbrainz_id='123-456') tl_track = models.TlTrack(track=track, tlid=17) self.frontend.track_playback_started(tl_track) self.frontend.lastfm.update_now_playing.assert_called_with( 'ABC, XYZ', 'One Two Three', duration='180', album='The Collection', track_number='3', mbid='123-456')
def web_to_track(web_track, bitrate=None, *, check_playable=True): ref = web_to_track_ref(web_track, check_playable=check_playable) if ref is None: return artists = [ web_to_artist(web_artist) for web_artist in web_track.get("artists", []) ] artists = [a for a in artists if a] album = web_to_album(web_track.get("album", {})) return models.Track( uri=ref.uri, name=ref.name, artists=artists, album=album, length=web_track.get("duration_ms"), disc_no=web_track.get("disc_number"), track_no=web_track.get("track_number"), bitrate=bitrate, )
def to_track(qobuz_track, qobuz_album=None): if qobuz_track is None: return if qobuz_album is None: # Make an API call to request the album qobuz_album = qobuz_track.album album = to_album(qobuz_album) artist = to_artist(qobuz_track_artist_lookup(qobuz_track._performer_id)) if artist is None or album is None: return return models.Track( uri="qobuz:track:{0}:{1}:{2}".format(qobuz_album.artist.id, qobuz_album.id, qobuz_track.id), name=qobuz_track.title, artists=[artist], album=album, length=qobuz_track.duration * 1000, # s -> ms disc_no=qobuz_track.media_number, track_no=qobuz_track.track_number, )
def web_to_track(web_track, bitrate=None): ref = web_to_track_ref(web_track) if ref is None: return artists = [ web_to_artist(web_artist) for web_artist in web_track.get("artists", []) ] artists = [a for a in artists if a] title, artists = clean_track_info(ref.name, artists) album = web_to_album(web_track.get("album", {})) return models.Track( uri=ref.uri, name=title, artists=artists, album=album, length=web_track.get("duration_ms"), disc_no=web_track.get("disc_number"), track_no=web_track.get("track_number"), bitrate=bitrate, )
def test_track_playback_started_updates_now_playing(pylast_mock, frontend): frontend.lastfm = mock.Mock(spec=pylast.LastFMNetwork) artists = [models.Artist(name="ABC"), models.Artist(name="XYZ")] album = models.Album(name="The Collection") track = models.Track( name="One Two Three", artists=artists, album=album, track_no=3, length=180432, musicbrainz_id="123-456", ) tl_track = models.TlTrack(track=track, tlid=17) frontend.track_playback_started(tl_track) frontend.lastfm.update_now_playing.assert_called_with( "ABC, XYZ", "One Two Three", duration="180", album="The Collection", track_number="3", mbid="123-456", )
class BaseTest(unittest.TestCase): tracks = [ models.Track(uri='pandora:track:id_mock:token_mock1', length=40000), # Regular track models.Track(uri='pandora:track:id_mock:token_mock2', length=40000), # Regular track models.Track(uri='pandora:ad:id_mock:token_mock3', length=40000), # Advertisement models.Track(uri='mock:track:id_mock:token_mock4', length=40000), # Not a pandora track models.Track(uri='pandora:track:id_mock_other:token_mock5', length=40000), # Different station models.Track(uri='pandora:track:id_mock:token_mock6', length=None), # No duration ] uris = [ 'pandora:track:id_mock:token_mock1', 'pandora:track:id_mock:token_mock2', 'pandora:ad:id_mock:token_mock3', 'mock:track:id_mock:token_mock4', 'pandora:track:id_mock_other:token_mock5', 'pandora:track:id_mock:token_mock6' ] def setUp(self): config = {'core': {'max_tracklist_length': 10000}} self.audio = dummy_audio.create_proxy(DummyAudio) self.backend = dummy_backend.create_proxy(DummyPandoraBackend, audio=self.audio) self.non_pandora_backend = dummy_backend.create_proxy(DummyBackend, audio=self.audio) self.core = core.Core.start( config, audio=self.audio, backends=[self.backend, self.non_pandora_backend]).proxy() def lookup(uris): result = {uri: [] for uri in uris} for track in self.tracks: if track.uri in result: result[track.uri].append(track) return result self.core.library.lookup = lookup self.tl_tracks = self.core.tracklist.add(uris=self.uris).get() self.events = Queue.Queue() def send(cls, event, **kwargs): self.events.put((cls, event, kwargs)) self.patcher = mock.patch('mopidy.listener.send') self.send_mock = self.patcher.start() self.send_mock.side_effect = send # TODO: Remove this patcher once Mopidy 1.2 has been released. try: self.core_patcher = mock.patch('mopidy.listener.send_async') self.core_send_mock = self.core_patcher.start() self.core_send_mock.side_effect = send except AttributeError: # Mopidy > 1.1 no longer has mopidy.listener.send_async pass self.actor_register = [self.backend, self.core, self.audio] def tearDown(self): pykka.ActorRegistry.stop_all() mock.patch.stopall() def replay_events(self, until=None): while True: try: e = self.events.get(timeout=0.1) cls, event, kwargs = e if event == until: break for actor in self.actor_register: if isinstance(actor, pykka.ActorProxy): if isinstance(actor._actor, cls): actor.on_event(event, **kwargs).get() else: if isinstance(actor, cls): actor.on_event(event, **kwargs) except Queue.Empty: # All events replayed. break
def test_change_track_aborts_if_no_track_uri(provider): track = models.Track() assert provider.change_track(track) is False
def search(self, query): """Search Jellyfin for a term. :param query: Search query :type query: dict :returns: Search results :rtype: mopidy.models.SearchResult """ logger.debug('Searching in Jellyfin for {}'.format(query)) # something to store the results in data = [] tracks = [] albums = [] artists = [] for itemtype, term in query.items(): for item in term: data.extend(self._get_search(itemtype, item)) # walk through all items and create stuff for item in data: if item.get('Type') == 'Audio': track_artists = [ models.Artist(name=artist) for artist in item.get('Artists') ] tracks.append( models.Track(uri='jellyfin:track:{}'.format( item.get('ItemId')), track_no=item.get('IndexNumber'), name=item.get('Name'), artists=track_artists, album=models.Album(name=item.get('Album'), artists=track_artists))) elif item.get('Type') == 'MusicAlbum': album_artists = [ models.Artist(name=artist) for artist in item.get('Artists') ] albums.append( models.Album(uri='jellyfin:album:{}'.format( item.get('ItemId')), name=item.get('Name'), artists=album_artists)) elif item.get('Type') == 'MusicArtist': artists.append( models.Artist(uri='jellyfin:artist:{}'.format( item.get('ItemId')), name=item.get('Name'))) return models.SearchResult(uri='jellyfin:search', tracks=tracks, artists=artists, albums=albums)
def playlist(path, items=[], mtime=None): return models.Playlist( uri=path_to_uri(path), name=name_from_path(path), tracks=[models.Track(uri=item.uri, name=item.name) for item in items], last_modified=(int(mtime * 1000) if mtime else None))
_MODELS = { 'podcast': lambda item: models.Album( uri=uri(item['feedUrl']), name=item.get('collectionName', item['trackName']), artists=artists(item), date=(item.get('releaseDate', '').partition('T')[0] or None), num_tracks=item.get('trackCount') ), 'podcast-episode': lambda item: models.Track( uri=uri(item['feedUrl'], item['episodeGuid']), name=item['trackName'], album=models.Album( uri=uri(item['feedUrl']), name=item['collectionName'] ), artists=artists(item), date=(item.get('releaseDate', '').partition('T')[0] or None), genre=item.get('primaryGenreName'), length=item.get('trackTimeMillis'), comment=item.get('description') ) } _REFS = { 'podcast': lambda item: models.Ref.album( uri=uri(item['feedUrl']), name=item.get('collectionName', item['trackName']) ), 'podcast-episode': lambda item: models.Ref.track( uri=uri(item['feedUrl'], item['episodeGuid']),
def lookup(self, uri): pandora_uri = PandoraUri.factory(uri) logger.info("Looking up Pandora {} {}...".format( pandora_uri.uri_type, pandora_uri.uri)) if isinstance(pandora_uri, SearchUri): # Create the station first so that it can be browsed. station_uri = self._create_station_for_token(pandora_uri.token) track = self._browse_tracks(station_uri.uri)[0] # Recursive call to look up first track in station that was searched for. return self.lookup(track.uri) track_kwargs = {"uri": uri} (album_kwargs, artist_kwargs) = {}, {} if isinstance(pandora_uri, TrackUri): try: track = self.lookup_pandora_track(uri) except KeyError: logger.exception( "Failed to lookup Pandora URI '{}'.".format(uri)) return [] else: # TODO: Album.images has been deprecated in Mopidy 1.2. Remove this code when all frontends have been # updated to make use of the newer LibraryController.get_images() images = self.get_images([uri])[uri] if len(images) > 0: album_kwargs = {"images": [image.uri for image in images]} if isinstance(pandora_uri, AdItemUri): track_kwargs["name"] = "Advertisement" if not track.title: track.title = "(Title not specified)" artist_kwargs["name"] = track.title if not track.company_name: track.company_name = "(Company name not specified)" album_kwargs["name"] = track.company_name else: track_kwargs["name"] = track.song_name track_kwargs["length"] = track.track_length * 1000 try: track_kwargs["bitrate"] = int(track.bitrate) except TypeError: # Bitrate not specified for this stream, ignore. pass artist_kwargs["name"] = track.artist_name album_kwargs["name"] = track.album_name elif isinstance(pandora_uri, StationUri): station = self.backend.api.get_station(pandora_uri.station_id) album_kwargs = {"images": [station.art_url]} track_kwargs["name"] = station.name artist_kwargs["name"] = "Pandora Station" album_kwargs["name"] = ", ".join(station.genre) else: raise ValueError( "Unexpected type to perform Pandora track lookup: {}.".format( pandora_uri.uri_type)) artist_kwargs[ "uri"] = uri # Artist lookups should just point back to the track itself. track_kwargs["artists"] = [models.Artist(**artist_kwargs)] album_kwargs[ "uri"] = uri # Album lookups should just point back to the track itself. track_kwargs["album"] = models.Album(**album_kwargs) return [models.Track(**track_kwargs)]
}, { "name": "track01.mp3", "title": "Track #1", "format": "VBR MP3", "track": "01", }, ], "metadata": {"identifier": "album", "title": "Album", "mediatype": "audio"}, } ALBUM = models.Album(name="Album", uri="internetarchive:album") TRACK1 = models.Track( album=ALBUM, name="Track #1", track_no=1, uri="internetarchive:album#track01.mp3", ) TRACK2 = models.Track( album=ALBUM, name="Track #2", track_no=2, uri="internetarchive:album#track02.mp3", ) def test_lookup_root(library, client_mock): assert library.lookup("internetarchive:") == []
def test_lookup_item(backend, items): with mock.patch.object(backend, 'client') as m: m.properties.return_value = Future.fromvalue(items[0]) assert backend.library.lookup(items[0]['URI']) == [ models.Track(name='Track #1', uri='dleyna://media/11') ]
def exact_search(self, query): # Variable prep tracks = [] raw_artist = '' artist_ref = [] raw_albums = [] # Check query for artist name if 'artist' in query: raw_artist = query.get('artist') elif 'albumartist' in query: raw_artist = query.get('albumartist') cleaned_artist = unidecode(raw_artist[0]).lower() # Use if query has artist name if raw_artist: # URL encode artist string artist = quote(raw_artist[0].encode('utf8')).replace('/', '-') artist_ref = [models.Artist(name=raw_artist[0])] url = self.api_url('/Artists/{}?UserId={}'.format( artist, self.user_id)) artist_data = self.http.get(url) artist_id = artist_data.get('Id') # Get album list if self.albumartistsort: album_url = self.api_url( '/Items?IncludeItemTypes=MusicAlbum&Recursive=true&' 'AlbumArtistIds={}&UserId={}&'.format( artist_id, self.user_id)) else: album_url = self.api_url( '/Items?IncludeItemTypes=MusicAlbum&Recursive=true&' 'ArtistIds={}&UserId={}&'.format(artist_id, self.user_id)) album_data = self.http.get(album_url) if album_data: raw_albums = album_data.get('Items') if self.albumartistsort: track_url = self.api_url( '/Items?IncludeItemTypes=Audio&Recursive=true&' 'AlbumArtistIds={}&UserId={}&'.format( artist_id, self.user_id)) else: track_url = self.api_url( '/Items?IncludeItemTypes=Audio&Recursive=true&' 'ArtistIds={}&UserId={}&'.format(artist_id, self.user_id)) track_data = self.http.get(track_url) if track_data: # If the query has an album, only match those tracks if query.get('album'): tracks = [ models.Track( uri='jellyfin:track:{}'.format(track.get('Id')), track_no=track.get('IndexNumber'), disc_no=track.get('ParentIndexNumber'), name=track.get('Name'), artists=artist_ref, album=models.Album(name=track.get('Album'), artists=artist_ref)) for track in track_data.get('Items') if track.get('Album') == query.get('album')[0] ] # Otherwise return all tracks else: tracks = [ models.Track( uri='jellyfin:track:{}'.format(track.get('Id')), track_no=track.get('IndexNumber'), disc_no=track.get('ParentIndexNumber'), name=track.get('Name'), artists=artist_ref, album=models.Album(name=track.get('Album'), artists=artist_ref)) for track in track_data.get('Items') ] # Use if query only has an album name elif 'album' in query: album_name = query.get('album')[0] album = quote(album_name.encode('utf8')) if raw_albums: album_id = [ i.get('Id') for i in raw_albums if i.get('Name') == unidecode(album_name) ][0] else: url = self.api_url( '/Items?IncludeItemTypes=Audio&Recursive=true&' 'Albums={}&UserId={}&'.format(album, self.user_id)) album_data = self.http.get(url).get('Items') album_id = album_data[0].get('AlbumId') tracks = self.get_search_tracks(artist_ref, album_id) return models.SearchResult( uri='jellyfin:search', tracks=tracks, artists=artist_ref, )
def test_lookup_item(backend, items): with mock.patch.object(backend, "client") as m: m.properties.return_value = Future.fromvalue(items[0]) assert backend.library.lookup(items[0]["URI"]) == [ models.Track(name="Track #1", uri="dleyna://media/11") ]
def test_change_track_aborts_on_spotify_error(session_mock, provider): track = models.Track(uri="spotfy:track:test") session_mock.get_track.side_effect = spotify.Error assert provider.change_track(track) is False
date='1972-09-16')), ({ 'kind': 'podcast-episode', 'feedUrl': 'http://example.com/feed', 'collectionName': 'bar', 'episodeGuid': '1', 'trackName': 'foo', 'artistName': 'baz', 'primaryGenreName': 'Music', 'releaseDate': '1972-09-16T16:55:00Z', 'trackTimeMillis': 3600 }, models.Track(uri='podcast+http://example.com/feed#1', name='foo', album=models.Album(uri='podcast+http://example.com/feed', name='bar'), artists=[models.Artist(name='baz')], date='1972-09-16', genre='Music', length=3600))]) def test_model(item, expected): assert translator.model(item) == expected @pytest.mark.parametrize('item', [{}, { 'kind': 'song', 'trackName': 'foo' }, { 'kind': 'podcast', 'trackName': 'foo' }, { 'kind': 'podcast-episode',
def test_change_track_skips_if_no_track_uri(provider): track = models.Track(uri=None) provider.change_pandora_track = mock.PropertyMock() assert provider.change_track(track) is False assert not provider.change_pandora_track.called
def lookup(self, uri): logger.debug(u'looking up uri = %s' % uri) track = models.Track(uri=uri) return [track]