mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
add last fm similar artists to db table
+ add db methods for the above + try and discard last fm store
This commit is contained in:
+4
-1
@@ -100,7 +100,6 @@ def get_album_tracks(albumhash: str):
|
||||
t["_pos"] = int(f"{t['disc']}{track}")
|
||||
|
||||
tracks = sorted(tracks, key=lambda t: t["_pos"])
|
||||
tracks = [track_serializer(t, _remove={"_pos"}) for t in tracks]
|
||||
|
||||
return {"tracks": tracks}
|
||||
|
||||
@@ -164,4 +163,8 @@ def get_album_versions():
|
||||
and create_hash(og_album_title) != create_hash(a.og_title)
|
||||
]
|
||||
|
||||
for a in albums:
|
||||
tracks = TrackStore.get_tracks_by_albumhash(a.albumhash)
|
||||
a.get_date_from_tracks(tracks)
|
||||
|
||||
return {"data": albums}
|
||||
|
||||
+38
-25
@@ -2,10 +2,13 @@
|
||||
Contains all the artist(s) routes.
|
||||
"""
|
||||
from collections import deque
|
||||
import random
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as fmdb
|
||||
|
||||
from app.models import Album, FavType, Track
|
||||
from app.serializers.album import serialize_for_card_many
|
||||
from app.serializers.track import serialize_tracks
|
||||
@@ -141,6 +144,11 @@ class ArtistsCache:
|
||||
album_tracks = remove_duplicates(album_tracks)
|
||||
|
||||
album.get_date_from_tracks(album_tracks)
|
||||
|
||||
if album.date == 0:
|
||||
AlbumStore.remove_album_by_hash(album.albumhash)
|
||||
continue
|
||||
|
||||
album.check_is_single(album_tracks)
|
||||
|
||||
entry.type_checked = True
|
||||
@@ -288,28 +296,33 @@ def get_all_artist_tracks(artisthash: str):
|
||||
return {"tracks": serialize_tracks(tracks)}
|
||||
|
||||
|
||||
#
|
||||
# @api.route("/artist/<artisthash>/similar", methods=["GET"])
|
||||
# def get_similar_artists(artisthash: str):
|
||||
# """
|
||||
# Returns similar artists.
|
||||
# """
|
||||
# limit = request.args.get("limit")
|
||||
#
|
||||
# if limit is None:
|
||||
# limit = 6
|
||||
#
|
||||
# limit = int(limit)
|
||||
#
|
||||
# artist = ArtistStore.get_artist_by_hash(artisthash)
|
||||
#
|
||||
# if artist is None:
|
||||
# return {"error": "Artist not found"}, 404
|
||||
#
|
||||
# similar_hashes = fetch_similar_artists(artist.name)
|
||||
# similar = ArtistStore.get_artists_by_hashes(similar_hashes)
|
||||
#
|
||||
# if len(similar) > limit:
|
||||
# similar = random.sample(similar, limit)
|
||||
#
|
||||
# return {"similar": similar[:limit]}
|
||||
@api.route("/artist/<artisthash>/similar", methods=["GET"])
|
||||
def get_similar_artists(artisthash: str):
|
||||
"""
|
||||
Returns similar artists.
|
||||
"""
|
||||
limit = request.args.get("limit")
|
||||
|
||||
if limit is None:
|
||||
limit = 6
|
||||
|
||||
limit = int(limit)
|
||||
|
||||
artist = ArtistStore.get_artist_by_hash(artisthash)
|
||||
|
||||
if artist is None:
|
||||
return {"error": "Artist not found"}, 404
|
||||
|
||||
# result = LastFMStore.get_similar_artists_for(artist.artisthash)
|
||||
result = fmdb.get_similar_artists_for(artist.artisthash)
|
||||
|
||||
if result is None:
|
||||
return {"artists": []}
|
||||
|
||||
similar = ArtistStore.get_artists_by_hashes(result.get_artist_hash_set())
|
||||
|
||||
# print(similar)
|
||||
if len(similar) > limit:
|
||||
similar = random.sample(similar, limit)
|
||||
|
||||
return {"artists": similar[:limit]}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from app.models.lastfm import SimilarArtist
|
||||
|
||||
from ..utils import SQLiteManager
|
||||
|
||||
|
||||
class SQLiteLastFMSimilarArtists:
|
||||
"""
|
||||
This class contains methods for interacting with the lastfm_similar_artists table.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def insert_one(cls, artist: SimilarArtist):
|
||||
"""
|
||||
Inserts a single artist into the database.
|
||||
"""
|
||||
sql = """INSERT OR REPLACE INTO lastfm_similar_artists(artisthash, similar_artists) VALUES(?,?)"""
|
||||
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (artist.artisthash, artist.similar_artist_hashes))
|
||||
cur.close()
|
||||
|
||||
@classmethod
|
||||
def get_similar_artists_for(cls, artisthash: str):
|
||||
"""
|
||||
Returns a list of similar artists.
|
||||
"""
|
||||
sql = """SELECT * FROM lastfm_similar_artists WHERE artisthash = ?"""
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (artisthash,))
|
||||
similar_artists = cur.fetchone()
|
||||
cur.close()
|
||||
|
||||
if similar_artists is None:
|
||||
return None
|
||||
|
||||
return SimilarArtist(artisthash, similar_artists[2])
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
"""
|
||||
Returns a list of all similar artists.
|
||||
"""
|
||||
sql = """SELECT * FROM lastfm_similar_artists"""
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql)
|
||||
similar_artists = cur.fetchall()
|
||||
cur.close()
|
||||
|
||||
for a in similar_artists:
|
||||
yield SimilarArtist(a[1], a[2])
|
||||
|
||||
@classmethod
|
||||
def exists(cls, artisthash: str):
|
||||
"""
|
||||
Checks if an artist exists in the database by counting the number of rows
|
||||
"""
|
||||
sql = """SELECT COUNT(*) FROM lastfm_similar_artists WHERE artisthash = ?"""
|
||||
with SQLiteManager(userdata_db=True) as cur:
|
||||
cur.execute(sql, (artisthash,))
|
||||
count = cur.fetchone()[0]
|
||||
cur.close()
|
||||
return count > 0
|
||||
@@ -26,7 +26,14 @@ CREATE TABLE IF NOT EXISTS settings (
|
||||
root_dirs text NOT NULL,
|
||||
exclude_dirs text,
|
||||
artist_separators text
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS lastfm_similar_artists (
|
||||
id integer PRIMARY KEY,
|
||||
artisthash text NOT NULL,
|
||||
similar_artists text NOT NULL,
|
||||
UNIQUE (artisthash)
|
||||
);
|
||||
"""
|
||||
|
||||
CREATE_APPDB_TABLES = """
|
||||
@@ -58,8 +65,6 @@ CREATE TABLE IF NOT EXISTS albums (
|
||||
UNIQUE (albumhash)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS artists (
|
||||
id integer PRIMARY KEY,
|
||||
artisthash text NOT NULL,
|
||||
|
||||
+57
-8
@@ -1,26 +1,29 @@
|
||||
from collections import deque
|
||||
import json
|
||||
import os
|
||||
from collections import deque
|
||||
from typing import Generator
|
||||
from tqdm import tqdm
|
||||
|
||||
from requests import ConnectionError as RequestConnectionError
|
||||
from requests import ReadTimeout
|
||||
from tqdm import tqdm
|
||||
|
||||
from app import settings
|
||||
from app.db.sqlite.tracks import SQLiteTrackMethods
|
||||
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
|
||||
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
|
||||
from app.db.sqlite.settings import SettingsSQLMethods as sdb
|
||||
from app.db.sqlite.tracks import SQLiteTrackMethods
|
||||
from app.lib.artistlib import CheckArtistImages
|
||||
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
|
||||
|
||||
from app.lib.taglib import extract_thumb, get_tags
|
||||
from app.lib.trackslib import validate_tracks
|
||||
from app.logger import log
|
||||
from app.models import Album, Artist, Track
|
||||
from app.utils.filesystem import run_fast_scandir
|
||||
|
||||
from app.models.lastfm import SimilarArtist
|
||||
from app.requests.artists import fetch_similar_artists
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.tracks import TrackStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.store.tracks import TrackStore
|
||||
from app.utils.filesystem import run_fast_scandir
|
||||
from app.utils.network import Ping
|
||||
|
||||
get_all_tracks = SQLiteTrackMethods.get_all_tracks
|
||||
@@ -100,6 +103,9 @@ class Populate:
|
||||
if tried_to_download_new_images:
|
||||
ProcessArtistColors()
|
||||
|
||||
if Ping()():
|
||||
FetchSimilarArtistsLastFM()
|
||||
|
||||
@staticmethod
|
||||
def remove_modified(tracks: Generator[Track, None, None]):
|
||||
"""
|
||||
@@ -111,9 +117,14 @@ class Populate:
|
||||
modified = set()
|
||||
|
||||
for track in tracks:
|
||||
try:
|
||||
if track.last_mod == os.path.getmtime(track.filepath):
|
||||
unmodified.add(track.filepath)
|
||||
continue
|
||||
except FileNotFoundError:
|
||||
print(f"File not found: {track.filepath}")
|
||||
TrackStore.tracks.remove(track)
|
||||
remove_tracks_by_filepaths(track.filepath)
|
||||
|
||||
modified.add(track.filepath)
|
||||
|
||||
@@ -214,3 +225,41 @@ class ProcessTrackThumbnails:
|
||||
)
|
||||
|
||||
list(results)
|
||||
|
||||
|
||||
def save_similar_artists(artist: Artist):
|
||||
"""
|
||||
Downloads and saves similar artists to the database.
|
||||
"""
|
||||
|
||||
if lastfmdb.exists(artist.artisthash):
|
||||
return
|
||||
|
||||
artist_hashes = fetch_similar_artists(artist.name)
|
||||
artist_ = SimilarArtist(artist.artisthash, "~".join(artist_hashes))
|
||||
|
||||
if len(artist_.similar_artist_hashes) == 0:
|
||||
return
|
||||
|
||||
print(artist.artisthash, artist.name)
|
||||
lastfmdb.insert_one(artist_)
|
||||
|
||||
|
||||
class FetchSimilarArtistsLastFM:
|
||||
"""
|
||||
Fetches similar artists from LastFM using a process pool.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
artists = ArtistStore.artists
|
||||
|
||||
with Pool(processes=cpu_count()) as pool:
|
||||
results = list(
|
||||
tqdm(
|
||||
pool.imap_unordered(save_similar_artists, artists),
|
||||
total=len(artists),
|
||||
desc="Downloading similar artists",
|
||||
)
|
||||
)
|
||||
|
||||
list(results)
|
||||
|
||||
+7
-1
@@ -43,6 +43,7 @@ class Album:
|
||||
self.og_title = self.title
|
||||
self.image = self.albumhash + ".webp"
|
||||
|
||||
# Fetch album artists from title
|
||||
if get_flag(ParserFlags.EXTRACT_FEAT):
|
||||
featured, self.title = parse_feat_from_title(self.title)
|
||||
|
||||
@@ -56,6 +57,7 @@ class Album:
|
||||
|
||||
TrackStore.append_track_artists(self.albumhash, featured, self.title)
|
||||
|
||||
# Handle album version data
|
||||
if get_flag(ParserFlags.CLEAN_ALBUM_TITLE):
|
||||
get_versions = not get_flag(ParserFlags.MERGE_ALBUM_VERSIONS)
|
||||
|
||||
@@ -66,6 +68,8 @@ class Album:
|
||||
|
||||
if "super_deluxe" in self.versions:
|
||||
self.versions.remove("deluxe")
|
||||
|
||||
self.versions = [v.replace("_", " ") for v in self.versions]
|
||||
else:
|
||||
self.base_title = get_base_title_and_versions(
|
||||
self.title, get_versions=False
|
||||
@@ -180,10 +184,12 @@ class Album:
|
||||
Args:
|
||||
tracks (list[Track]): The tracks of the album.
|
||||
"""
|
||||
if self.date:
|
||||
return
|
||||
|
||||
dates = {t.date for t in tracks if t.date}
|
||||
|
||||
if len(dates) == 0:
|
||||
self.date = 0
|
||||
return
|
||||
|
||||
self.date = datetime.datetime.fromtimestamp(min(dates)).year
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class SimilarArtist:
|
||||
artisthash: str
|
||||
similar_artist_hashes: str
|
||||
|
||||
def get_artist_hash_set(self) -> set[str]:
|
||||
"""
|
||||
Returns a set of similar artists.
|
||||
"""
|
||||
return set(self.similar_artist_hashes.split("~"))
|
||||
+11
-2
@@ -1,24 +1,33 @@
|
||||
"""
|
||||
Requests related to artists
|
||||
"""
|
||||
from pprint import pprint
|
||||
import requests
|
||||
|
||||
from app import settings
|
||||
from app.utils.hashing import create_hash
|
||||
from requests import ConnectionError
|
||||
import urllib.parse
|
||||
|
||||
|
||||
def fetch_similar_artists(name: str):
|
||||
"""
|
||||
Fetches similar artists from Last.fm
|
||||
"""
|
||||
url = f"https://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist={name}&api_key=" \
|
||||
f"{settings.Keys.LASTFM_API}&format=json&limit=250"
|
||||
url = f"https://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist={urllib.parse.quote_plus(name, safe='')}&api_key={settings.Keys.LASTFM_API}&format=json&limit=250"
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
except ConnectionError:
|
||||
return []
|
||||
|
||||
data = response.json()
|
||||
|
||||
try:
|
||||
artists = data["similarartists"]["artist"]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
for artist in artists:
|
||||
yield create_hash(artist["name"])
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
Prepares the server for use.
|
||||
"""
|
||||
from app.setup.files import create_config_dir
|
||||
from app.setup.sqlite import setup_sqlite, run_migrations
|
||||
|
||||
from app.setup.sqlite import run_migrations, setup_sqlite
|
||||
from app.store.albums import AlbumStore
|
||||
from app.store.tracks import TrackStore
|
||||
from app.store.artists import ArtistStore
|
||||
from app.store.tracks import TrackStore
|
||||
|
||||
|
||||
def run_setup():
|
||||
|
||||
@@ -132,6 +132,7 @@ class AlbumVersionEnum(Enum):
|
||||
|
||||
DELUXE = ("deluxe",)
|
||||
SUPER_DELUXE = ("super deluxe",)
|
||||
COMPLETE = ("complete",)
|
||||
|
||||
LEGACY = ("legacy",)
|
||||
SPECIAL = ("special",)
|
||||
|
||||
Reference in New Issue
Block a user