feat: use thumbnails from folders

+ cache failed lastfm scrobbles
+ implement lastfm scrobble filter
+ change /home to /nothome
This commit is contained in:
cwilvx
2025-01-07 23:13:19 +03:00
parent 2a12487220
commit fe39cadfdc
10 changed files with 136 additions and 31 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ from app.lib.home.get_recently_played import get_recently_played
from app.store.homepage import HomepageStore
bp_tag = Tag(name="Home", description="Homepage items")
api = APIBlueprint("home", __name__, url_prefix="/home", abp_tags=[bp_tag])
api = APIBlueprint("home", __name__, url_prefix="/nothome", abp_tags=[bp_tag])
@api.get("/recents/added")
+108 -9
View File
@@ -1,3 +1,4 @@
from fileinput import filename
from pathlib import Path
from flask_openapi3 import Tag
from flask_openapi3 import APIBlueprint
@@ -5,6 +6,10 @@ from pydantic import BaseModel, Field
from flask import send_from_directory
from app.settings import Defaults, Paths
from app.store.albums import AlbumStore
from app.store.tracks import TrackStore
from app.utils.threading import background
from PIL import Image
bp_tag = Tag(
name="Images", description="Image filenames are constructured as '{itemhash}.webp'"
@@ -12,6 +17,74 @@ bp_tag = Tag(
api = APIBlueprint("imgserver", __name__, url_prefix="/img", abp_tags=[bp_tag])
@background
def cache_thumbnails(filepath: Path, trackhash: str):
"""
Resizes the image and stores it in the cache directory.
"""
image = Image.open(filepath)
path = Path(Paths.get_image_cache_path())
aspect_ratio = image.width / image.height
sizes = {
"xsmall": 64,
"small": 96,
"medium": 256,
"large": 512,
}
for size, width in sizes.items():
width = min(width, image.width)
height = int(width / aspect_ratio)
resized_path = path / size / (trackhash + ".webp")
resized_path.parent.mkdir(parents=True, exist_ok=True)
image.resize((width, height)).save(resized_path, format="webp")
def find_thumbnail(albumhash: str, pathhash: str):
# entry = TrackStore.trackhashmap.get(albumhash)
entry = AlbumStore.albummap.get(albumhash)
if entry is None:
return None, None, ""
track_file = None
tracks = TrackStore.get_tracks_by_trackhashes(entry.trackhashes)
for track in tracks:
if track.pathhash == pathhash:
track_file = track
break
if track_file is None:
return None, None, ""
folder = Path(track_file.folder)
# INFO: Check if the folder has image files
extensions = [".jpg", ".jpeg", ".png", ".webp"]
hierarchy = ["cover", "front", "back", "folder", "album", "artwork"]
images: list[Path] = []
for item in folder.iterdir():
if item.suffix in extensions:
images.append(item)
if len(images) == 0:
return None, None, ""
# INFO: Check if the folder has image files in the hierarchy
for item in hierarchy:
for image in images:
if image.name.lower().startswith(item.lower()):
return image.parent, image.name, track_file.albumhash
# INFO: If no image falls in the hierarchy, return the first image
first_image = images[0]
return first_image.parent, first_image.name, track_file.albumhash
def send_fallback_img(filename: str = "default.webp"):
"""
Returns the fallback image from the assets folder.
@@ -25,7 +98,9 @@ def send_fallback_img(filename: str = "default.webp"):
return send_from_directory(folder, filename)
def send_file_or_fallback(folder: str, filename: str, fallback: str = "default.webp"):
def send_file_or_fallback(
folder: str, filename: str, fallback: str = "default.webp", pathhash: str = ""
):
"""
Returns the file from the folder or the fallback image.
"""
@@ -34,6 +109,22 @@ def send_file_or_fallback(folder: str, filename: str, fallback: str = "default.w
if fpath.exists():
return send_from_directory(folder, filename)
if pathhash != "":
# INFO: Check if the image is in the cache
cache_path = Path(Paths.get_image_cache_path()) / fpath.parent.name / filename
if cache_path.exists():
return send_from_directory(cache_path.parent, cache_path.name)
# INFO: Find the thumbnail
parent, file, albumhash = find_thumbnail(
filename.replace(".webp", ""), pathhash
)
# INFO: Cache and send the thumbnail
if file is not None and parent is not None:
cache_thumbnails(parent / file, albumhash)
return send_from_directory(parent, file)
return send_fallback_img(fallback)
@@ -44,6 +135,13 @@ class ImagePath(BaseModel):
)
class ImageQuery(BaseModel):
pathhash: str = Field(
description="The path hash used to find the thumbnail",
default="",
)
# @api.get("/t/o/<imgpath>")
# def send_original_thumbnail(path: ImagePath):
# """
@@ -60,39 +158,39 @@ class ImagePath(BaseModel):
# TRACK THUMBNAILS
@api.get("/thumbnail/<imgpath>")
def send_lg_thumbnail(path: ImagePath):
def send_lg_thumbnail(path: ImagePath, query: ImageQuery):
"""
Get large thumbnail (500 x 500)
"""
folder = Paths.get_lg_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
return send_file_or_fallback(folder, path.imgpath, pathhash=query.pathhash)
@api.get("/thumbnail/xsmall/<imgpath>")
def send_xsm_thumbnail(path: ImagePath):
def send_xsm_thumbnail(path: ImagePath, query: ImageQuery):
"""
Get extra small thumbnail (64px)
"""
folder = Paths.get_xsm_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
return send_file_or_fallback(folder, path.imgpath, pathhash=query.pathhash)
@api.get("/thumbnail/small/<imgpath>")
def send_sm_thumbnail(path: ImagePath):
def send_sm_thumbnail(path: ImagePath, query: ImageQuery):
"""
Get small thumbnail (96px)
"""
folder = Paths.get_sm_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
return send_file_or_fallback(folder, path.imgpath, pathhash=query.pathhash)
@api.get("/thumbnail/medium/<imgpath>")
def send_md_thumbnail(path: ImagePath):
def send_md_thumbnail(path: ImagePath, query: ImageQuery):
"""
Get medium thumbnail (256px)
"""
folder = Paths.get_md_thumb_path()
return send_file_or_fallback(folder, path.imgpath)
return send_file_or_fallback(folder, path.imgpath, pathhash=query.pathhash)
# ARTISTS
@@ -141,6 +239,7 @@ def send_playlist_image(path: PlaylistImagePath):
folder = Paths.get_playlist_img_path()
return send_file_or_fallback(folder, path.imgpath, "playlist.svg")
# MIXES
@api.get("/mix/medium/<imgpath>")
def send_md_mix_image(path: ImagePath):
+15 -5
View File
@@ -94,13 +94,19 @@ def log_track(body: LogTrackBody):
if artist:
artist.increment_playcount(duration, timestamp)
track = TrackStore.trackhashmap.get(body.trackhash)
if track:
track.increment_playcount(duration, timestamp)
trackentry.increment_playcount(duration, timestamp)
track = trackentry.tracks[0]
lastfm = LastFmPlugin()
if lastfm.enabled:
print(track.duration / 2, 240, body.duration, "\n")
if (
lastfm.enabled
and track.duration > 30
and body.duration >= min(track.duration / 2, 240)
# SEE: https://www.last.fm/api/scrobbling#when-is-a-scrobble-a-scrobble
):
lastfm.scrobble(trackentry.tracks[0], timestamp)
return {"msg": "recorded"}, 201
@@ -350,7 +356,11 @@ def get_stats():
if len(tracks) > 0
else ""
),
tracks[0].image if len(tracks) > 0 else None,
(
tracks[0].image + "?pathhash=" + tracks[0].pathhash
if len(tracks) > 0
else None
),
)
fav_count = FavoritesTable.count_favs_in_period(start_time, end_time)