Skip to content

Commit

Permalink
Added favicon, toolbar view sort and filter, refactoring caches
Browse files Browse the repository at this point in the history
  • Loading branch information
blastbeng committed Sep 30, 2024
1 parent 220432a commit 937b864
Show file tree
Hide file tree
Showing 25 changed files with 410 additions and 212 deletions.
5 changes: 2 additions & 3 deletions spotisub/helpers/spotipy_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,14 @@ def create_sp_client():
"""Creates the spotipy client"""
secrets = get_secrets()
scope = "user-top-read,user-library-read,user-read-recently-played"
cache_path = os.path.abspath(os.curdir) + '/cache/spotipy_cache'
creds = SpotifyOAuth(
scope=scope,
client_id=secrets["client_id"],
client_secret=secrets["client_secret"],
redirect_uri=secrets["redirect_uri"],
open_browser=False,
cache_path=os.path.dirname(
os.path.abspath(__file__)) +
"/../../cache/spotipy_cache")
cache_path=cache_path)

return spotipy.Spotify(auth_manager=creds)

Expand Down
160 changes: 74 additions & 86 deletions spotisub/helpers/subsonic_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import os
import random
import time
import pickle
import threading
import libsonic
from expiringdict import ExpiringDict
from libsonic.errors import DataNotFoundError
from spotipy.exceptions import SpotifyException
from spotisub import spotisub
from spotisub import database
from spotisub import constants
Expand Down Expand Up @@ -54,47 +56,36 @@

# caches
playlist_cache = ExpiringDict(max_len=500, max_age_seconds=300)
spotify_playlist_cache = ExpiringDict(max_len=1000, max_age_seconds=3600)
spotify_artist_cache = ExpiringDict(max_len=1000, max_age_seconds=3600)
spotify_album_cache = ExpiringDict(max_len=1000, max_age_seconds=3600)
spotify_song_cache = ExpiringDict(max_len=1000, max_age_seconds=3600)


def get_spotify_artist_from_cache(sp, spotify_uri):
spotify_artist = None
if spotify_uri not in spotify_artist_cache:
spotify_artist = sp.artist(spotify_uri)
spotify_artist_cache[spotify_uri] = spotify_artist
else:
spotify_artist = spotify_artist_cache[spotify_uri]
return spotify_artist

def get_spotify_playlist_from_cache(sp, spotify_uri):
spotify_playlist = None
if spotify_uri not in spotify_playlist_cache:
spotify_playlist = sp.playlist(spotify_uri)
spotify_playlist_cache[spotify_uri] = spotify_playlist
else:
spotify_playlist = spotify_playlist_cache[spotify_uri]
return spotify_playlist

def get_spotify_album_from_cache(sp, spotify_uri):
spotify_album = None
if spotify_uri not in spotify_album_cache:
spotify_album = sp.album(spotify_uri)
spotify_album_cache[spotify_uri] = spotify_album
else:
spotify_album = spotify_album_cache[spotify_uri]
return spotify_album

def get_spotify_song_from_cache(sp, spotify_uri):
spotify_song = None
if spotify_uri not in spotify_song_cache:
spotify_song = sp.track(spotify_uri)
spotify_song_cache[spotify_uri] = spotify_song
spotify_cache = None

def load_spotify_cache_from_file():
object = ExpiringDict(max_len=10000, max_age_seconds=43200)
path = os.path.abspath(os.curdir) + '/cache/spotify_object_cache.pkl'
if os.path.exists(path):
with open(path, 'rb') as f:
old_cache_obj = pickle.load(f)
for key, value in old_cache_obj.items():
object[key] = value
return object

def save_spotify_cache_to_file(object):
path = os.path.abspath(os.curdir) + '/cache/spotify_object_cache.pkl'
with open(path, 'wb') as f:
pickle.dump(object, f)

def get_spotify_object_from_cache(sp, spotify_uri):
spotify_object = None
if spotify_uri not in spotify_cache:
try:
spotify_object = sp.artist(spotify_uri)
spotify_cache[spotify_uri] = spotify_object
save_spotify_cache_to_file(spotify_cache)
except SpotifyException:
pass
else:
spotify_song = spotify_song_cache[spotify_uri]
return spotify_song
spotify_object = spotify_cache[spotify_uri]
return spotify_object


def check_pysonic_connection():
"""Return SubsonicOfflineException if pysonic is offline"""
Expand Down Expand Up @@ -177,9 +168,11 @@ def add_missing_values_to_track(sp, track):
if "id" in track:
uri = 'spotify:track:' + track['id']
if "album" not in track or has_isrc(track) is False:
track = get_spotify_song_from_cache(sp, uri)
spotify_track = get_spotify_object_from_cache(sp, uri)
if spotify_track is not None:
track = spotify_track
time.sleep(1)
elif "uri" not in track:
if "uri" not in track:
track["uri"] = uri
return track
return None
Expand Down Expand Up @@ -474,19 +467,19 @@ def select_all_playlists(spotipy_helper, page=None,
songs = []

ids = []

for playlist in all_playlists:
playlist["image"] = ""
if playlist["subsonic_playlist_id"] not in ids:
ids.append(playlist["subsonic_playlist_id"])
if playlist["type"] == constants.JOB_ATT_ID or playlist["type"] == constants.JOB_AR_ID:
spotify_artist = get_spotify_artist_from_cache(spotipy_helper.get_spotipy_client(), playlist["spotify_playlist_uri"])
if "images" in spotify_artist and len(spotify_artist["images"]) > 0:
spotify_artist = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), playlist["spotify_playlist_uri"])
if spotify_artist is not None and "images" in spotify_artist and len(spotify_artist["images"]) > 0:
playlist["image"] = spotify_artist["images"][0]["url"]
elif playlist["type"] == constants.JOB_UP_ID:
spotify_playlist = get_spotify_playlist_from_cache(spotipy_helper.get_spotipy_client(), playlist["spotify_playlist_uri"])
if "images" in spotify_playlist and len(spotify_playlist["images"]) > 0:
spotify_playlist = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), playlist["spotify_playlist_uri"])
if spotify_playlist is not None and "images" in spotify_playlist and len(spotify_playlist["images"]) > 0:
playlist["image"] = spotify_playlist["images"][0]["url"]
else:
playlist["image"] = ""
prefix = os.environ.get(
constants.PLAYLIST_PREFIX,
constants.PLAYLIST_PREFIX_DEFAULT_VALUE).replace(
Expand Down Expand Up @@ -606,24 +599,23 @@ def load_artist(uuid, spotipy_helper, page=None,
uuid, page=page, limit=limit, order=order, asc=asc)
sp = None

spotify_artist = get_spotify_artist_from_cache(spotipy_helper.get_spotipy_client(), artist_db.spotify_uri)
spotify_artist = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), artist_db.spotify_uri)

if spotify_artist is None:
raise SpotifyDataException
artist = {}
artist["name"] = artist_db.name
artist["genres"] = ""
artist["url"] = ""
artist["image"] = ""
artist["popularity"] = ""
if "genres" in spotify_artist:
artist["genres"] = ", ".join(spotify_artist["genres"])
if "popularity" in spotify_artist:
artist["popularity"] = str(spotify_artist["popularity"]) + "%"
if "external_urls" in spotify_artist and "spotify" in spotify_artist["external_urls"]:
artist["url"] = spotify_artist["external_urls"]["spotify"]
if "images" in spotify_artist and len(spotify_artist["images"]) > 0:
artist["image"] = spotify_artist["images"][0]["url"]
if spotify_artist is not None:
if "genres" in spotify_artist:
artist["genres"] = ", ".join(spotify_artist["genres"])
if "popularity" in spotify_artist:
artist["popularity"] = str(spotify_artist["popularity"]) + "%"
if "external_urls" in spotify_artist and "spotify" in spotify_artist["external_urls"]:
artist["url"] = spotify_artist["external_urls"]["spotify"]
if "images" in spotify_artist and len(spotify_artist["images"]) > 0:
artist["image"] = spotify_artist["images"][0]["url"]
return artist, songs, count


Expand All @@ -633,21 +625,20 @@ def load_album(uuid, spotipy_helper, page=None,
uuid, page=page, limit=limit, order=order, asc=asc)
sp = None

spotify_album = get_spotify_album_from_cache(spotipy_helper.get_spotipy_client(), album_db.spotify_uri)
spotify_album = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), album_db.spotify_uri)

if spotify_album is None:
raise SpotifyDataException
album = {}
album["name"] = album_db.name
album["url"] = ""
album["image"] = ""
album["release_date"] = ""
if "release_date" in spotify_album:
album["release_date"] = spotify_album["release_date"]
if "external_urls" in spotify_album and "spotify" in spotify_album["external_urls"]:
album["url"] = spotify_album["external_urls"]["spotify"]
if "images" in spotify_album and len(spotify_album["images"]) > 0:
album["image"] = spotify_album["images"][0]["url"]
if spotify_album is not None:
if "release_date" in spotify_album:
album["release_date"] = spotify_album["release_date"]
if "external_urls" in spotify_album and "spotify" in spotify_album["external_urls"]:
album["url"] = spotify_album["external_urls"]["spotify"]
if "images" in spotify_album and len(spotify_album["images"]) > 0:
album["image"] = spotify_album["images"][0]["url"]

return album, songs, count

Expand All @@ -658,10 +649,7 @@ def load_song(uuid, spotipy_helper, page=None,
uuid, page=page, limit=limit, order=order, asc=asc)
sp = None

spotify_song = get_spotify_song_from_cache(spotipy_helper.get_spotipy_client(), song_db.spotify_uri)

if spotify_song is None:
raise SpotifyDataException
spotify_song = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), song_db.spotify_uri)

song = {}
song["name"] = song_db.title
Expand All @@ -670,22 +658,22 @@ def load_song(uuid, spotipy_helper, page=None,
song["popularity"] = ""
song["preview"] = ""

if "preview_url" in spotify_song:
song["preview_url"] = spotify_song["preview_url"]
if "popularity" in spotify_song:
song["popularity"] = str(spotify_song["popularity"]) + "%"
if "external_urls" in spotify_song and "spotify" in spotify_song["external_urls"]:
song["url"] = spotify_song["external_urls"]["spotify"]
if spotify_song is not None:
if "preview_url" in spotify_song:
song["preview_url"] = spotify_song["preview_url"]
if "popularity" in spotify_song:
song["popularity"] = str(spotify_song["popularity"]) + "%"
if "external_urls" in spotify_song and "spotify" in spotify_song["external_urls"]:
song["url"] = spotify_song["external_urls"]["spotify"]

if len(songs) > 0:
spotify_album = None
if songs[0].spotify_album_uri not in spotify_album_cache:
spotify_album = get_spotify_album_from_cache(spotipy_helper.get_spotipy_client(), songs[0].spotify_album_uri)
spotify_album = get_spotify_object_from_cache(spotipy_helper.get_spotipy_client(), songs[0].spotify_album_uri)

if spotify_album is None:
raise SpotifyDataException

if "images" in spotify_album and len(spotify_album["images"]) > 0:
song["image"] = spotify_album["images"][0]["url"]
if spotify_album is not None:
if "images" in spotify_album and len(spotify_album["images"]) > 0:
song["image"] = spotify_album["images"][0]["url"]

return song, songs, count

return song, songs, count
spotify_cache = load_spotify_cache_from_file()
50 changes: 34 additions & 16 deletions spotisub/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import json
import math
from time import strftime
from requests import ConnectionError
from requests import ReadTimeout
from urllib3.exceptions import MaxRetryError
from flask import Blueprint
from flask import Response
from flask import request
Expand Down Expand Up @@ -54,18 +57,9 @@ def after_request(response):

@spotisub.errorhandler(Exception)
def all_exception_handler(error):
if isinstance(error, SubsonicOfflineException):
return render_template('errors/404.html',
title=title,
errors=["Unable to communicate with Subsonic.", "Please check your configuration and make sure your instance is online."])
elif isinstance(error, SpotifyException) or isinstance(error, SpotifyApiException):
return render_template('errors/404.html',
title=title,
errors=["Unable to communicate with Spotify.", "Please check your configuration."])
elif isinstance(error, ConnectionError):
return render_template('errors/404.html',
title=title,
errors=["Connection Error. Please check Spotisub logs."])
return render_template('errors/404.html',
title='Error!',
errors=[repr(error)])

blueprint = Blueprint('api', __name__, url_prefix='/api/v1')
api = Api(blueprint, doc='/docs/')
Expand Down Expand Up @@ -96,7 +90,7 @@ def get_json_message(message, is_ok):
@spotisub.route('/overview/<int:page>/<int:limit>/<string:order>/')
@spotisub.route('/overview/<int:page>/<int:limit>/<string:order>/<int:asc>/')
@login_required
def overview(page=1, limit=25, order='subsonic_spotify_relation.subsonic_playlist_name', asc=1):
def overview(page=1, limit=100, order='subsonic_spotify_relation.subsonic_playlist_name', asc=1):
title = 'Overview'
spotipy_helper.get_secrets()
all_playlists, song_count = subsonic_helper.select_all_playlists(spotipy_helper,
Expand All @@ -120,7 +114,28 @@ def overview(page=1, limit=25, order='subsonic_spotify_relation.subsonic_playlis
order=order,
asc=asc,
sorting_dict=sorting_dict)



@spotisub.route('/')
@spotisub.route('/overview_content/')
@spotisub.route('/overview_content/<int:page>/')
@spotisub.route('/overview_content/<int:page>/<int:limit>/')
@spotisub.route('/overview_content/<int:page>/<int:limit>/<string:order>/')
@spotisub.route('/overview_content/<int:page>/<int:limit>/<string:order>/<int:asc>/')
@login_required
def overview_content(page=1, limit=25, order='subsonic_spotify_relation.subsonic_playlist_name', asc=1):
spotipy_helper.get_secrets()
all_playlists, song_count = subsonic_helper.select_all_playlists(spotipy_helper,
page=page - 1, limit=limit, order=order, asc=(asc == 1))
sorting_dict = {}
sorting_dict["Playlist Name"] = "subsonic_spotify_relation.subsonic_playlist_name"
sorting_dict["Type"] = "playlist_info.type"
return render_template('overview_content.html',
playlists=all_playlists,
limit=limit,
result_size=song_count,
order=order,
asc=asc)

@spotisub.route('/playlists/')
@spotisub.route('/playlists/<int:missing_only>/')
Expand Down Expand Up @@ -511,5 +526,8 @@ def remove_subsonic_deleted_playlist():
constants.SCHEDULER_ENABLED_DEFAULT_VALUE) != "1"))


# Used to initialize cache for the first 500 playlists
subsonic_helper.select_all_playlists(spotipy_helper, page=0, limit=500, order='subsonic_spotify_relation.subsonic_playlist_name', asc=True)
# Used to initialize cache for the first 100 playlists
try:
subsonic_helper.select_all_playlists(spotipy_helper, page=0, limit=100, order='subsonic_spotify_relation.subsonic_playlist_name', asc=True)
except:
pass
Binary file added spotisub/static/img/android-chrome-192x192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spotisub/static/img/android-chrome-256x256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spotisub/static/img/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions spotisub/static/img/browserconfig.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file added spotisub/static/img/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spotisub/static/img/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spotisub/static/img/favicon.ico
Binary file not shown.
Binary file added spotisub/static/img/mstile-150x150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions spotisub/static/img/safari-pinned-tab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 937b864

Please sign in to comment.