コード例 #1
0
    def start(self):
        """ Start the Spotify client and HTTP server """
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return False

        can_start = self.start_lock.acquire(blocking=False)
        try:
            # If there is a start in process, just wait until it finishes, but don't raise another one
            if not can_start:
                Log.Debug(
                    "Start already in progress, waiting it finishes to return")
                self.start_lock.acquire()
            else:
                Log.Debug("Start triggered, entering private section")
                self.start_marker.clear()

                if self.client:
                    self.client.restart(self.username, self.password)
                else:
                    self.client = SpotifyClient(self.username, self.password)

                self.last_track_uri = None
                self.last_track_object = None
                Dict['play_count'] = 0
                Dict['last_restart'] = time.time()
                self.start_marker.set()
                Log.Debug("Start finished, leaving private section")
        finally:
            self.start_lock.release()

        return self.client and self.client.is_logged_in()
コード例 #2
0
ファイル: host.py プロジェクト: jetmets/Spotify2.bundle
    def start(self):
        self.messages = []

        if not self.username or not self.password:
            self.messages.append((logging.ERROR, 'Username or Password not entered'))
            Log.Error('Username or Password not entered')
            return

        Log.Debug('bundle_path: "%s"', self.bundle_path)

        if not self.client:
            self.client = SpotifyClient(self)

        # Start server (if 'proxy_tracks' is enabled)
        if not self.server and self.proxy_tracks:
            self.server = Server(self)
            self.server.start()

        # Stop server if 'proxy_tracks' has been disabled
        if self.server and not self.proxy_tracks:
            self.server.stop()
            self.server = None

        # Update server preferences
        if self.server:
            self.server.supports_ranges = PREF_SS_RANGES.get(Prefs['proxy_ranges'], True)

        # Update reference on SpotifyClient
        self.client.server = self.server

        # start/restart the client
        self.client.start()
コード例 #3
0
 def start(self):
     ''' Start the Spotify client and HTTP server '''
     if not self.username or not self.password:
         Log("Username or password not set: not logging in")
         return
     self.client = SpotifyClient(self.username, self.password, self.ioloop)
     self.client.connect()
     self.server = SpotifyServer(self.client)
     self.server.start()
コード例 #4
0
ファイル: plugin.py プロジェクト: 10alc/Spotify2.bundle
    def start(self):
        """ Start the Spotify client and HTTP server """
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return False

        can_start = self.start_lock.acquire(blocking=False)
        try:
            # If there is a start in process, just wait until it finishes, but don't raise another one
            if not can_start:
                Log.Debug("Start already in progress, waiting it finishes to return")
                self.start_lock.acquire()
            else:
                Log.Debug("Start triggered, entering private section")
                self.start_marker.clear()

                if self.client:
                    self.client.restart(self.username, self.password)
                else:
                    self.client = SpotifyClient(self.username, self.password)

                self.last_track_uri = None
                self.last_track_object = None
                Dict['play_count']   = 0
                Dict['last_restart'] = time.time()
                self.start_marker.set()
                Log.Debug("Start finished, leaving private section")
        finally:
            self.start_lock.release()

        return self.client and self.client.is_logged_in()
コード例 #5
0
ファイル: plugin.py プロジェクト: jarlebh/Spotify.bundle
 def start(self):
     ''' Start the Spotify client and HTTP server '''
     if not self.username or not self.password:
         Log("Username or password not set: not logging in")
         return
     self.client = SpotifyClient(self.username, self.password, self.ioloop)
     self.client.connect()
     self.server = SpotifyServer(self.client)
     self.server.start()
コード例 #6
0
def run():

    # # if you'd prefer an input
    # token = input("What's your OAUTH Token? ")
    # client = SpotifyClient(token)
    client = SpotifyClient(os.getenv('token'))
    print(f"token is: {os.getenv('token')}")

    #--search for random songs
    tracks = client.get_tracks()

    # get each track id
    track_ids = [track['id'] for track in tracks]
    
    #--add songs to library
    was_added_to_library = client.add_tracks_to_library(track_ids)
    if (was_added_to_library == True):
        for track in tracks:
            print(f"Added {track['name']} to your library")
コード例 #7
0
def run():

    # # if you'd prefer an input
    # token = input("What's your OAUTH Token? ")
    # client = SpotifyClient(token)
    client = SpotifyClient(os.getenv('token'))
    print(f"token is: {os.getenv('token')}")

    choice = input("Return top 'artists' or 'tracks'?  ")
    if not (choice == 'artists' or choice == 'tracks'):
        print("Term not recognized. Default: tracks.")
        choice = 'tracks'

    if choice == 'tracks':
        want_analysis = input("Include audio analysis (y,n)?  ")
        tracks = client.get_top_tracks()
        print()
        print(
            'The following are your top tracks, starting with the most played')
        print()
        for i, track in enumerate(tracks):
            num = '{:<3}'.format(str(i + 1) + '.')
            print(f"{num} '{track['name']}' by {track['artists'][0]['name']} ")
            if (want_analysis == 'y'):
                features = client.get_analysis(track["id"])
                print(f"energy: {features['energy']}")
                print(f"valence: {features['valence']}")
                print(f"mode: {features['mode']}")
                print(f"danceability: {features['danceability']}")
                print(f"tempo: {features['tempo']}")
                print()

    if choice == 'artists':
        want_details = input("Include audio analysis (y,n)?  ")
        artists = client.get_top_artists()
        print()
        print(
            'The following are your top artists, starting with the most played'
        )
        print()
        for i, artist in enumerate(artists):
            num = '{:<3}'.format(str(i + 1) + '.')
            print(f"{num} {artist['name']} ")
            if (want_details == 'y'):
                details = client.get_artist_details(artist["id"])
                print(f"main genre: {details['genres'][0]}")
                print(f"popularity: {details['popularity']}")
                print()
コード例 #8
0
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--size",
                        type=int,
                        help="Size of the generated playlist")
    parser.add_argument("--name",
                        type=str,
                        help="Name of the generated playlist")
    parser.add_argument("--user",
                        type=str,
                        help="User for whom to create the playlist")
    parser.add_argument("--seed", type=str, help="ID of the seed track")
    args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, stream=sys.stdout)

    client = SpotifyClient(args.user)
    seed_track = client.get_track(args.seed)
    factory = ArtistChainFactory(client,
                                 seed_track,
                                 DriftingWeight(seed_track),
                                 unique_artists=False)

    # If a playlist of this name exists, use it, otherwise create one.
    for playlist_ in client.get_playlists():
        if playlist_.name == args.name:
            playlist = playlist_
            break
    else:
        playlist = Playlist(name=args.name)

    for _, track in zip(range(args.size), factory):
        playlist.append(track)

    LOG.info("Finished generating playlist %s", str(playlist))

    client.store_playlist(playlist)
コード例 #9
0
class SpotifyPlugin(object):
    def __init__(self):
        self.client = None
        self.server = None
        self.play_lock = Semaphore(1)
        self.start_lock = Semaphore(1)
        self.start_marker = Event()
        self.last_track_uri = None
        self.last_track_object = None

        Dict.Reset()
        Dict['play_count'] = 0
        Dict['last_restart'] = 0
        Dict['schedule_restart_each'] = 5 * 60  # restart each  X minutes
        Dict['play_restart_each'] = 2  # restart each  X plays
        Dict[
            'check_restart_each'] = 5  # check if I should restart each X seconds

        Dict[
            'radio_salt'] = False  # Saves last radio salt so multiple queries return the same radio track list

        self.start()

        self.session = requests.session()
        self.session_cached = CacheControl(self.session)

        Thread.CreateTimer(Dict['check_restart_each'],
                           self.check_automatic_restart,
                           globalize=True)

    @property
    def username(self):
        return Prefs["username"]

    @property
    def password(self):
        return Prefs["password"]

    def check_automatic_restart(self):

        can_restart = False

        try:

            diff = time.time() - Dict['last_restart']
            scheduled_restart = diff >= Dict['schedule_restart_each']
            play_count_restart = Dict['play_count'] >= Dict['play_restart_each']
            must_restart = play_count_restart or scheduled_restart

            if must_restart:
                can_restart = self.play_lock.acquire(blocking=False)
                if can_restart:
                    Log.Debug('Automatic restart started')
                    self.start()
                    Log.Debug('Automatic restart finished')

        finally:

            if can_restart:
                self.play_lock.release()

            Thread.CreateTimer(Dict['check_restart_each'],
                               self.check_automatic_restart,
                               globalize=True)

    @check_restart
    def preferences_updated(self):
        """ Called when the user updates the plugin preferences"""
        self.start()  # Trigger a client restart

    def start(self):
        """ Start the Spotify client and HTTP server """
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return False

        can_start = self.start_lock.acquire(blocking=False)
        try:
            # If there is a start in process, just wait until it finishes, but don't raise another one
            if not can_start:
                Log.Debug(
                    "Start already in progress, waiting it finishes to return")
                self.start_lock.acquire()
            else:
                Log.Debug("Start triggered, entering private section")
                self.start_marker.clear()

                if self.client:
                    self.client.restart(self.username, self.password)
                else:
                    self.client = SpotifyClient(self.username, self.password)

                self.last_track_uri = None
                self.last_track_object = None
                Dict['play_count'] = 0
                Dict['last_restart'] = time.time()
                self.start_marker.set()
                Log.Debug("Start finished, leaving private section")
        finally:
            self.start_lock.release()

        return self.client and self.client.is_logged_in()

    @check_restart
    def play(self, uri):
        Log('play(%s)' % repr(uri))

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        track_url = None
        if not self.client.is_track_uri_valid(uri):
            Log("Play track callback invoked with invalid URI (%s). This is very bad :-("
                % uri)
            track_url = "http://www.xamuel.com/blank-mp3-files/2sec.mp3"
        else:
            self.play_lock.acquire(blocking=True)
            try:
                track_url = self.get_track_url(uri)

                # If first request failed, trigger re-connection to spotify
                retry_num = 0
                while not track_url and retry_num < 2:
                    Log.Info(
                        'get_track_url (%s) failed, re-connecting to spotify...'
                        % uri)
                    time.sleep(
                        retry_num *
                        0.5)  # Wait some time based on number of failures
                    if self.start():
                        track_url = self.get_track_url(uri)
                    retry_num = retry_num + 1

                if track_url == False or track_url is None:
                    # Send an empty and short mp3 so player do not fail and we can go on listening next song
                    Log.Error(
                        "Play track (%s) couldn't be obtained. This is very bad :-("
                        % uri)
                    track_url = 'http://www.xamuel.com/blank-mp3-files/2sec.mp3'
                elif retry_num == 0:  # If I didn't restart, add 1 to playcount
                    Dict['play_count'] = Dict['play_count'] + 1
            finally:
                self.play_lock.release()

        return Redirect(track_url)

    def get_track_url(self, track_uri):
        if not self.client.is_track_uri_valid(track_uri):
            return None

        track_url = None

        track = self.client.get(track_uri)
        if track:
            track_url = track.getFileURL(urlOnly=True, retries=1)

        return track_url

    #
    # TRACK DETAIL
    #
    @check_restart
    def metadata(self, uri):
        Log('metadata(%s)' % repr(uri))

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        oc = ObjectContainer()
        track_object = None

        if not self.client.is_track_uri_valid(uri):
            Log("Metadata callback invoked with invalid URI (%s)" % uri)
            track_object = self.create_track_object_empty(uri)
        else:
            if self.last_track_uri == uri:
                track_object = self.last_track_object
            else:
                track_metadata = self.get_track_metadata(uri)

                if track_metadata:
                    track_object = self.create_track_object_from_metatada(
                        track_metadata)
                    self.last_track_uri = uri
                    self.last_track_object = track_object
                else:
                    track_object = self.create_track_object_empty(uri)

        oc.add(track_object)
        return oc

    def get_track_metadata(self, track_uri):
        if not self.client.is_track_uri_valid(track_uri):
            return None

        track = self.client.get(track_uri)
        if not track:
            return None

        #track_uri       = track.getURI().decode("utf-8")
        title = track.getName().decode("utf-8")
        image_url = self.select_image(track.getAlbumCovers())
        track_duration = int(track.getDuration())
        track_number = int(track.getNumber())
        track_album = track.getAlbum(nameOnly=True).decode("utf-8")
        track_artists = track.getArtists(nameOnly=True).decode("utf-8")
        metadata = TrackMetadata(title, image_url, track_uri, track_duration,
                                 track_number, track_album, track_artists)

        return metadata

    @staticmethod
    def select_image(images):
        if images == None:
            return None

        if images.get(640):
            return images[640]
        elif images.get(320):
            return images[320]
        elif images.get(300):
            return images[300]
        elif images.get(160):
            return images[160]
        elif images.get(60):
            return images[60]

        Log.Info('Unable to select image, available sizes: %s' % images.keys())
        return None

    def get_uri_image(self, uri):
        images = None
        obj = self.client.get(uri)
        if isinstance(obj, SpotifyArtist):
            images = obj.getPortraits()
        elif isinstance(obj, SpotifyAlbum):
            images = obj.getCovers()
        elif isinstance(obj, SpotifyTrack):
            images = obj.getAlbum().getCovers()
        elif isinstance(obj, SpotifyPlaylist):
            images = obj.getImages()

        return self.select_image(images)

    @authenticated
    @check_restart
    def image(self, uri):
        if not uri:
            # TODO media specific placeholders
            return Redirect(R('placeholder-artist.png'))

        Log.Debug('Getting image for: %s' % uri)

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        if uri.startswith('spotify:'):
            # Fetch object for spotify URI and select image
            image_url = self.get_uri_image(uri)

            if not image_url:
                # TODO media specific placeholders
                return Redirect(R('placeholder-artist.png'))
        else:
            # pre-selected image provided
            Log.Debug('Using pre-selected image URL: "%s"' % uri)
            image_url = uri

        return self.session_cached.get(image_url).content

    #
    # SECOND_LEVEL_MENU
    #

    @authenticated
    @check_restart
    def explore(self):
        Log("explore")
        """ Explore shared music
        """
        return ObjectContainer(objects=[
            DirectoryObject(key=route_path('explore/featured_playlists'),
                            title=L("MENU_FEATURED_PLAYLISTS"),
                            thumb=R("icon-explore-featuredplaylists.png")),
            DirectoryObject(key=route_path('explore/top_playlists'),
                            title=L("MENU_TOP_PLAYLISTS"),
                            thumb=R("icon-explore-topplaylists.png")),
            DirectoryObject(key=route_path('explore/new_releases'),
                            title=L("MENU_NEW_RELEASES"),
                            thumb=R("icon-explore-newreleases.png")),
            DirectoryObject(key=route_path('explore/genres'),
                            title=L("MENU_GENRES"),
                            thumb=R("icon-explore-genres.png"))
        ], )

    @authenticated
    @check_restart
    def discover(self):
        Log("discover")

        oc = ObjectContainer(title2=L("MENU_DISCOVER"),
                             view_group=ViewMode.Stories)

        stories = self.client.discover()
        for story in stories:
            self.add_story_to_directory(story, oc)
        return oc

    @authenticated
    @check_restart
    def radio(self):
        Log("radio")
        """ Show radio options """
        return ObjectContainer(objects=[
            DirectoryObject(key=route_path('radio/stations'),
                            title=L("MENU_RADIO_STATIONS"),
                            thumb=R("icon-radio-stations.png")),
            DirectoryObject(key=route_path('radio/genres'),
                            title=L("MENU_RADIO_GENRES"),
                            thumb=R("icon-radio-genres.png"))
        ], )

    @authenticated
    @check_restart
    def your_music(self):
        Log("your_music")
        """ Explore your music
        """
        return ObjectContainer(objects=[
            DirectoryObject(key=route_path('your_music/playlists'),
                            title=L("MENU_PLAYLISTS"),
                            thumb=R("icon-playlists.png")),
            DirectoryObject(key=route_path('your_music/starred'),
                            title=L("MENU_STARRED"),
                            thumb=R("icon-starred.png")),
            DirectoryObject(key=route_path('your_music/albums'),
                            title=L("MENU_ALBUMS"),
                            thumb=R("icon-albums.png")),
            DirectoryObject(key=route_path('your_music/artists'),
                            title=L("MENU_ARTISTS"),
                            thumb=R("icon-artists.png")),
        ], )

    #
    # EXPLORE
    #

    @authenticated
    @check_restart
    def featured_playlists(self):
        Log("featured playlists")

        oc = ObjectContainer(title2=L("MENU_FEATURED_PLAYLISTS"),
                             content=ContainerContent.Playlists,
                             view_group=ViewMode.Playlists)

        playlists = self.client.get_featured_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def top_playlists(self):
        Log("top playlists")

        oc = ObjectContainer(title2=L("MENU_TOP_PLAYLISTS"),
                             content=ContainerContent.Playlists,
                             view_group=ViewMode.Playlists)

        playlists = self.client.get_top_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def new_releases(self):
        Log("new releases")

        oc = ObjectContainer(title2=L("MENU_NEW_RELEASES"),
                             content=ContainerContent.Albums,
                             view_group=ViewMode.Albums)

        albums = self.client.get_new_releases()

        for album in albums:
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def genres(self):
        Log("genres")

        oc = ObjectContainer(title2=L("MENU_GENRES"),
                             content=ContainerContent.Playlists,
                             view_group=ViewMode.Playlists)

        genres = self.client.get_genres()

        for genre in genres:
            self.add_genre_to_directory(genre, oc)

        return oc

    @authenticated
    @check_restart
    def genre_playlists(self, genre_name):
        Log("genre playlists")

        oc = ObjectContainer(title2=genre_name,
                             content=ContainerContent.Playlists,
                             view_group=ViewMode.Playlists)

        playlists = self.client.get_playlists_by_genre(genre_name)

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    #
    # RADIO
    #

    @authenticated
    @check_restart
    def radio_stations(self):
        Log('radio stations')

        Dict['radio_salt'] = False
        oc = ObjectContainer(title2=L("MENU_RADIO_STATIONS"))
        stations = self.client.get_radio_stations()
        for station in stations:
            oc.add(
                PopupDirectoryObject(
                    key=route_path('radio/stations/' + station.getURI()),
                    title=station.getTitle(),
                    thumb=function_path('image.png',
                                        uri=self.select_image(
                                            station.getImages()))))
        return oc

    @authenticated
    @check_restart
    def radio_genres(self):
        Log('radio genres')

        Dict['radio_salt'] = False
        oc = ObjectContainer(title2=L("MENU_RADIO_GENRES"))
        genres = self.client.get_radio_genres()
        for genre in genres:
            oc.add(
                PopupDirectoryObject(
                    key=route_path('radio/genres/' + genre.getURI()),
                    title=genre.getTitle(),
                    thumb=function_path('image.png',
                                        uri=self.select_image(
                                            genre.getImages()))))
        return oc

    @authenticated
    @check_restart
    def radio_track_num(self, uri):
        Log('radio track num')

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        return ObjectContainer(
            title2=L("MENU_RADIO_TRACK_NUM"),
            objects=[
                DirectoryObject(key=route_path('radio/play/' + uri + '/10'),
                                title=localized_format("MENU_TRACK_NUM", "10"),
                                thumb=R("icon-radio-item.png")),
                DirectoryObject(key=route_path('radio/play/' + uri + '/20'),
                                title=localized_format("MENU_TRACK_NUM", "20"),
                                thumb=R("icon-radio-item.png")),
                DirectoryObject(key=route_path('radio/play/' + uri + '/50'),
                                title=localized_format("MENU_TRACK_NUM", "50"),
                                thumb=R("icon-radio-item.png")),
                DirectoryObject(key=route_path('radio/play/' + uri + '/80'),
                                title=localized_format("MENU_TRACK_NUM", "80"),
                                thumb=R("icon-radio-item.png")),
                DirectoryObject(key=route_path('radio/play/' + uri + '/100'),
                                title=localized_format("MENU_TRACK_NUM",
                                                       "100"),
                                thumb=R("icon-radio-item.png"))
            ],
        )

    @authenticated
    @check_restart
    def radio_tracks(self, uri, num_tracks):
        Log('radio tracks')

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        oc = None
        radio = self.client.get_radio(uri)

        if not Dict['radio_salt']:
            Dict['radio_salt'] = radio.generateSalt()

        salt = Dict['radio_salt']
        tracks = radio.getTracks(salt=salt, num_tracks=int(num_tracks))

        oc = ObjectContainer(title2=radio.getTitle().decode("utf-8"),
                             content=ContainerContent.Tracks,
                             view_group=ViewMode.Tracks)

        for track in tracks:
            self.add_track_to_directory(track, oc)

        return oc

    #
    # YOUR_MUSIC
    #

    @authenticated
    @check_restart
    def playlists(self):
        Log("playlists")

        oc = ObjectContainer(title2=L("MENU_PLAYLISTS"),
                             content=ContainerContent.Playlists,
                             view_group=ViewMode.Playlists)

        playlists = self.client.get_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def starred(self):
        Log("starred")

        oc = ObjectContainer(title2=L("MENU_STARRED"),
                             content=ContainerContent.Tracks,
                             view_group=ViewMode.Tracks)

        starred = self.client.get_starred()

        for x, track in enumerate(starred.getTracks()):
            self.add_track_to_directory(track, oc, index=x)

        return oc

    @authenticated
    @check_restart
    def albums(self):
        Log("albums")

        oc = ObjectContainer(title2=L("MENU_ALBUMS"),
                             content=ContainerContent.Albums,
                             view_group=ViewMode.Albums)

        albums = self.client.get_my_albums()

        for album in albums:
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def artists(self):
        Log("artists")

        oc = ObjectContainer(title2=L("MENU_ARTISTS"),
                             content=ContainerContent.Artists,
                             view_group=ViewMode.Artists)

        artists = self.client.get_my_artists()

        for artist in artists:
            self.add_artist_to_directory(artist, oc)

        return oc

    #
    # ARTIST DETAIL
    #

    @authenticated
    @check_restart
    def artist(self, uri):
        Log("artist")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        artist = self.client.get(uri)
        return ObjectContainer(
            title2=artist.getName().decode("utf-8"),
            objects=[
                DirectoryObject(key=route_path('artist/%s/top_tracks' % uri),
                                title=L("MENU_TOP_TRACKS"),
                                thumb=R("icon-artist-toptracks.png")),
                DirectoryObject(key=route_path('artist/%s/albums' % uri),
                                title=L("MENU_ALBUMS"),
                                thumb=R("icon-albums.png")),
                DirectoryObject(key=route_path('artist/%s/related' % uri),
                                title=L("MENU_RELATED"),
                                thumb=R("icon-artist-related.png")),
                DirectoryObject(key=route_path('radio/stations/' + uri),
                                title=L("MENU_RADIO"),
                                thumb=R("icon-radio-custom.png"))
            ],
        )

    @authenticated
    @check_restart
    def artist_albums(self, uri):
        Log("artist_albums")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        artist = self.client.get(uri)

        oc = ObjectContainer(title2=artist.getName().decode("utf-8"),
                             content=ContainerContent.Albums)

        for album in artist.getAlbums():
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def artist_top_tracks(self, uri):
        Log("artist_top_tracks")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        oc = None
        artist = self.client.get(uri)
        top_tracks = artist.getTracks()

        if top_tracks:
            oc = ObjectContainer(title2=artist.getName().decode("utf-8"),
                                 content=ContainerContent.Tracks,
                                 view_group=ViewMode.Tracks)
            for track in artist.getTracks():
                self.add_track_to_directory(track, oc)
        else:
            oc = MessageContainer(header=L("MSG_TITLE_NO_RESULTS"),
                                  message=localized_format(
                                      "MSG_FMT_NO_RESULTS",
                                      artist.getName().decode("utf-8")))
        return oc

    @authenticated
    @check_restart
    def artist_related(self, uri):
        Log("artist_related")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        artist = self.client.get(uri)

        oc = ObjectContainer(title2=localized_format(
            "MSG_RELATED_TO",
            artist.getName().decode("utf-8")),
                             content=ContainerContent.Artists)

        for artist in artist.getRelatedArtists():
            self.add_artist_to_directory(artist, oc)

        return oc

    #
    # ALBUM DETAIL
    #

    @authenticated
    @check_restart
    def album(self, uri):
        Log("album")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        album = self.client.get(uri)

        oc = ObjectContainer(title2=album.getName().decode("utf-8"),
                             content=ContainerContent.Artists)

        oc.add(
            DirectoryObject(key=route_path('album/%s/tracks' % uri),
                            title=L("MENU_ALBUM_TRACKS"),
                            thumb=R("icon-album-tracks.png")))

        artists = album.getArtists()
        for artist in artists:
            self.add_artist_to_directory(artist, oc)

        oc.add(
            DirectoryObject(key=route_path('radio/stations/' + uri),
                            title=L("MENU_RADIO"),
                            thumb=R("icon-radio-custom.png")))

        return oc

    @authenticated
    @check_restart
    def album_tracks(self, uri):
        Log("album_tracks")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        album = self.client.get(uri)

        oc = ObjectContainer(title2=album.getName().decode("utf-8"),
                             content=ContainerContent.Tracks,
                             view_group=ViewMode.Tracks)

        for track in album.getTracks():
            self.add_track_to_directory(track, oc)

        return oc

    #
    # PLAYLIST DETAIL
    #

    @authenticated
    @check_restart
    def playlist(self, uri):
        Log("playlist")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A",
                                                       ":").decode("utf8")

        pl = self.client.get(uri)
        if pl is None:
            # Unable to find playlist
            return MessageContainer(header=L("MSG_TITLE_UNKNOWN_PLAYLIST"),
                                    message='URI: %s' % uri)

        Log("Get playlist: %s", pl.getName().decode("utf-8"))
        Log.Debug('playlist truncated: %s', pl.obj.contents.truncated)

        oc = ObjectContainer(title2=pl.getName().decode("utf-8"),
                             content=ContainerContent.Tracks,
                             view_group=ViewMode.Tracks,
                             mixed_parents=True)

        for x, track in enumerate(pl.getTracks()):
            self.add_track_to_directory(track, oc, index=x)

        return oc

    #
    # MAIN MENU
    #
    def main_menu(self):
        Log("main_menu")

        return ObjectContainer(objects=[
            InputDirectoryObject(key=route_path('search'),
                                 prompt=L("PROMPT_SEARCH"),
                                 title=L("MENU_SEARCH"),
                                 thumb=R("icon-search.png")),
            DirectoryObject(key=route_path('explore'),
                            title=L("MENU_EXPLORE"),
                            thumb=R("icon-explore.png")),
            DirectoryObject(key=route_path('discover'),
                            title=L("MENU_DISCOVER"),
                            thumb=R("icon-discover.png")),
            DirectoryObject(key=route_path('radio'),
                            title=L("MENU_RADIO"),
                            thumb=R("icon-radio.png")),
            DirectoryObject(key=route_path('your_music'),
                            title=L("MENU_YOUR_MUSIC"),
                            thumb=R("icon-yourmusic.png")),
            PrefsObject(title=L("MENU_PREFS"), thumb=R("icon-preferences.png"))
        ], )

    #
    # Create objects
    #
    def create_track_object_from_track(self, track, index=None):
        if not track:
            return None

        # Get metadata info
        track_uri = track.getURI()
        title = track.getName().decode("utf-8")
        image_url = self.select_image(track.getAlbumCovers())
        track_duration = int(track.getDuration()) - 500
        track_number = int(track.getNumber())
        track_album = track.getAlbum(nameOnly=True).decode("utf-8")
        track_artists = track.getArtists(nameOnly=True).decode("utf-8")
        metadata = TrackMetadata(title, image_url, track_uri, track_duration,
                                 track_number, track_album, track_artists)

        return self.create_track_object_from_metatada(metadata, index=index)

    def create_track_object_from_metatada(self, metadata, index=None):
        if not metadata:
            return None
        return self.create_track_object(metadata.uri, metadata.duration,
                                        metadata.title, metadata.album,
                                        metadata.artists, metadata.number,
                                        metadata.image_url, index)

    def create_track_object_empty(self, uri):
        if not uri:
            return None
        return self.create_track_object(uri, -1, "", "", "", 0, None)

    def create_track_object(self,
                            uri,
                            duration,
                            title,
                            album,
                            artists,
                            track_number,
                            image_url,
                            index=None):
        rating_key = uri
        if index is not None:
            rating_key = '%s::%s' % (uri, index)

        art_num = str(randint(1, 40)).rjust(2, "0")

        track_obj = TrackObject(items=[
            MediaObject(parts=[PartObject(key=route_path('play/%s' % uri))],
                        duration=duration,
                        container=Container.MP3,
                        audio_codec=AudioCodec.MP3,
                        audio_channels=2)
        ],
                                key=route_path('metadata', uri),
                                rating_key=rating_key,
                                title=title,
                                album=album,
                                artist=artists,
                                index=index if index != None else track_number,
                                duration=duration,
                                source_title='Spotify',
                                art=R('art-' + art_num + '.png'),
                                thumb=function_path('image.png',
                                                    uri=image_url))

        Log.Debug(
            'New track object for metadata: --|%s|%s|%s|%s|%s|%s|--' %
            (image_url, uri, str(duration), str(track_number), album, artists))

        return track_obj

    def create_album_object(self,
                            album,
                            custom_summary=None,
                            custom_image_url=None):
        """ Factory method for album objects """
        title = album.getName().decode("utf-8")
        if Prefs["displayAlbumYear"] and album.getYear() != 0:
            title = "%s (%s)" % (title, album.getYear())
        artist_name = album.getArtists(nameOnly=True).decode("utf-8")
        summary = '' if custom_summary == None else custom_summary.decode(
            'utf-8')
        image_url = self.select_image(album.getCovers(
        )) if custom_image_url == None else custom_image_url

        return DirectoryObject(
            key=route_path('album', album.getURI()),
            title=title + " - " + artist_name,
            tagline=artist_name,
            summary=summary,
            art=function_path('image.png', uri=image_url),
            thumb=function_path('image.png', uri=image_url),
        )

        #return AlbumObject(
        #    key=route_path('album', album.getURI().decode("utf-8")),
        #    rating_key=album.getURI().decode("utf-8"),
        #
        #    title=title,
        #    artist=artist_name,
        #    summary=summary,
        #
        #    track_count=album.getNumTracks(),
        #    source_title='Spotify',
        #
        #    art=function_path('image.png', uri=image_url),
        #    thumb=function_path('image.png', uri=image_url),
        #)

    def create_playlist_object(self, playlist):
        uri = playlist.getURI()
        image_url = self.select_image(playlist.getImages())
        artist = playlist.getUsername().decode('utf8')
        title = playlist.getName().decode("utf-8")
        summary = ''
        if playlist.getDescription() != None and len(
                playlist.getDescription()) > 0:
            summary = playlist.getDescription().decode("utf-8")

        return DirectoryObject(
            key=route_path('playlist', uri),
            title=title + " - " + artist,
            tagline=artist,
            summary=summary,
            art=function_path('image.png', uri=image_url)
            if image_url != None else R("placeholder-playlist.png"),
            thumb=function_path('image.png', uri=image_url)
            if image_url != None else R("placeholder-playlist.png"))

        #return AlbumObject(
        #    key=route_path('playlist', uri),
        #    rating_key=uri,
        #
        #    title=title,
        #    artist=artist,
        #    summary=summary,
        #
        #    source_title='Spotify',
        #
        #    art=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png"),
        #    thumb=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png")
        #)

    def create_genre_object(self, genre):
        uri = genre.getTemplateName()
        title = genre.getName().decode("utf-8")
        image_url = genre.getIconUrl()

        return DirectoryObject(
            key=route_path('genre', uri),
            title=title,
            art=function_path('image.png', uri=image_url)
            if image_url != None else R("placeholder-playlist.png"),
            thumb=function_path('image.png', uri=image_url)
            if image_url != None else R("placeholder-playlist.png"))

    def create_artist_object(self,
                             artist,
                             custom_summary=None,
                             custom_image_url=None):
        image_url = self.select_image(artist.getPortraits(
        )) if custom_image_url == None else custom_image_url
        artist_name = artist.getName().decode("utf-8")
        summary = '' if custom_summary == None else custom_summary.decode(
            'utf-8')

        return DirectoryObject(key=route_path('artist', artist.getURI()),
                               title=artist_name,
                               summary=summary,
                               art=function_path('image.png', uri=image_url),
                               thumb=function_path('image.png', uri=image_url))

        #return ArtistObject(
        #        key=route_path('artist', artist.getURI().decode("utf-8")),
        #        rating_key=artist.getURI().decode("utf-8"),
        #
        #        title=artist_name,
        #        summary=summary,
        #        source_title='Spotify',
        #
        #        art=function_path('image.png', uri=image_url),
        #        thumb=function_path('image.png', uri=image_url)
        #    )

    #
    # Insert objects into container
    #

    def add_section_header(self, title, oc):
        oc.add(DirectoryObject(key='', title=title))

    def add_track_to_directory(self, track, oc, index=None):
        if not self.client.is_track_playable(track):
            Log("Ignoring unplayable track: %s" % track.getName())
            return

        track_uri = track.getURI().decode("utf-8")
        if not self.client.is_track_uri_valid(track_uri):
            Log("Ignoring unplayable track: %s, invalid uri: %s" %
                (track.getName(), track_uri))
            return

        oc.add(self.create_track_object_from_track(track, index=index))

    def add_album_to_directory(self,
                               album,
                               oc,
                               custom_summary=None,
                               custom_image_url=None):
        if not self.client.is_album_playable(album):
            Log("Ignoring unplayable album: %s" % album.getName())
            return
        oc.add(
            self.create_album_object(album,
                                     custom_summary=custom_summary,
                                     custom_image_url=custom_image_url))

    def add_artist_to_directory(self,
                                artist,
                                oc,
                                custom_summary=None,
                                custom_image_url=None):
        oc.add(
            self.create_artist_object(artist,
                                      custom_summary=custom_summary,
                                      custom_image_url=custom_image_url))

    def add_playlist_to_directory(self, playlist, oc):
        oc.add(self.create_playlist_object(playlist))

    def add_genre_to_directory(self, genre, oc):
        oc.add(self.create_genre_object(genre))

    def add_story_to_directory(self, story, oc):
        content_type = story.getContentType()
        image_url = self.select_image(story.getImages())
        item = story.getObject()
        if content_type == 'artist':
            self.add_artist_to_directory(item,
                                         oc,
                                         custom_summary=story.getDescription(),
                                         custom_image_url=image_url)
        elif content_type == 'album':
            self.add_album_to_directory(item,
                                        oc,
                                        custom_summary=story.getDescription(),
                                        custom_image_url=image_url)
        elif content_type == 'track':
            self.add_album_to_directory(item.getAlbum(),
                                        oc,
                                        custom_summary=story.getDescription() +
                                        " - " + item.getName(),
                                        custom_image_url=image_url)
コード例 #10
0
ファイル: plugin.py プロジェクト: 10alc/Spotify2.bundle
class SpotifyPlugin(object):
    def __init__(self):
        self.client = None
        self.server = None
        self.play_lock      = Semaphore(1)
        self.start_lock     = Semaphore(1)
        self.start_marker   = Event()
        self.last_track_uri = None
        self.last_track_object = None

        Dict.Reset()
        Dict['play_count']             = 0
        Dict['last_restart']           = 0
        Dict['schedule_restart_each']  = 5*60    # restart each  X minutes
        Dict['play_restart_each']      = 2       # restart each  X plays
        Dict['check_restart_each']     = 5       # check if I should restart each X seconds

        Dict['radio_salt']             = False   # Saves last radio salt so multiple queries return the same radio track list

        self.start()

        self.session = requests.session()
        self.session_cached = CacheControl(self.session)

        Thread.CreateTimer(Dict['check_restart_each'], self.check_automatic_restart, globalize=True)

    @property
    def username(self):
        return Prefs["username"]

    @property
    def password(self):
        return Prefs["password"]

    def check_automatic_restart(self):

        can_restart = False

        try:

            diff = time.time() - Dict['last_restart']
            scheduled_restart  = diff >= Dict['schedule_restart_each']
            play_count_restart = Dict['play_count'] >= Dict['play_restart_each']
            must_restart = play_count_restart or scheduled_restart

            if must_restart:
                can_restart = self.play_lock.acquire(blocking=False)
                if can_restart:
                    Log.Debug('Automatic restart started')
                    self.start()
                    Log.Debug('Automatic restart finished')

        finally:

            if can_restart:
                self.play_lock.release()

            Thread.CreateTimer(Dict['check_restart_each'], self.check_automatic_restart, globalize=True)

    @check_restart
    def preferences_updated(self):
        """ Called when the user updates the plugin preferences"""
        self.start() # Trigger a client restart

    def start(self):
        """ Start the Spotify client and HTTP server """
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return False

        can_start = self.start_lock.acquire(blocking=False)
        try:
            # If there is a start in process, just wait until it finishes, but don't raise another one
            if not can_start:
                Log.Debug("Start already in progress, waiting it finishes to return")
                self.start_lock.acquire()
            else:
                Log.Debug("Start triggered, entering private section")
                self.start_marker.clear()

                if self.client:
                    self.client.restart(self.username, self.password)
                else:
                    self.client = SpotifyClient(self.username, self.password)

                self.last_track_uri = None
                self.last_track_object = None
                Dict['play_count']   = 0
                Dict['last_restart'] = time.time()
                self.start_marker.set()
                Log.Debug("Start finished, leaving private section")
        finally:
            self.start_lock.release()

        return self.client and self.client.is_logged_in()

    @check_restart
    def play(self, uri):
        Log('play(%s)' % repr(uri))

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        track_url = None
        if not self.client.is_track_uri_valid(uri):
            Log("Play track callback invoked with invalid URI (%s). This is very bad :-(" % uri)
            track_url = "http://www.xamuel.com/blank-mp3-files/2sec.mp3"
        else:
            self.play_lock.acquire(blocking=True)
            try:
                track_url = self.get_track_url(uri)

                # If first request failed, trigger re-connection to spotify
                retry_num = 0
                while not track_url and retry_num < 2:
                    Log.Info('get_track_url (%s) failed, re-connecting to spotify...' % uri)
                    time.sleep(retry_num*0.5) # Wait some time based on number of failures
                    if self.start():
                        track_url = self.get_track_url(uri)
                    retry_num = retry_num + 1

                if track_url == False or track_url is None:
                    # Send an empty and short mp3 so player do not fail and we can go on listening next song
                    Log.Error("Play track (%s) couldn't be obtained. This is very bad :-(" % uri)
                    track_url = 'http://www.xamuel.com/blank-mp3-files/2sec.mp3'
                elif retry_num == 0: # If I didn't restart, add 1 to playcount
                    Dict['play_count'] = Dict['play_count'] + 1
            finally:
                self.play_lock.release()

        return Redirect(track_url)

    def get_track_url(self, track_uri):
        if not self.client.is_track_uri_valid(track_uri):
            return None

        track_url = None

        track = self.client.get(track_uri)
        if track:
            track_url = track.getFileURL(urlOnly=True, retries=1)

        return track_url

    #
    # TRACK DETAIL
    #
    @check_restart
    def metadata(self, uri):
        Log('metadata(%s)' % repr(uri))

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        oc = ObjectContainer()
        track_object = None

        if not self.client.is_track_uri_valid(uri):
            Log("Metadata callback invoked with invalid URI (%s)" % uri)
            track_object = self.create_track_object_empty(uri)
        else:
            if self.last_track_uri == uri:
                track_object = self.last_track_object
            else:
                track_metadata = self.get_track_metadata(uri)

                if track_metadata:
                    track_object = self.create_track_object_from_metatada(track_metadata)
                    self.last_track_uri = uri
                    self.last_track_object = track_object
                else:
                    track_object = self.create_track_object_empty(uri)

        oc.add(track_object)
        return oc

    def get_track_metadata(self, track_uri):
        if not self.client.is_track_uri_valid(track_uri):
            return None

        track = self.client.get(track_uri)
        if not track:
            return None

        #track_uri       = track.getURI().decode("utf-8")
        title           = track.getName().decode("utf-8")
        image_url       = self.select_image(track.getAlbumCovers())
        track_duration  = int(track.getDuration())
        track_number    = int(track.getNumber())
        track_album     = track.getAlbum(nameOnly=True).decode("utf-8")
        track_artists   = track.getArtists(nameOnly=True).decode("utf-8")
        metadata        = TrackMetadata(title, image_url, track_uri, track_duration, track_number, track_album, track_artists)

        return metadata

    @staticmethod
    def select_image(images):
        if images == None:
            return None

        if images.get(640):
            return images[640]
        elif images.get(320):
            return images[320]
        elif images.get(300):
            return images[300]
        elif images.get(160):
            return images[160]
        elif images.get(60):
            return images[60]

        Log.Info('Unable to select image, available sizes: %s' % images.keys())
        return None

    def get_uri_image(self, uri):
        images = None
        obj = self.client.get(uri)
        if isinstance(obj, SpotifyArtist):
            images = obj.getPortraits()
        elif isinstance(obj, SpotifyAlbum):
            images = obj.getCovers()
        elif isinstance(obj, SpotifyTrack):
            images = obj.getAlbum().getCovers()
        elif isinstance(obj, SpotifyPlaylist):
            images = obj.getImages()

        return self.select_image(images)

    @authenticated
    @check_restart
    def image(self, uri):
        if not uri:
            # TODO media specific placeholders
            return Redirect(R('placeholder-artist.png'))

        Log.Debug('Getting image for: %s' % uri)

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        if uri.startswith('spotify:'):
            # Fetch object for spotify URI and select image
            image_url = self.get_uri_image(uri)

            if not image_url:
                # TODO media specific placeholders
                return Redirect(R('placeholder-artist.png'))
        else:
            # pre-selected image provided
            Log.Debug('Using pre-selected image URL: "%s"' % uri)
            image_url = uri

        return self.session_cached.get(image_url).content

    #
    # SECOND_LEVEL_MENU
    #

    @authenticated
    @check_restart
    def explore(self):
        Log("explore")

        """ Explore shared music
        """
        return ObjectContainer(
            objects=[
                DirectoryObject(
                    key=route_path('explore/featured_playlists'),
                    title=L("MENU_FEATURED_PLAYLISTS"),
                    thumb=R("icon-explore-featuredplaylists.png")
                ),
                DirectoryObject(
                    key=route_path('explore/top_playlists'),
                    title=L("MENU_TOP_PLAYLISTS"),
                    thumb=R("icon-explore-topplaylists.png")
                ),
                DirectoryObject(
                    key=route_path('explore/new_releases'),
                    title=L("MENU_NEW_RELEASES"),
                    thumb=R("icon-explore-newreleases.png")
                ),
                DirectoryObject(
                    key=route_path('explore/genres'),
                    title=L("MENU_GENRES"),
                    thumb=R("icon-explore-genres.png")
                )
            ],
        )

    @authenticated
    @check_restart
    def discover(self):
        Log("discover")

        oc = ObjectContainer(
            title2=L("MENU_DISCOVER"),
            view_group=ViewMode.Stories
        )

        stories = self.client.discover()
        for story in stories:
            self.add_story_to_directory(story, oc)
        return oc

    @authenticated
    @check_restart
    def radio(self):
        Log("radio")

        """ Show radio options """
        return ObjectContainer(
            objects=[
                DirectoryObject(
                    key=route_path('radio/stations'),
                    title=L("MENU_RADIO_STATIONS"),
                    thumb=R("icon-radio-stations.png")
                ),
                DirectoryObject(
                    key=route_path('radio/genres'),
                    title=L("MENU_RADIO_GENRES"),
                    thumb=R("icon-radio-genres.png")
                )
            ],
        )

    @authenticated
    @check_restart
    def your_music(self):
        Log("your_music")

        """ Explore your music
        """
        return ObjectContainer(
            objects=[
                DirectoryObject(
                    key=route_path('your_music/playlists'),
                    title=L("MENU_PLAYLISTS"),
                    thumb=R("icon-playlists.png")
                ),
                DirectoryObject(
                    key=route_path('your_music/starred'),
                    title=L("MENU_STARRED"),
                    thumb=R("icon-starred.png")
                ),
                DirectoryObject(
                    key=route_path('your_music/albums'),
                    title=L("MENU_ALBUMS"),
                    thumb=R("icon-albums.png")
                ),
                DirectoryObject(
                    key=route_path('your_music/artists'),
                    title=L("MENU_ARTISTS"),
                    thumb=R("icon-artists.png")
                ),
            ],
        )

    #
    # EXPLORE
    #

    @authenticated
    @check_restart
    def featured_playlists(self):
        Log("featured playlists")

        oc = ObjectContainer(
            title2=L("MENU_FEATURED_PLAYLISTS"),
            content=ContainerContent.Playlists,
            view_group=ViewMode.Playlists
        )

        playlists = self.client.get_featured_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def top_playlists(self):
        Log("top playlists")

        oc = ObjectContainer(
            title2=L("MENU_TOP_PLAYLISTS"),
            content=ContainerContent.Playlists,
            view_group=ViewMode.Playlists
        )

        playlists = self.client.get_top_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def new_releases(self):
        Log("new releases")

        oc = ObjectContainer(
            title2=L("MENU_NEW_RELEASES"),
            content=ContainerContent.Albums,
            view_group=ViewMode.Albums
        )

        albums = self.client.get_new_releases()

        for album in albums:
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def genres(self):
        Log("genres")

        oc = ObjectContainer(
            title2=L("MENU_GENRES"),
            content=ContainerContent.Playlists,
            view_group=ViewMode.Playlists
        )

        genres = self.client.get_genres()

        for genre in genres:
            self.add_genre_to_directory(genre, oc)

        return oc

    @authenticated
    @check_restart
    def genre_playlists(self, genre_name):
        Log("genre playlists")

        oc = ObjectContainer(
            title2=genre_name,
            content=ContainerContent.Playlists,
            view_group=ViewMode.Playlists
        )

        playlists = self.client.get_playlists_by_genre(genre_name)

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    #
    # RADIO
    #

    @authenticated
    @check_restart
    def radio_stations(self):
        Log('radio stations')

        Dict['radio_salt'] = False
        oc = ObjectContainer(title2=L("MENU_RADIO_STATIONS"))
        stations = self.client.get_radio_stations()
        for station in stations:
            oc.add(PopupDirectoryObject(
                        key=route_path('radio/stations/' + station.getURI()),
                        title=station.getTitle(),
                        thumb=function_path('image.png', uri=self.select_image(station.getImages()))
                        ))
        return oc

    @authenticated
    @check_restart
    def radio_genres(self):
        Log('radio genres')

        Dict['radio_salt'] = False
        oc = ObjectContainer(title2=L("MENU_RADIO_GENRES"))
        genres = self.client.get_radio_genres()
        for genre in genres:
            oc.add(PopupDirectoryObject(
                        key=route_path('radio/genres/' + genre.getURI()),
                        title=genre.getTitle(),
                        thumb=function_path('image.png', uri=self.select_image(genre.getImages()))
                        ))
        return oc

    @authenticated
    @check_restart
    def radio_track_num(self, uri):
        Log('radio track num')

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        return ObjectContainer(
            title2=L("MENU_RADIO_TRACK_NUM"),
            objects=[
                DirectoryObject(
                    key=route_path('radio/play/' + uri + '/10'),
                    title=localized_format("MENU_TRACK_NUM", "10"),
                    thumb=R("icon-radio-item.png")
                ),
                DirectoryObject(
                    key=route_path('radio/play/' + uri + '/20'),
                    title=localized_format("MENU_TRACK_NUM", "20"),
                    thumb=R("icon-radio-item.png")
                ),
                DirectoryObject(
                    key=route_path('radio/play/' + uri + '/50'),
                    title=localized_format("MENU_TRACK_NUM", "50"),
                    thumb=R("icon-radio-item.png")
                ),
                DirectoryObject(
                    key=route_path('radio/play/' + uri + '/80'),
                    title=localized_format("MENU_TRACK_NUM", "80"),
                    thumb=R("icon-radio-item.png")
                ),
                DirectoryObject(
                    key=route_path('radio/play/' + uri + '/100'),
                    title=localized_format("MENU_TRACK_NUM", "100"),
                    thumb=R("icon-radio-item.png")
                )
            ],
        )

    @authenticated
    @check_restart
    def radio_tracks(self, uri, num_tracks):
        Log('radio tracks')

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        oc     = None
        radio  = self.client.get_radio(uri)

        if not Dict['radio_salt']:
            Dict['radio_salt'] = radio.generateSalt()

        salt = Dict['radio_salt']
        tracks = radio.getTracks(salt=salt, num_tracks=int(num_tracks))

        oc = ObjectContainer(
            title2     = radio.getTitle().decode("utf-8"),
            content    = ContainerContent.Tracks,
            view_group = ViewMode.Tracks
        )

        for track in tracks:
            self.add_track_to_directory(track, oc)

        return oc

    #
    # YOUR_MUSIC
    #

    @authenticated
    @check_restart
    def playlists(self):
        Log("playlists")

        oc = ObjectContainer(
            title2=L("MENU_PLAYLISTS"),
            content=ContainerContent.Playlists,
            view_group=ViewMode.Playlists
        )

        playlists = self.client.get_playlists()

        for playlist in playlists:
            self.add_playlist_to_directory(playlist, oc)

        return oc

    @authenticated
    @check_restart
    def starred(self):
        Log("starred")

        oc = ObjectContainer(
            title2=L("MENU_STARRED"),
            content=ContainerContent.Tracks,
            view_group=ViewMode.Tracks
        )

        starred = self.client.get_starred()

        for x, track in enumerate(starred.getTracks()):
            self.add_track_to_directory(track, oc, index=x)

        return oc

    @authenticated
    @check_restart
    def albums(self):
        Log("albums")

        oc = ObjectContainer(
            title2=L("MENU_ALBUMS"),
            content=ContainerContent.Albums,
            view_group=ViewMode.Albums
        )

        albums = self.client.get_my_albums()

        for album in albums:
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def artists(self):
        Log("artists")

        oc = ObjectContainer(
            title2=L("MENU_ARTISTS"),
            content=ContainerContent.Artists,
            view_group=ViewMode.Artists
        )

        artists = self.client.get_my_artists()

        for artist in artists:
            self.add_artist_to_directory(artist, oc)

        return oc

    #
    # ARTIST DETAIL
    #

    @authenticated
    @check_restart
    def artist(self, uri):
        Log("artist")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        artist = self.client.get(uri)
        return ObjectContainer(
            title2=artist.getName().decode("utf-8"),

            objects=[
                DirectoryObject(
                    key  = route_path('artist/%s/top_tracks' % uri),
                    title=L("MENU_TOP_TRACKS"),
                    thumb=R("icon-artist-toptracks.png")
                ),
                DirectoryObject(
                    key  = route_path('artist/%s/albums' % uri),
                    title =L("MENU_ALBUMS"),
                    thumb =R("icon-albums.png")
                ),
                DirectoryObject(
                    key  = route_path('artist/%s/related' % uri),
                    title =L("MENU_RELATED"),
                    thumb =R("icon-artist-related.png")
                ),
                DirectoryObject(
                    key=route_path('radio/stations/' + uri),
                    title =L("MENU_RADIO"),
                    thumb =R("icon-radio-custom.png")
                )
            ],
        )

    @authenticated
    @check_restart
    def artist_albums(self, uri):
        Log("artist_albums")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        artist = self.client.get(uri)

        oc = ObjectContainer(
            title2=artist.getName().decode("utf-8"),
            content=ContainerContent.Albums
        )

        for album in artist.getAlbums():
            self.add_album_to_directory(album, oc)

        return oc

    @authenticated
    @check_restart
    def artist_top_tracks(self, uri):
        Log("artist_top_tracks")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        oc          = None
        artist      = self.client.get(uri)
        top_tracks  = artist.getTracks()

        if top_tracks:
            oc = ObjectContainer(
                title2=artist.getName().decode("utf-8"),
                content=ContainerContent.Tracks,
                view_group=ViewMode.Tracks
            )
            for track in artist.getTracks():
                self.add_track_to_directory(track, oc)
        else:
            oc = MessageContainer(
                header=L("MSG_TITLE_NO_RESULTS"),
                message=localized_format("MSG_FMT_NO_RESULTS", artist.getName().decode("utf-8"))
            )
        return oc

    @authenticated
    @check_restart
    def artist_related(self, uri):
        Log("artist_related")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        artist = self.client.get(uri)

        oc = ObjectContainer(
            title2=localized_format("MSG_RELATED_TO", artist.getName().decode("utf-8")),
            content=ContainerContent.Artists
        )

        for artist in artist.getRelatedArtists():
            self.add_artist_to_directory(artist, oc)

        return oc

    #
    # ALBUM DETAIL
    #

    @authenticated
    @check_restart
    def album(self, uri):
        Log("album")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        album = self.client.get(uri)

        oc = ObjectContainer(
            title2=album.getName().decode("utf-8"),
            content=ContainerContent.Artists
        )

        oc.add(DirectoryObject(
                    key  = route_path('album/%s/tracks' % uri),
                    title=L("MENU_ALBUM_TRACKS"),
                    thumb=R("icon-album-tracks.png")))

        artists = album.getArtists()
        for artist in artists:
            self.add_artist_to_directory(artist, oc)

        oc.add(DirectoryObject(
                    key=route_path('radio/stations/' + uri),
                    title =L("MENU_RADIO"),
                    thumb =R("icon-radio-custom.png")))

        return oc

    @authenticated
    @check_restart
    def album_tracks(self, uri):
        Log("album_tracks")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")

        album = self.client.get(uri)

        oc = ObjectContainer(
            title2=album.getName().decode("utf-8"),
            content=ContainerContent.Tracks,
            view_group=ViewMode.Tracks
        )

        for track in album.getTracks():
            self.add_track_to_directory(track, oc)

        return oc

    #
    # PLAYLIST DETAIL
    #

    @authenticated
    @check_restart
    def playlist(self, uri):
        Log("playlist")

        uri = urllib.quote(uri.encode("utf8")).replace("%3A", ":").decode("utf8")
        
        pl = self.client.get(uri)
        if pl is None:
            # Unable to find playlist
            return MessageContainer(
                header=L("MSG_TITLE_UNKNOWN_PLAYLIST"),
                message='URI: %s' % uri
            )

        Log("Get playlist: %s", pl.getName().decode("utf-8"))
        Log.Debug('playlist truncated: %s', pl.obj.contents.truncated)

        oc = ObjectContainer(
            title2=pl.getName().decode("utf-8"),
            content=ContainerContent.Tracks,
            view_group=ViewMode.Tracks,
            mixed_parents=True
        )

        for x, track in enumerate(pl.getTracks()):
            self.add_track_to_directory(track, oc, index=x)

        return oc

    #
    # MAIN MENU
    #
    def main_menu(self):
        Log("main_menu")

        return ObjectContainer(
            objects=[
                InputDirectoryObject(
                    key=route_path('search'),
                    prompt=L("PROMPT_SEARCH"),
                    title=L("MENU_SEARCH"),
                    thumb=R("icon-search.png")
                ),
                DirectoryObject(
                    key=route_path('explore'),
                    title=L("MENU_EXPLORE"),
                    thumb=R("icon-explore.png")
                ),
                DirectoryObject(
                    key=route_path('discover'),
                    title=L("MENU_DISCOVER"),
                    thumb=R("icon-discover.png")
                ),
                DirectoryObject(
                    key=route_path('radio'),
                    title=L("MENU_RADIO"),
                    thumb=R("icon-radio.png")
                ),
                DirectoryObject(
                    key=route_path('your_music'),
                    title=L("MENU_YOUR_MUSIC"),
                    thumb=R("icon-yourmusic.png")
                ),
                PrefsObject(
                    title=L("MENU_PREFS"),
                    thumb=R("icon-preferences.png")
                )
            ],
        )

    #
    # Create objects
    #
    def create_track_object_from_track(self, track, index=None):
        if not track:
            return None

        # Get metadata info
        track_uri       = track.getURI()
        title           = track.getName().decode("utf-8")
        image_url       = self.select_image(track.getAlbumCovers())
        track_duration  = int(track.getDuration()) - 500
        track_number    = int(track.getNumber())
        track_album     = track.getAlbum(nameOnly=True).decode("utf-8")
        track_artists   = track.getArtists(nameOnly=True).decode("utf-8")
        metadata = TrackMetadata(title, image_url, track_uri, track_duration, track_number, track_album, track_artists)

        return self.create_track_object_from_metatada(metadata, index=index)

    def create_track_object_from_metatada(self, metadata, index=None):
        if not metadata:
            return None
        return self.create_track_object(metadata.uri, metadata.duration, metadata.title, metadata.album, metadata.artists, metadata.number, metadata.image_url, index)

    def create_track_object_empty(self, uri):
        if not uri:
            return None
        return self.create_track_object(uri, -1, "", "", "", 0, None)

    def create_track_object(self, uri, duration, title, album, artists, track_number, image_url, index=None):
        rating_key = uri
        if index is not None:
            rating_key = '%s::%s' % (uri, index)

        art_num = str(randint(1,40)).rjust(2, "0")

        track_obj = TrackObject(
            items=[
                MediaObject(
                    parts=[PartObject(key=route_path('play/%s' % uri))],
                    duration=duration,
                    container=Container.MP3, audio_codec=AudioCodec.MP3, audio_channels = 2
                )
            ],

            key = route_path('metadata', uri),
            rating_key = rating_key,

            title  = title,
            album  = album,
            artist = artists,

            index    = index if index != None else track_number,
            duration = duration,

            source_title='Spotify',
            art   = R('art-' + art_num + '.png'),
            thumb = function_path('image.png', uri=image_url)
        )

        Log.Debug('New track object for metadata: --|%s|%s|%s|%s|%s|%s|--' % (image_url, uri, str(duration), str(track_number), album, artists))

        return track_obj

    def create_album_object(self, album, custom_summary=None, custom_image_url=None):
        """ Factory method for album objects """
        title = album.getName().decode("utf-8")
        if Prefs["displayAlbumYear"] and album.getYear() != 0:
            title = "%s (%s)" % (title, album.getYear())
        artist_name = album.getArtists(nameOnly=True).decode("utf-8")
        summary     = '' if custom_summary == None else custom_summary.decode('utf-8')
        image_url   = self.select_image(album.getCovers()) if custom_image_url == None else custom_image_url

        return DirectoryObject(
            key=route_path('album', album.getURI()),

            title=title + " - " + artist_name,
            tagline=artist_name,
            summary=summary,

            art=function_path('image.png', uri=image_url),
            thumb=function_path('image.png', uri=image_url),
        )

        #return AlbumObject(
        #    key=route_path('album', album.getURI().decode("utf-8")),
        #    rating_key=album.getURI().decode("utf-8"),
        #
        #    title=title,
        #    artist=artist_name,
        #    summary=summary,
        #
        #    track_count=album.getNumTracks(),
        #    source_title='Spotify',
        #
        #    art=function_path('image.png', uri=image_url),
        #    thumb=function_path('image.png', uri=image_url),
        #)

    def create_playlist_object(self, playlist):
        uri         = playlist.getURI()
        image_url   = self.select_image(playlist.getImages())
        artist      = playlist.getUsername().decode('utf8')
        title       = playlist.getName().decode("utf-8")
        summary     = ''
        if playlist.getDescription() != None and len(playlist.getDescription()) > 0:
            summary = playlist.getDescription().decode("utf-8")

        return DirectoryObject(
            key=route_path('playlist', uri),

            title=title + " - " + artist,
            tagline=artist,
            summary=summary,

            art=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png"),
            thumb=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png")
        )

        #return AlbumObject(
        #    key=route_path('playlist', uri),
        #    rating_key=uri,
        #
        #    title=title,
        #    artist=artist,
        #    summary=summary,
        #
        #    source_title='Spotify',
        #
        #    art=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png"),
        #    thumb=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png")
        #)

    def create_genre_object(self, genre):
        uri         = genre.getTemplateName()
        title       = genre.getName().decode("utf-8")
        image_url   = genre.getIconUrl()

        return DirectoryObject(
            key=route_path('genre', uri),

            title=title,

            art=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png"),
            thumb=function_path('image.png', uri=image_url) if image_url != None else R("placeholder-playlist.png")
        )

    def create_artist_object(self, artist, custom_summary=None, custom_image_url=None):
        image_url   = self.select_image(artist.getPortraits()) if custom_image_url == None else custom_image_url
        artist_name = artist.getName().decode("utf-8")
        summary     = '' if custom_summary == None else custom_summary.decode('utf-8')

        return DirectoryObject(
                    key=route_path('artist', artist.getURI()),

                    title=artist_name,
                    summary=summary,

                    art=function_path('image.png', uri=image_url),
                    thumb=function_path('image.png', uri=image_url)
                )

        #return ArtistObject(
        #        key=route_path('artist', artist.getURI().decode("utf-8")),
        #        rating_key=artist.getURI().decode("utf-8"),
        #
        #        title=artist_name,
        #        summary=summary,
        #        source_title='Spotify',
        #
        #        art=function_path('image.png', uri=image_url),
        #        thumb=function_path('image.png', uri=image_url)
        #    )

    #
    # Insert objects into container
    #

    def add_section_header(self, title, oc):
        oc.add(
            DirectoryObject(
                key='',
                title=title
            )
        )

    def add_track_to_directory(self, track, oc, index = None):
        if not self.client.is_track_playable(track):
            Log("Ignoring unplayable track: %s" % track.getName())
            return

        track_uri = track.getURI().decode("utf-8")
        if not self.client.is_track_uri_valid(track_uri):
            Log("Ignoring unplayable track: %s, invalid uri: %s" % (track.getName(), track_uri))
            return

        oc.add(self.create_track_object_from_track(track, index=index))

    def add_album_to_directory(self, album, oc, custom_summary=None, custom_image_url=None):
        if not self.client.is_album_playable(album):
            Log("Ignoring unplayable album: %s" % album.getName())
            return
        oc.add(self.create_album_object(album, custom_summary=custom_summary, custom_image_url=custom_image_url))

    def add_artist_to_directory(self, artist, oc, custom_summary=None, custom_image_url=None):
        oc.add(self.create_artist_object(artist, custom_summary=custom_summary, custom_image_url=custom_image_url))

    def add_playlist_to_directory(self, playlist, oc):
        oc.add(self.create_playlist_object(playlist))

    def add_genre_to_directory(self, genre, oc):
        oc.add(self.create_genre_object(genre))

    def add_story_to_directory(self, story, oc):
        content_type = story.getContentType()
        image_url    = self.select_image(story.getImages())
        item         = story.getObject()
        if content_type == 'artist':
            self.add_artist_to_directory(item, oc, custom_summary=story.getDescription(), custom_image_url=image_url)
        elif content_type == 'album':
            self.add_album_to_directory(item,  oc, custom_summary=story.getDescription(), custom_image_url=image_url)
        elif content_type == 'track':
            self.add_album_to_directory(item.getAlbum(), oc, custom_summary=story.getDescription() + " - " + item.getName(), custom_image_url=image_url)
コード例 #11
0
ファイル: host.py プロジェクト: jetmets/Spotify2.bundle
class SpotifyHost(object):
    def __init__(self):
        self.client = None
        self.server = None

        self.messages = []
        self.start()

        self.search = SpotifySearch(self)

        self.session = requests.session()
        self.session_cached = CacheControl(self.session)

        self.containers = Containers(self)

        # Server detail
        self.server_name = None
        self.server_address = None
        self.server_version = None

        self.local_address = None

        # Private
        self.credits_data = None

    @property
    def username(self):
        return Prefs["username"]

    @property
    def password(self):
        return Prefs["password"]

    @property
    def proxy_tracks(self):
        return Prefs['proxy_tracks']

    @property
    def hostname(self):
        if Prefs['proxy_hostname']:
            # Custom hostname defined in preferences
            return Prefs['proxy_hostname']

        if self.local_address:
            # Hostname identified from <socket>.getsockname()
            return self.local_address

        if self.server_address:
            # Hostname identified from Plex API
            return self.server_address

        # Fallback to socket hostname
        return socket.gethostname()

    @property
    def sp(self):
        if not self.client:
            return None

        return self.client.sp

    @property
    def code_path(self):
        return Core.code_path

    @property
    def bundle_path(self):
        return os.path.abspath(os.path.join(self.code_path, '..'))

    @property
    def credits(self):
        if not self.credits_data:
            try:
                # Parse credits file
                self.credits_data = json.loads(Resource.Load('credits.json'))
            except (ValueError, TypeError):
                # Invalid credits file
                self.credits_data = {}

        return self.credits_data

    def preferences_updated(self):
        # Update logging levels
        logging_handler.setup()

        # Trigger a client restart
        self.start()

    def start(self):
        self.messages = []

        if not self.username or not self.password:
            self.messages.append((logging.ERROR, 'Username or Password not entered'))
            Log.Error('Username or Password not entered')
            return

        Log.Debug('bundle_path: "%s"', self.bundle_path)

        if not self.client:
            self.client = SpotifyClient(self)

        # Start server (if 'proxy_tracks' is enabled)
        if not self.server and self.proxy_tracks:
            self.server = Server(self)
            self.server.start()

        # Stop server if 'proxy_tracks' has been disabled
        if self.server and not self.proxy_tracks:
            self.server.stop()
            self.server = None

        # Update server preferences
        if self.server:
            self.server.supports_ranges = PREF_SS_RANGES.get(Prefs['proxy_ranges'], True)

        # Update reference on SpotifyClient
        self.client.server = self.server

        # start/restart the client
        self.client.start()

    def get(self, url, *args, **kwargs):
        try:
            return self.session.get(url, *args, **kwargs)
        except:
            return None

    def get_xml(self, url, *args, **kwargs):
        response = self.session.get(url, *args, **kwargs)
        if not response:
            return None

        return parse_xml(response.content)

    def refresh(self):
        self.refresh_server()
        self.refresh_local()

        Log.Info('Using the host/address "%s" for streaming', self.hostname)

    def refresh_server(self):
        Log.Debug('Refreshing server info...')

        # Determine local server name
        detail = self.get_xml('http://127.0.0.1:32400')

        if not detail:
            Log.Warn('"/" request failed, unable to retrieve info')
            return None

        self.server_name = detail.get('friendlyName')

        # Find server address and version
        servers = self.get_xml('http://127.0.0.1:32400/servers')

        if not servers:
            Log.Warn('"/servers" request failed, unable to retrieve server info')
            return None

        for server in servers.findall('Server'):
            if server.get('name').lower() == self.server_name.lower():
                self.server_address = server.get('address')
                self.server_version = server.get('version')
                break

        Log.Debug(
            'Updated server info - name: %s, address: %s, version: %s',
            self.server_name,
            self.server_address,
            self.server_version
        )

    def refresh_local(self):
        try:
            s_discovery = socket.socket(type=socket.SOCK_DGRAM)
            s_discovery.connect(('spotify.com', 80))

            netloc = s_discovery.getsockname()
            s_discovery.close()

            if len(netloc) != 2:
                self.local_address = None
                Log.Warn('Invalid response from getsockname(): %s', netloc)
                return

            self.local_address, _ = netloc
            Log.Debug('Updated local info - address: %s', self.local_address)
        except Exception, ex:
            self.local_address = None
            Log.Warn('Unable to discover local address - %s', ex)
コード例 #12
0
class SpotifyPlugin(RunLoopMixin):
    ''' The main spotify plugin class '''
    def __init__(self, ioloop):
        self.ioloop = ioloop
        self.client = None
        self.server = None
        self.browsers = {}
        self.start()

    @property
    def username(self):
        return Prefs["username"]

    @property
    def password(self):
        return Prefs["password"]

    def preferences_updated(self):
        ''' Called when the user updates the plugin preferences

        Note: if a user changes the username and password and we have an
        existing client we need to restart the plugin to use the new details.
        libspotify doesn't play nice with username and password changes.
        '''
        if not self.client:
            self.start()
        elif self.client.needs_restart(self.username, self.password):
            self.restart()
        else:
            Log("User details unchanged")

    def restart(self):
        ''' Restart the plugin to pick up new authentication details

        Note: don't restart inline since it will make the framework barf.
        Instead schedule a callback on the ioloop's next tick
        '''
        Log("Restarting plugin")
        if self.client:
            self.client.disconnect()
        self.schedule_timer(0.2, lambda: urlopen(RESTART_URL))

    def start(self):
        ''' Start the Spotify client and HTTP server '''
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return
        self.client = SpotifyClient(self.username, self.password, self.ioloop)
        self.client.connect()
        self.server = SpotifyServer(self.client)
        self.server.start()

    def play_track(self, uri):
        ''' Play a spotify track: redirect the user to the actual stream '''
        if not uri:
            Log("Play track callback invoked with NULL URI")
            return
        track_url = self.server.get_track_url(uri)
        Log("Redirecting client to stream proxied at: %s" % track_url)
        return Redirect(track_url)

    def create_track_object(self, track):
        ''' Factory for track directory objects '''
        album_uri = str(Link.from_album(track.album()))
        track_uri = str(Link.from_track(track, 0))
        thumbnail_url = self.server.get_art_url(album_uri)
        callback = Callback(self.play_track, uri=track_uri, ext="aiff")
        artists = (a.name().decode("utf-8") for a in track.artists())
        return TrackObject(
            items=[MediaObject(parts=[PartObject(key=callback)], )],
            key=track.name().decode("utf-8"),
            rating_key=track.name().decode("utf-8"),
            title=track.name().decode("utf-8"),
            album=track.album().name().decode("utf-8"),
            artist=", ".join(artists),
            index=track.index(),
            duration=int(track.duration()),
            thumb=thumbnail_url)

    def create_album_object(self, album):
        ''' Factory method for album objects '''
        album_uri = str(Link.from_album(album))
        title = album.name().decode("utf-8")
        if Prefs["displayAlbumYear"] and album.year() != 0:
            title = "%s (%s)" % (title, album.year())
        return DirectoryObject(key=Callback(self.get_album_tracks,
                                            uri=album_uri),
                               title=title,
                               thumb=self.server.get_art_url(album_uri))

    def add_track_to_directory(self, track, directory):
        if not self.client.is_track_playable(track):
            Log("Ignoring unplayable track: %s" % track.name())
            return
        directory.add(self.create_track_object(track))

    def add_album_to_directory(self, album, directory):
        if not self.client.is_album_playable(album):
            Log("Ignoring unplayable album: %s" % album.name())
            return
        directory.add(self.create_album_object(album))

    def add_artist_to_directory(self, artist, directory):
        artist_uri = str(Link.from_artist(artist))
        directory.add(
            DirectoryObject(key=Callback(self.get_artist_albums,
                                         uri=artist_uri),
                            title=artist.name().decode("utf-8"),
                            thumb=R("placeholder-artist.png")))

    @authenticated
    def get_playlist(self, folder_id, index):
        playlists = self.client.get_playlists(folder_id)
        if len(playlists) < index + 1:
            return MessageContainer(header=L("MSG_TITLE_PLAYLIST_ERROR"),
                                    message=L("MSG_BODY_PLAYIST_ERROR"))
        playlist = playlists[index]
        tracks = list(playlist)
        Log("Get playlist: %s", playlist.name().decode("utf-8"))
        directory = ObjectContainer(title2=playlist.name().decode("utf-8"),
                                    view_group=ViewMode.Tracks)
        for track in assert_loaded(tracks):
            self.add_track_to_directory(track, directory)
        return directory

    @authenticated
    def get_artist_albums(self, uri, completion):
        ''' Browse an artist invoking the completion callback when done.

        :param uri:            The Spotify URI of the artist to browse.
        :param completion:     A callback to invoke with results when done.
        '''
        artist = Link.from_string(uri).as_artist()

        def browse_finished(browser):
            del self.browsers[uri]
            albums = browser.albums()
            directory = ObjectContainer(title2=artist.name().decode("utf-8"),
                                        view_group=ViewMode.Tracks)
            for album in albums:
                self.add_album_to_directory(album, directory)
            completion(directory)

        self.browsers[uri] = self.client.browse_artist(artist, browse_finished)

    @authenticated
    def get_album_tracks(self, uri, completion):
        ''' Browse an album invoking the completion callback when done.

        :param uri:            The Spotify URI of the album to browse.
        :param completion:     A callback to invoke with results when done.
        '''
        album = Link.from_string(uri).as_album()

        def browse_finished(browser):
            del self.browsers[uri]
            tracks = list(browser)
            directory = ObjectContainer(title2=album.name().decode("utf-8"),
                                        view_group=ViewMode.Tracks)
            for track in tracks:
                self.add_track_to_directory(track, directory)
            completion(directory)

        self.browsers[uri] = self.client.browse_album(album, browse_finished)

    @authenticated
    def get_playlists(self, folder_id=0):
        Log("Get playlists")
        directory = ObjectContainer(title2=L("MENU_PREFS"),
                                    view_group=ViewMode.Playlists)
        playlists = self.client.get_playlists(folder_id)
        for playlist in playlists:
            index = playlists.index(playlist)
            if playlist.type() in [
                    'folder_start', 'folder_end', 'placeholder'
            ]:
                callback = Callback(self.get_playlists,
                                    folder_id=playlist.id())
            else:
                callback = Callback(self.get_playlist,
                                    folder_id=folder_id,
                                    index=index)
            directory.add(
                DirectoryObject(key=callback,
                                title=playlist.name().decode("utf-8"),
                                thumb=R("placeholder-playlist.png")))
        return directory

    @authenticated
    def get_starred_tracks(self):
        ''' Return a directory containing the user's starred tracks'''
        Log("Get starred tracks")
        directory = ObjectContainer(title2=L("MENU_STARRED"),
                                    view_group=ViewMode.Tracks)
        starred = list(self.client.get_starred_tracks())
        for track in starred:
            self.add_track_to_directory(track, directory)
        return directory

    @authenticated
    def search(self, query, completion, artists=False, albums=False):
        ''' Search asynchronously invoking the completion callback when done.

        :param query:          The query string to use.
        :param completion:     A callback to invoke with results when done.
        :param artists:        Determines whether artist matches are returned.
        :param albums:         Determines whether album matches are returned.
        '''
        params = "%s: %s" % ("artists" if artists else "albums", query)
        Log("Search for %s" % params)

        def search_finished(results, userdata):
            Log("Search completed: %s" % params)
            result = ObjectContainer(title2="Results")
            for artist in results.artists() if artists else ():
                self.add_artist_to_directory(artist, result)
            for album in results.albums() if albums else ():
                self.add_album_to_directory(album, result)
            if not len(result):
                if len(results.did_you_mean()):
                    message = localized_format("MSG_FMT_DID_YOU_MEAN",
                                               results.did_you_mean())
                else:
                    message = localized_format("MSG_FMT_NO_RESULTS", query)
                result = MessageContainer(header=L("MSG_TITLE_NO_RESULTS"),
                                          message=message)
            completion(result)

        self.client.search(query, search_finished)

    @authenticated
    def search_menu(self):
        Log("Search menu")
        return ObjectContainer(
            title2=L("MENU_SEARCH"),
            objects=[
                InputDirectoryObject(key=Callback(self.search, albums=True),
                                     prompt=L("PROMPT_ALBUM_SEARCH"),
                                     title=L("MENU_ALBUM_SEARCH"),
                                     thumb=R("icon-default.png")),
                InputDirectoryObject(key=Callback(self.search, artists=True),
                                     prompt=L("PROMPT_ARTIST_SEARCH"),
                                     title=L("MENU_ARTIST_SEARCH"),
                                     thumb=R("icon-default.png"))
            ],
        )

    def main_menu(self):
        Log("Spotify main menu")
        return ObjectContainer(objects=[
            DirectoryObject(key=Callback(self.get_playlists),
                            title=L("MENU_PLAYLISTS"),
                            thumb=R("icon-default.png")),
            DirectoryObject(key=Callback(self.search_menu),
                            title=L("MENU_SEARCH"),
                            thumb=R("icon-default.png")),
            DirectoryObject(key=Callback(self.get_starred_tracks),
                            title=L("MENU_STARRED"),
                            thumb=R("icon-default.png")),
            PrefsObject(title=L("MENU_PREFS"), thumb=R("icon-default.png"))
        ], )
コード例 #13
0
ファイル: plugin.py プロジェクト: jarlebh/Spotify.bundle
class SpotifyPlugin(RunLoopMixin):
    ''' The main spotify plugin class '''

    def __init__(self, ioloop):
        self.ioloop = ioloop
        self.client = None
        self.server = None
        self.browsers = {}
        self.start()

    @property
    def username(self):
        return Prefs["username"]

    @property
    def password(self):
        return Prefs["password"]

    def preferences_updated(self):
        ''' Called when the user updates the plugin preferences

        Note: if a user changes the username and password and we have an
        existing client we need to restart the plugin to use the new details.
        libspotify doesn't play nice with username and password changes.
        '''
        if not self.client:
            self.start()
        elif self.client.needs_restart(self.username, self.password):
            self.restart()
        else:
            Log("User details unchanged")

    def restart(self):
        ''' Restart the plugin to pick up new authentication details

        Note: don't restart inline since it will make the framework barf.
        Instead schedule a callback on the ioloop's next tick
        '''
        Log("Restarting plugin")
        if self.client:
            self.client.disconnect()
        self.schedule_timer(0.2, lambda: urlopen(RESTART_URL))

    def start(self):
        ''' Start the Spotify client and HTTP server '''
        if not self.username or not self.password:
            Log("Username or password not set: not logging in")
            return
        self.client = SpotifyClient(self.username, self.password, self.ioloop)
        self.client.connect()
        self.server = SpotifyServer(self.client)
        self.server.start()

    def play_track(self, uri):
        ''' Play a spotify track: redirect the user to the actual stream '''
        if not uri:
            Log("Play track callback invoked with NULL URI")
            return
        track_url = self.server.get_track_url(uri)
        Log("Redirecting client to stream proxied at: %s" % track_url)
        return Redirect(track_url)

    def create_track_object(self, track):
        ''' Factory for track directory objects '''
        album_uri = str(Link.from_album(track.album()))
        track_uri = str(Link.from_track(track, 0))
        thumbnail_url = self.server.get_art_url(album_uri)
        callback = Callback(self.play_track, uri = track_uri, ext = "aiff")
        artists = (a.name().decode("utf-8") for a in track.artists())
        return TrackObject(
            items = [
                MediaObject(
                    parts = [PartObject(key = callback)],
                )
            ],
            key = track.name().decode("utf-8"),
            rating_key = track.name().decode("utf-8"),
            title = track.name().decode("utf-8"),
            album = track.album().name().decode("utf-8"),
            artist = ", ".join(artists),
            index = track.index(),
            duration = int(track.duration()),
            thumb = thumbnail_url
       )

    def create_album_object(self, album):
        ''' Factory method for album objects '''
        album_uri = str(Link.from_album(album))
        title = album.name().decode("utf-8")
        if Prefs["displayAlbumYear"] and album.year() != 0:
            title = "%s (%s)" % (title, album.year())
        return DirectoryObject(
            key = Callback(self.get_album_tracks, uri = album_uri),
            title = title,
            thumb = self.server.get_art_url(album_uri)
        )

    def add_track_to_directory(self, track, directory):
        if not self.client.is_track_playable(track):
            Log("Ignoring unplayable track: %s" % track.name())
            return
        directory.add(self.create_track_object(track))

    def add_album_to_directory(self, album, directory):
        if not self.client.is_album_playable(album):
            Log("Ignoring unplayable album: %s" % album.name())
            return
        directory.add(self.create_album_object(album))

    def add_artist_to_directory(self, artist, directory):
        artist_uri = str(Link.from_artist(artist))
        directory.add(
            DirectoryObject(
                key = Callback(self.get_artist_albums, uri = artist_uri),
                title = artist.name().decode("utf-8"),
                thumb = R("placeholder-artist.png")
            )
        )

    @authenticated
    def get_playlist(self, folder_id, index):
        playlists = self.client.get_playlists(folder_id)
        if len(playlists) < index + 1:
            return MessageContainer(
                header = L("MSG_TITLE_PLAYLIST_ERROR"),
                message = L("MSG_BODY_PLAYIST_ERROR")
            )
        playlist = playlists[index]
        tracks = list(playlist)
        Log("Get playlist: %s", playlist.name().decode("utf-8"))
        directory = ObjectContainer(
            title2 = playlist.name().decode("utf-8"),
            view_group = ViewMode.Tracks)
        for track in assert_loaded(tracks):
            self.add_track_to_directory(track, directory)
        return directory

    @authenticated
    def get_artist_albums(self, uri, completion):
        ''' Browse an artist invoking the completion callback when done.

        :param uri:            The Spotify URI of the artist to browse.
        :param completion:     A callback to invoke with results when done.
        '''
        artist = Link.from_string(uri).as_artist()
        def browse_finished(browser):
            del self.browsers[uri]
            albums = list(browser)
            directory = ObjectContainer(
                title2 = artist.name().decode("utf-8"),
                view_group = ViewMode.Tracks)
            for album in albums:
                self.add_album_to_directory(album, directory)
            completion(directory)
        self.browsers[uri] = self.client.browse_artist(artist, browse_finished)

    @authenticated
    def get_album_tracks(self, uri, completion):
        ''' Browse an album invoking the completion callback when done.

        :param uri:            The Spotify URI of the album to browse.
        :param completion:     A callback to invoke with results when done.
        '''
        album = Link.from_string(uri).as_album()
        def browse_finished(browser):
            del self.browsers[uri]
            tracks = list(browser)
            directory = ObjectContainer(
                title2 = album.name().decode("utf-8"),
                view_group = ViewMode.Tracks)
            for track in tracks:
                self.add_track_to_directory(track, directory)
            completion(directory)
        self.browsers[uri] = self.client.browse_album(album, browse_finished)

    @authenticated
    def get_playlists(self, folder_id = 0):
        Log("Get playlists")
        directory = ObjectContainer(
            title2 = L("MENU_PREFS"),
            view_group = ViewMode.Playlists)
        playlists = self.client.get_playlists(folder_id)
        for playlist in playlists:
            index = playlists.index(playlist)
            if isinstance(playlist, PlaylistFolder):
                callback = Callback(
                    self.get_playlists, folder_id = playlist.id())
            else:
                callback = Callback(
                    self.get_playlist, folder_id = folder_id, index = index)
            directory.add(
                DirectoryObject(
                    key = callback,
                    title = playlist.name().decode("utf-8"),
                    thumb = R("placeholder-playlist.png")
                )
            )
        return directory

    @authenticated
    def get_starred_tracks(self):
        ''' Return a directory containing the user's starred tracks'''
        Log("Get starred tracks")
        directory = ObjectContainer(
            title2 = L("MENU_STARRED"),
            view_group = ViewMode.Tracks)
        starred = list(self.client.get_starred_tracks())
        for track in starred:
            self.add_track_to_directory(track, directory)
        return directory

    @authenticated
    def search(self, query, completion, artists = False, albums = False):
        ''' Search asynchronously invoking the completion callback when done.

        :param query:          The query string to use.
        :param completion:     A callback to invoke with results when done.
        :param artists:        Determines whether artist matches are returned.
        :param albums:         Determines whether album matches are returned.
        '''
        params = "%s: %s" % ("artists" if artists else "albums", query)
        Log("Search for %s" % params)
        def search_finished(results, userdata):
            Log("Search completed: %s" % params)
            result = ObjectContainer(title2 = "Results")
            for artist in results.artists() if artists else ():
                self.add_artist_to_directory(artist, result)
            for album in results.albums() if albums else ():
                self.add_album_to_directory(album, result)
            if not len(result):
                if len(results.did_you_mean()):
                    message = localized_format(
                        "MSG_FMT_DID_YOU_MEAN", results.did_you_mean())
                else:
                    message = localized_format("MSG_FMT_NO_RESULTS", query)
                result = MessageContainer(
                    header = L("MSG_TITLE_NO_RESULTS"), message = message)
            completion(result)
        self.client.search(query, search_finished)

    @authenticated
    def search_menu(self):
        Log("Search menu")
        return ObjectContainer(
            title2 = L("MENU_SEARCH"),
            objects = [
                InputDirectoryObject(
                    key = Callback(self.search, albums = True),
                    prompt = L("PROMPT_ALBUM_SEARCH"),
                    title = L("MENU_ALBUM_SEARCH"),
                    thumb = R("icon-default.png")
                ),
                InputDirectoryObject(
                    key = Callback(self.search, artists = True),
                    prompt = L("PROMPT_ARTIST_SEARCH"),
                    title = L("MENU_ARTIST_SEARCH"),
                    thumb = R("icon-default.png")
                )
            ],
        )

    def main_menu(self):
        Log("Spotify main menu")
        return ObjectContainer(
            objects = [
                DirectoryObject(
                    key = Callback(self.get_playlists),
                    title = L("MENU_PLAYLISTS"),
                    thumb = R("icon-default.png")
                ),
                DirectoryObject(
                    key = Callback(self.search_menu),
                    title = L("MENU_SEARCH"),
                    thumb = R("icon-default.png")
                ),
                DirectoryObject(
                    key = Callback(self.get_starred_tracks),
                    title = L("MENU_STARRED"),
                    thumb = R("icon-default.png")
                ),
                PrefsObject(
                    title = L("MENU_PREFS"),
                    thumb = R("icon-default.png")
                )
            ],
        )
コード例 #14
0
import logging
import os
import time
from pprint import pformat

import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
import util
from client import SpotifyClient

util.set_logging_config()
api = SpotifyClient()


def extract_tracks(result):
    tracks = []
    while True:
        for item in result['items']:
            if not item['track']['is_local']:
                track_data = {
                    'id': item['track']['id'],
                    'name': item['track']['name'],
                    'added_at': item['added_at']
                }
                tracks.append(track_data)

        if not result['next']: break
        result = api.next(result)

    return tracks
コード例 #15
0
ファイル: spotapi.py プロジェクト: khafkaa/spotify-api
import time
import json
from datetime import datetime
from iter.accessories import fetch
from client import SpotifyClient

app = os.environ.get('spotify_app')
key = os.environ.get('spotify_key')
url = os.environ.get('app_redirect')
tkn = os.environ.get('csrf_token')
uri = os.environ.get('current_track_uri')

liked_tracks = os.environ.get('default_playlist')
authorization_file = os.environ.get('spotify_auth_file')

api = SpotifyClient(client=app, secret=key, csrf=tkn, redirect=url)
api.refresh = os.environ.get('spotify_access')


def create_tmp(path):
    """create tmp directory if it doesn't already exist"""
    if os.path.isdir(path):
        return 1
    try:
        os.mkdir(path)
        return 1

    except OSError as error:
        print(error)
        return 0
コード例 #16
0
def run():

    # set token
    client = SpotifyClient(os.getenv('token'))
    print(f"token is: {os.getenv('token')}")

    # # choose artists or tracks
    # choice = input("Return top 'artists' or 'tracks'?  ")
    # if not (choice == 'artists' or choice== 'tracks'):
    #     print("Term not recognized. Default: tracks.")
    #     choice = 'tracks'

    choice = 'tracks'

    if choice == 'tracks':
        # choose term/limit
        term = input(
            "Choose term 'long' (ever), 'medium' (1/2 year), or 'short' (month) :  "
        )
        if (term == 'long'): term = 'long_term'
        if (term == 'medium'): term = 'medium_term'
        if (term == 'short'): term = 'short_term'
        if not (term == 'long_term' or term == 'medium_term'
                or term == 'short_term'):
            print("Term not recognized. Default: short_term.")
            term = 'short_term'

        limit = input("How many items (max 50)?   ")
        try:
            if not (int(limit) > 0 and int(limit) < 51):
                print("Out of range. Default: 5.")
                limit = 5
        except:
            print("Invalid Input. Default: 5.")
            limit = 5

        # get tracks
        want_analysis = input("Print audio analysis (y,n)?  ")
        tracks = client.get_top_tracks(term, limit)

        print()
        print(
            'The following are your top tracks, starting with the most played')
        print()

        # create string of names like js array
        names = "const names = ["
        artists = "const artists = ["
        energies = "const energies = ["
        valences = "const valences = ["
        modes = "const modes = ["
        danceabilities = "const danceabilities = ["
        tempos = "const tempos = ["

        artworks = "const artworks = ["
        previews = "const previews = ["

        for track in tracks:
            names += ("\"" + track['name'] + "\"" + ', ')
            artists += ("\"" + track['artists'][0]['name'] + "\"" + ', ')

            print(f"'{track['name']}' by {track['artists'][0]['name']} ")

            features = client.get_analysis(track["id"])

            energies += (str(features['energy']) + ', ')
            valences += (str(features['valence']) + ', ')
            modes += (str(features['mode']) + ', ')
            danceabilities += (str(features['danceability']) + ', ')
            tempos += (str(features['tempo']) + ', ')

            if (want_analysis == 'y'):
                print(f"energy: {features['energy']}")
                print(f"valence: {features['valence']}")
                print(f"mode: {features['mode']}")
                print(f"danceability: {features['danceability']}")
                print(f"tempo: {features['tempo']}")
                print()

            # track artwork and preview
            track = client.get_track(track["id"])
            artworks += ("\"" + track['album']['images'][0]['url'] + "\"" +
                         ', ')
            if (track['preview_url'] == None):
                previews += ("\"" + " " + "\"" + ', ')
            else:
                previews += ("\"" + track['preview_url'] + "\"" + ', ')

        # remove last space and comms
        names = names[:-2]
        artists = artists[:-2]
        energies = energies[:-2]
        valences = valences[:-2]
        modes = modes[:-2]
        danceabilities = danceabilities[:-2]
        tempos = tempos[:-2]
        # lst commas weird but still fine
        artworks[:-2]
        previews[:-2]

        names += "]"
        artists += "]"
        energies += "]"
        valences += "]"
        modes += "]"
        danceabilities += "]"
        tempos += "]"
        artworks += "]"
        previews += "]"

        fname = 'track-data-' + term + '.js'
        # print(f'filemane: {fname}')

        f = open(fname, 'w')
        f.write(names)
        f.write("\n")
        f.write(artists)
        f.write("\n")
        f.write(energies)
        f.write("\n")
        f.write(valences)
        f.write("\n")
        f.write(modes)
        f.write("\n")
        f.write(danceabilities)
        f.write("\n")
        f.write(tempos)
        f.write("\n")
        f.write(artworks)
        f.write("\n")
        f.write(previews)
        f.write("\n")
        f.close()