コード例 #1
0
class Server(object):
    def __init__(self, port=12555):
        self.port = port

        self.sp = Spotify()
        self.cache = {}

        self.current = None
        self.on_login = Event()

    def start(self):
        # Spotify
        self.sp.login(os.environ['USERNAME'], os.environ['PASSWORD'],
                      lambda: self.on_login.set())

        # CherryPy
        cherrypy.config.update({
            'server.socket_host': '0.0.0.0',
            'server.socket_port': self.port
        })

        cherrypy.tree.mount(
            None, config={'/': {
                'request.dispatch': Dispatcher(self)
            }})

        cherrypy.engine.start()

    def album(self, uri):
        self.on_login.wait()  # Wait for login

        complete = Event()
        album_tracks = []

        # Fetch album metadata
        @self.sp.metadata(uri)
        def on_album(album):

            # Fetch metadata for each track
            @self.sp.metadata([tr.uri for tr in album.discs[0].tracks])
            def on_tracks(tracks):

                # Append onto 'tracks' list
                album_tracks.extend(tracks)

                # Work complete
                complete.set()

        # Wait until tracks are loaded (give up after 5 seconds)
        complete.wait(5)

        # Render template
        return env.get_template('album.html').render(
            tracks=[{
                'name': track.name,
                'uri': str(track.uri)
            } for track in album_tracks])

    def track(self, uri):
        self.on_login.wait()  # Wait for login

        # Update current
        if self.current:
            # Track changed, call finish()
            if uri != self.current.uri:
                log.debug(
                    'Changing tracks, calling finish() on previous track')
                self.current.finish()

        # Create new TrackReference (if one doesn't exist yet)
        if uri not in self.cache:
            log.debug('[%s] Creating new TrackReference' % uri)

            # Create new track reference
            self.cache[uri] = TrackReference(self, uri)

        # Get track reference from cache
        tr = self.cache[uri]

        # Start download
        tr.fetch()

        # Wait until track is ready
        tr.on_opened.wait(10)

        if not tr.success:
            self.current = None
            cherrypy.response.status = 500
            return

        # Update current
        self.current = tr

        r_start, r_end = self.handle_range(tr)

        # Update headers
        cherrypy.response.headers['Accept-Ranges'] = 'bytes'
        cherrypy.response.headers[
            'Content-Type'] = tr.response_headers.getheader('Content-Type')
        cherrypy.response.headers['Content-Length'] = r_end - r_start

        # Progressively return track from buffer
        return self.stream(tr, r_start, r_end)

    track._cp_config = {'response.stream': True}

    @staticmethod
    def stream(tr, r_start, r_end):
        position = r_start

        chunk_size_min = 6 * 1024
        chunk_size_max = 10 * 1024

        chunk_scale = 0
        chunk_size = chunk_size_min

        last_progress = None

        while True:
            # Adjust chunk_size
            if chunk_scale < 1:
                chunk_scale = 2 * (float(position) / tr.stream_length)
                chunk_size = int(chunk_size_min +
                                 (chunk_size_max * chunk_scale))

                if chunk_scale > 1:
                    chunk_scale = 1

            if position + chunk_size > r_end:
                chunk_size = r_end - position

            # Read chunk
            chunk = tr.read(position, chunk_size)

            if not chunk:
                log.debug('[%s] Finished at %s bytes (content-length: %s)' %
                          (tr.uri, position, tr.stream_length))
                break

            last_progress = log_progress(tr, '  Streaming', position,
                                         last_progress)

            position = position + len(chunk)

            # Write chunk
            yield chunk

        log.debug('[%s] Stream Complete', tr.uri)

    def handle_range(self, tr):
        r_start, r_end = self.parse_range(
            cherrypy.request.headers.get('Range'))

        if not r_start and not r_end:
            return 0, tr.stream_length - 1

        if r_end is None or r_end >= tr.stream_length:
            r_end = tr.stream_length - 1

        log.debug('[%s] Range: %s - %s', tr.uri, r_start, r_end)

        cherrypy.response.headers['Content-Range'] = 'bytes %s-%s/%s' % (
            r_start, r_end, tr.stream_length)
        cherrypy.response.status = 206

        return r_start, r_end

    @staticmethod
    def parse_range(value):
        value = value.split('=')

        if len(value) != 2:
            return 0, None

        range_type, range = value

        if range_type != 'bytes':
            return 0, None

        range = range.split('-')

        if len(range) != 2:
            return 0, None

        return int(range[0] or 0), int(range[1]) if range[1] else None

    def get_track_url(self, uri):
        return "http://%s:%d/track/%s.mp3" % (socket.gethostname(), self.port,
                                              uri)
コード例 #2
0
ファイル: server.py プロジェクト: novag/spotify.py
class Server(object):
    def __init__(self, port=12555):
        self.port = port

        self.sp = Spotify()
        self.cache = {}

        self.current = None
        self.on_login = Event()

    def start(self):
        # Spotify
        self.sp.login(os.environ['USERNAME'], os.environ['PASSWORD'], lambda: self.on_login.set())

        # CherryPy
        cherrypy.config.update({
            'server.socket_host': '0.0.0.0',
            'server.socket_port': self.port
        })

        cherrypy.tree.mount(None, config={
            '/': {
                'request.dispatch': Dispatcher(self)
            }
        })

        cherrypy.engine.start()

    def album(self, uri):
        self.on_login.wait()  # Wait for login

        complete = Event()
        album_tracks = []

        # Fetch album metadata
        @self.sp.metadata(uri)
        def on_album(album):

            # Fetch metadata for each track
            @self.sp.metadata([tr.uri for tr in album.discs[0].tracks])
            def on_tracks(tracks):

                # Append onto 'tracks' list
                album_tracks.extend(tracks)

                # Work complete
                complete.set()

        # Wait until tracks are loaded (give up after 5 seconds)
        complete.wait(5)

        # Render template
        return env.get_template('album.html').render(
            tracks=[
                {'name': track.name, 'uri': str(track.uri)}
                for track in album_tracks
            ]
        )

    def track(self, uri):
        self.on_login.wait()  # Wait for login

        # Update current
        if self.current:
            # Track changed, call finish()
            if uri != self.current.uri:
                log.debug('Changing tracks, calling finish() on previous track')
                self.current.finish()

        # Create new TrackReference (if one doesn't exist yet)
        if uri not in self.cache:
            log.debug('[%s] Creating new TrackReference' % uri)

            # Create new track reference
            self.cache[uri] = TrackReference(self, uri)

        # Get track reference from cache
        tr = self.cache[uri]

        # Start download
        tr.fetch()

        # Wait until track is ready
        tr.on_opened.wait(10)

        if not tr.success:
            self.current = None
            cherrypy.response.status = 500
            return

        # Update current
        self.current = tr

        # Update headers
        cherrypy.response.headers['Accept-Ranges'] = 'bytes'
        cherrypy.response.headers['Content-Type'] = tr.response_headers.getheader('Content-Type')
        cherrypy.response.headers['Content-Length'] = tr.response_headers.getheader('Content-Length')

        # Progressively return track from buffer
        return self.stream(tr, 0, cherrypy.response.headers['Content-Length'])

    track._cp_config = {'response.stream': True}

    @staticmethod
    def stream(tr, r_start, r_end):
        position = r_start

        chunk_size_min = 6 * 1024
        chunk_size_max = 10 * 1024

        chunk_scale = 0
        chunk_size = chunk_size_min

        last_progress = None

        while True:
            # Adjust chunk_size
            if chunk_scale < 1:
                chunk_scale = 2 * (float(position) / tr.stream_length)
                chunk_size = int(chunk_size_min + (chunk_size_max * chunk_scale))

                if chunk_scale > 1:
                    chunk_scale = 1

            if position + chunk_size > r_end:
                chunk_size = r_end - position

            # Read chunk
            chunk = tr.read(position, chunk_size)

            if not chunk:
                log.debug('[%s] Finished at %s bytes (content-length: %s)' % (tr.uri, position, tr.stream_length))
                break

            last_progress = log_progress(tr, '  Streaming', position, last_progress)

            position = position + len(chunk)

            # Write chunk
            yield chunk

        log.debug('[%s] Stream Complete', tr.uri)

    def get_track_url(self, uri):
        return "http://%s:%d/track/%s.mp3" % (
            socket.gethostname(), self.port, uri
        )