diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8c47c95 Binary files /dev/null and b/.DS_Store differ diff --git a/.syncedlyrics/musixmatch_token.json b/.syncedlyrics/musixmatch_token.json new file mode 100644 index 0000000..2818718 --- /dev/null +++ b/.syncedlyrics/musixmatch_token.json @@ -0,0 +1 @@ +{"token": "240425a54f5df91735a14b241dcfb1e5df28f942d95b9cad8ea6a4", "expiration_time": 1714086845} \ No newline at end of file diff --git a/__pycache__/_canvas.cpython-312.pyc b/__pycache__/_canvas.cpython-312.pyc new file mode 100644 index 0000000..2289500 Binary files /dev/null and b/__pycache__/_canvas.cpython-312.pyc differ diff --git a/_canvas.py b/_canvas.py new file mode 100755 index 0000000..5081141 --- /dev/null +++ b/_canvas.py @@ -0,0 +1,57 @@ +import requests +import random +from protos.canvas_pb2 import EntityCanvazRequest, EntityCanvazResponse + +API_HOST = "https://spclient.wg.spotify.com" +CANVAS_ROUTE = "/canvaz-cache/v0/canvases" +TOKEN_ENDPOINT = "https://open.spotify.com/get_access_token?reason=transport" +TOKEN_RENEW_TIME = 900 + +TRACK_URI_PREFIX = "spotify:track:" +OAUTH_SCOPES = "playlist-read" + +def get_access_token(): + try: + response = requests.get(TOKEN_ENDPOINT) + data = response.json() + return data["accessToken"] + except Exception as e: + raise Exception(e) + + +def get_canvas_for_track(access_token, track_id): + canvas_request = EntityCanvazRequest() + canvas_request_entities = canvas_request.entities.add() + canvas_request_entities.entity_uri = TRACK_URI_PREFIX + track_id + + try: + resp = requests.post( + API_HOST + CANVAS_ROUTE, + headers={ + "Accept": "application/protobuf", + "Content-Type": "application/x-www-form-urlencoded", + "Accept-Language": "en", + "User-Agent": "Spotify/8.5.49 iOS/Version 13.3.1 (Build 17D50)", + "Accept-Encoding": "gzip, deflate, br", + "Authorization": "Bearer %s" % access_token + }, + data=canvas_request.SerializeToString(), + ) + except: + raise ConnectionError + + canvas_response = EntityCanvazResponse() + canvas_response.ParseFromString(resp.content) + + if(len(canvas_response.canvases) == 0): + raise AttributeError + + canvas = random.choice(canvas_response.canvases) + + if canvas.url is not None: + return canvas.url + else: + return None + +# access_token = get_access_token() +# print(get_canvas_for_track(access_token, "1kADZJDyRUbmlLxYiqi077")) \ No newline at end of file diff --git a/code b/code index 7506e5f..7cf5f72 100644 --- a/code +++ b/code @@ -1 +1 @@ -AQBtjZ5AgmaU073h9RsK82t8w2XMeQ_HTSbqbAyd3PNVQe2YNnkM883WOcpbDfKt4ll2xu0LoUWEMX5sEUmtPSWCgFs1k_3GFUxtUJV8VslZUvgIYBUSSbqNWrxZqRtGJ-cL_Rdq6gbBKttHkkMdnZVeO2jUtJM2zCHSYEW49o-aoRpcMkzPm66_H88O9S8hVEegS7nG4uP0AUYwe0EiW6YMvFkCVG6XJ7AJpcO97U0tP0PhRbBrx1Y5lRAequ5YaY-BC4FFHwBcA8XePKvAnuDku0sbRd1vbw6dahcRdco \ No newline at end of file +AQDU_0NUgm9-EMYMDBYUTnBApWyYO6m4EDUExbHzjM_CcVkoGptsE69uePQ7ejw5NooRTCpQqIU7ZfOTfE5Nx8zj6amQBFRkgv7vMa5ViqCbH_hqos--D6mj-eG3jqW-sf-Oyq0vmPUaam6R0fB--ghWzM8EG8SRVQBGTnEN-OLf1PBX5I1sOS22GXrMud5BcFziAiYQxHn_GcnPXEyXigkHAsk60PFiWDUwW6fLdUKW2FtyJnnYH01al28eex1QEeqr2gg8e5ht9XGKfQej0ciFM62ESwIbpDZDi6bWdSk \ No newline at end of file diff --git a/protos/__pycache__/canvas_pb2.cpython-310.pyc b/protos/__pycache__/canvas_pb2.cpython-310.pyc new file mode 100755 index 0000000..b6a642e Binary files /dev/null and b/protos/__pycache__/canvas_pb2.cpython-310.pyc differ diff --git a/protos/__pycache__/canvas_pb2.cpython-312.pyc b/protos/__pycache__/canvas_pb2.cpython-312.pyc new file mode 100644 index 0000000..d276bc3 Binary files /dev/null and b/protos/__pycache__/canvas_pb2.cpython-312.pyc differ diff --git a/protos/canvas_pb2.py b/protos/canvas_pb2.py new file mode 100755 index 0000000..5f93d98 --- /dev/null +++ b/protos/canvas_pb2.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: canvas.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0c\x63\x61nvas.proto\x12\x17\x63om.spotify.canvazcache\"3\n\x06\x41rtist\x12\x0b\n\x03uri\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06\x61vatar\x18\x03 \x01(\t\"\xe6\x02\n\x14\x45ntityCanvazResponse\x12\x46\n\x08\x63\x61nvases\x18\x01 \x03(\x0b\x32\x34.com.spotify.canvazcache.EntityCanvazResponse.Canvaz\x12\x16\n\x0ettl_in_seconds\x18\x02 \x01(\x03\x1a\xed\x01\n\x06\x43\x61nvaz\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0f\n\x07\x66ile_id\x18\x03 \x01(\t\x12+\n\x04type\x18\x04 \x01(\x0e\x32\x1d.com.spotify.canvazcache.Type\x12\x12\n\nentity_uri\x18\x05 \x01(\t\x12/\n\x06\x61rtist\x18\x06 \x01(\x0b\x32\x1f.com.spotify.canvazcache.Artist\x12\x10\n\x08\x65xplicit\x18\x07 \x01(\x08\x12\x13\n\x0buploaded_by\x18\x08 \x01(\t\x12\x0c\n\x04\x65tag\x18\t \x01(\t\x12\x12\n\ncanvas_uri\x18\x0b \x01(\t\"\x88\x01\n\x13\x45ntityCanvazRequest\x12\x45\n\x08\x65ntities\x18\x01 \x03(\x0b\x32\x33.com.spotify.canvazcache.EntityCanvazRequest.Entity\x1a*\n\x06\x45ntity\x12\x12\n\nentity_uri\x18\x01 \x01(\t\x12\x0c\n\x04\x65tag\x18\x02 \x01(\t*R\n\x04Type\x12\t\n\x05IMAGE\x10\x00\x12\t\n\x05VIDEO\x10\x01\x12\x11\n\rVIDEO_LOOPING\x10\x02\x12\x18\n\x14VIDEO_LOOPING_RANDOM\x10\x03\x12\x07\n\x03GIF\x10\x04\x42\x16\n\x12\x63om.spotify.canvazH\x02\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'canvas_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\022com.spotify.canvazH\002' + _globals['_TYPE']._serialized_start=594 + _globals['_TYPE']._serialized_end=676 + _globals['_ARTIST']._serialized_start=41 + _globals['_ARTIST']._serialized_end=92 + _globals['_ENTITYCANVAZRESPONSE']._serialized_start=95 + _globals['_ENTITYCANVAZRESPONSE']._serialized_end=453 + _globals['_ENTITYCANVAZRESPONSE_CANVAZ']._serialized_start=216 + _globals['_ENTITYCANVAZRESPONSE_CANVAZ']._serialized_end=453 + _globals['_ENTITYCANVAZREQUEST']._serialized_start=456 + _globals['_ENTITYCANVAZREQUEST']._serialized_end=592 + _globals['_ENTITYCANVAZREQUEST_ENTITY']._serialized_start=550 + _globals['_ENTITYCANVAZREQUEST_ENTITY']._serialized_end=592 +# @@protoc_insertion_point(module_scope) diff --git a/spotifycontroller.py b/spotifycontroller.py index 7591d69..8017a60 100644 --- a/spotifycontroller.py +++ b/spotifycontroller.py @@ -1,11 +1,11 @@ from flask import Flask, render_template, request, url_for, redirect, send_from_directory -import time import requests from urllib.parse import urlencode -import webbrowser import base64 -import json import os +import _canvas as SpotifyCanvas +import time +import syncedlyrics app = Flask(__name__) @@ -88,6 +88,7 @@ def callback(): @app.route('/appdata') def appdata(): global access_token + stime = time.time() user_headers = { "Authorization": "Bearer " + access_token, "Content-Type": "application/json" @@ -96,9 +97,12 @@ def appdata(): currently_playing = requests.get("https://api.spotify.com/v1/me/player/currently-playing", headers=user_headers) if currently_playing.content: if currently_playing.json()["is_playing"] is True and currently_playing.json()["item"]["id"] == request.args.get('id'): - return { 'progress_ms': currently_playing.json()["progress_ms"] + print("QUICK" + str(time.time() - stime)) + return { 'progress_ms': currently_playing.json()["progress_ms"], } - elif currently_playing.json()["is_playing"] is True and currently_playing.json()["item"]["id"] != request.args.get('id'): + elif currently_playing.json()["is_playing"] is True and currently_playing.json()["item"]["id"] != request.args.get('id'): + + print("FULL" + str(time.time() - stime)) return { 'id': currently_playing.json()["item"]["id"], 'name': currently_playing.json()["item"]["name"], 'artist': currently_playing.json()["item"]["artists"][0]["name"], @@ -107,20 +111,19 @@ def appdata(): 'is_playing': currently_playing.json()["is_playing"], 'progress_ms': currently_playing.json()["progress_ms"], 'duration_ms': currently_playing.json()["item"]["duration_ms"], - 'is_liked': requests.get("https://api.spotify.com/v1/me/tracks/contains?ids=" + currently_playing.json()["item"]["id"], headers=user_headers).json()[0] + 'is_liked': requests.get("https://api.spotify.com/v1/me/tracks/contains?ids=" + currently_playing.json()["item"]["id"], headers=user_headers).json()[0], + 'canvas': False, + 'fetchlyrics': 'fetch' } elif currently_playing.json()["is_playing"] is False: - return { 'name': "Not Playing", - 'is_playing': False + return { 'is_playing': False } else: return { 'name': "Error", 'artist': "Error" } else: - return { 'name': "Not Playing", - 'image': "https://cdn.psychologytoday.com/sites/default/files/styles/article-inline-half-caption/public/field_blog_entry_images/2022-02/pause.png", - 'is_playing': False + return { 'is_playing': False } @app.route('/control', methods=['POST']) @@ -157,7 +160,26 @@ def control(): def icons(filename): return send_from_directory('icons', filename) - +@app.route('/canvas') +def canvas(): + print("CANVAS") + id = request.args.get('id') + try: + return { 'canvas_url': SpotifyCanvas.get_canvas_for_track(SpotifyCanvas.get_access_token(), id) } + except AttributeError: + return { 'canvas_url': None } + +@app.route('/lyrics') +def lyrics(): + name = request.args.get('name') + artist = request.args.get('artist') + if name and artist is not None: + full_lyrics = syncedlyrics.search("[" + name + "] [" + artist + "]") + else: + return { 'lyrics': '' } + if full_lyrics is None: + return { 'lyrics': "no lyrics" } + return { 'lyrics': full_lyrics } if __name__ == '__main__': app.run(port=8888, debug=True) \ No newline at end of file diff --git a/templates/webapp.html b/templates/webapp.html index 775743a..e099787 100644 --- a/templates/webapp.html +++ b/templates/webapp.html @@ -21,49 +21,72 @@ } .song-text { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - font-size: 20px; + font-size: 25px; font-weight: bold; text-align: center; - padding-left: 20px; - padding-right: 20px; + margin-bottom: 5px; } .artist-text { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - font-size: 15px; + font-size: 20px; text-align: center; - padding-left: 20px; - padding-right: 20px; + margin-top: 5px; } .middle { + flex: 1; text-align: center; display: inline-block; vertical-align: top; - padding-left: 20px; - padding-right: 20px; + } + .right { + height: 100%; + display: inline-block; + width: 33%; + } + .lyrics { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 32px; + text-align: center; + } + .buttons { + display: initial; + padding-top: 10%; + } + .total { + display: flex; + align-items: center; }
-