Ejemplo n.º 1
0
    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]
Ejemplo n.º 2
0
    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]
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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'))
        )
Ejemplo n.º 5
0
    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')
Ejemplo n.º 6
0
    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')
Ejemplo n.º 7
0
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,
    )
Ejemplo n.º 8
0
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,
    )
Ejemplo n.º 9
0
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,
    )
Ejemplo n.º 10
0
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",
    )
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
def test_change_track_aborts_if_no_track_uri(provider):
    track = models.Track()

    assert provider.change_track(track) is False
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
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']),
Ejemplo n.º 16
0
    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)]
Ejemplo n.º 17
0
        },
        {
            "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:") == []

Ejemplo n.º 18
0
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')
        ]
Ejemplo n.º 19
0
    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,
        )
Ejemplo n.º 20
0
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")
        ]
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
                   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',
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
 def lookup(self, uri):
     logger.debug(u'looking up uri = %s' % uri)
     track = models.Track(uri=uri)
     return [track]