add function to validate albums

+ extract colors in watchdogg
+ rename color db files
This commit is contained in:
mungai-njoroge 2023-07-12 08:56:30 +03:00
parent 4a7416853a
commit 861a854f91
18 changed files with 170 additions and 38 deletions

View File

@ -7,7 +7,7 @@ from dataclasses import asdict
from flask import Blueprint, request
from app.db.sqlite.albums import SQLiteAlbumMethods as adb
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as adb
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as lastfmdb
from app.models import FavType, Track

View File

@ -8,11 +8,9 @@ api = Blueprint("colors", __name__, url_prefix="/colors")
def get_album_color(albumhash: str):
album = Store.get_album_by_hash(albumhash)
if len(album.colors) > 0:
return {
"color": album.colors[0]
}
msg = {"color": ""}
return {
"color": ""
}
if album is None or len(album.colors) == 0:
return msg, 404
return {"color": album.colors[0]}

View File

@ -49,7 +49,7 @@ def send_track_file(trackhash: str):
try:
return send_file(track.filepath, mimetype=audio_type)
except FileNotFoundError:
except (FileNotFoundError, OSError) as e:
return msg, 404
return msg, 404

View File

@ -44,3 +44,23 @@ class SQLiteAlbumMethods:
return tuples_to_albums(albums)
return []
@staticmethod
def exists(albumhash: str, cur: Cursor = None):
"""
Checks if an album exists in the database.
"""
sql = "SELECT COUNT(1) FROM albums WHERE albumhash = ?"
def _exists(cur: Cursor):
cur.execute(sql, (albumhash,))
count = cur.fetchone()[0]
return count != 0
if cur:
return _exists(cur)
with SQLiteManager() as cur:
return _exists(cur)

View File

@ -43,3 +43,22 @@ class SQLiteArtistMethods:
for artist in cur_.fetchall():
yield artist
@staticmethod
def exists(artisthash: str, cur: Cursor = None):
"""
Checks if an artist exists in the database.
"""
sql = "SELECT COUNT(1) FROM artists WHERE artisthash = ?"
def _exists(cur: Cursor):
cur.execute(sql, (artisthash,))
count = cur.fetchone()[0]
return count != 0
if cur:
return _exists(cur)
with SQLiteManager() as cur:
return _exists(cur)

View File

@ -1,3 +1,22 @@
"""
Contains methods relating to albums.
"""
"""
from tqdm import tqdm
from app.store.albums import AlbumStore
from app.store.tracks import TrackStore
def validate_albums():
"""
Removes albums that have no tracks.
Probably albums that were added from incompletely written files.
"""
album_hashes = {t.albumhash for t in TrackStore.tracks}
albums = AlbumStore.albums
for album in tqdm(albums, desc="Validating albums"):
if album.albumhash not in album_hashes:
AlbumStore.remove_album(album)

View File

@ -9,8 +9,8 @@ import colorgram
from tqdm import tqdm
from app import settings
from app.db.sqlite.albums import SQLiteAlbumMethods as db
from app.db.sqlite.artists import SQLiteArtistMethods as adb
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
from app.db.sqlite.artistcolors import SQLiteArtistMethods as adb
from app.db.sqlite.utils import SQLiteManager
from app.store.artists import ArtistStore
@ -58,11 +58,8 @@ class ProcessAlbumColors:
with SQLiteManager() as cur:
try:
for album in tqdm(albums, desc="Processing missing album colors"):
sql = "SELECT COUNT(1) FROM albums WHERE albumhash = ?"
cur.execute(sql, (album.albumhash,))
count = cur.fetchone()[0]
if count != 0:
exists = aldb.exists(album.albumhash, cur=cur)
if exists:
continue
colors = process_color(album.albumhash)
@ -72,7 +69,7 @@ class ProcessAlbumColors:
album.set_colors(colors)
color_str = json.dumps(colors)
db.insert_one_album(cur, album.albumhash, color_str)
aldb.insert_one_album(cur, album.albumhash, color_str)
finally:
cur.close()
@ -90,12 +87,9 @@ class ProcessArtistColors:
for artist in tqdm(
all_artists, desc="Processing missing artist colors"
):
sql = "SELECT COUNT(1) FROM artists WHERE artisthash = ?"
exists = adb.exists(artist.artisthash, cur=cur)
cur.execute(sql, (artist.artisthash,))
count = cur.fetchone()[0]
if count != 0:
if exists:
continue
colors = process_color(artist.artisthash, is_album=False)

View File

@ -9,10 +9,10 @@ from tqdm import tqdm
from app import settings
from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb
from app.db.sqlite.lastfm.similar_artists import \
SQLiteLastFMSimilarArtists as lastfmdb
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.albumslib import validate_albums
from app.lib.artistlib import CheckArtistImages
from app.lib.colorlib import ProcessAlbumColors, ProcessArtistColors
from app.lib.taglib import extract_thumb, get_tags
@ -51,6 +51,8 @@ class Populate:
POPULATE_KEY = key
validate_tracks()
validate_albums()
tracks = get_all_tracks()
dirs_to_scan = sdb.get_root_dirs()
@ -122,8 +124,7 @@ class Populate:
if track.last_mod == os.path.getmtime(track.filepath):
unmodified.add(track.filepath)
continue
except FileNotFoundError:
print(f"File not found: {track.filepath}")
except (FileNotFoundError, OSError) as e:
TrackStore.remove_track_obj(track)
remove_tracks_by_filepaths(track.filepath)

View File

@ -78,7 +78,11 @@ def extract_date(date_str: str | None) -> int | None:
def get_tags(filepath: str):
filetype = filepath.split(".")[-1]
filename = (filepath.split("/")[-1]).replace(f".{filetype}", "")
last_mod = os.path.getmtime(filepath)
try:
last_mod = os.path.getmtime(filepath)
except FileNotFoundError:
return None
try:
tags = TinyTag.get(filepath)

View File

@ -11,9 +11,9 @@ from app.store.tracks import TrackStore
def validate_tracks() -> None:
"""
Gets all songs under the ~/ directory.
Removes track records whose files no longer exist.
"""
for track in tqdm(TrackStore.tracks, desc="Checking for deleted tracks"):
for track in tqdm(TrackStore.tracks, desc="Validating tracks"):
if not os.path.exists(track.filepath):
TrackStore.remove_track_obj(track)
tdb.remove_tracks_by_filepaths(track.filepath)

View File

@ -1,6 +1,7 @@
"""
This library contains the classes and functions related to the watchdog file watcher.
"""
import json
import os
import sqlite3
import time
@ -9,10 +10,14 @@ from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer
from app import settings
from app.db.sqlite.settings import SettingsSQLMethods as sdb
from app.db.sqlite.tracks import SQLiteManager
from app.db.sqlite.tracks import SQLiteTrackMethods as db
from app.lib.taglib import get_tags
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
from app.lib.colorlib import process_color
from app.lib.taglib import extract_thumb, get_tags
from app.logger import log
from app.models import Artist, Track
from app.store.albums import AlbumStore
@ -123,6 +128,22 @@ class Watcher:
self.run()
def handle_colors(cur: sqlite3.Cursor, albumhash: str):
exists = aldb.exists(albumhash, cur)
if exists:
return
colors = process_color(albumhash, is_album=True)
if colors is None:
return
aldb.insert_one_album(cur=cur, albumhash=albumhash, colors=json.dumps(colors))
return colors
def add_track(filepath: str) -> None:
"""
Processes the audio tags for a given file ands add them to the database and store.
@ -138,14 +159,23 @@ def add_track(filepath: str) -> None:
if tags is None or tags["bitrate"] == 0 or tags["duration"] == 0:
return
colors = None
with SQLiteManager() as cur:
db.insert_one_track(tags, cur)
extracted = extract_thumb(filepath, tags["albumhash"] + ".webp")
if not extracted:
return
colors = handle_colors(cur, tags["albumhash"])
track = Track(**tags)
TrackStore.add_track(track)
if not AlbumStore.album_exists(track.albumhash):
album = AlbumStore.create_album(track)
album.set_colors(colors)
AlbumStore.add_album(album)
artists: list[Artist] = track.artist + track.albumartist # type: ignore
@ -154,6 +184,7 @@ def add_track(filepath: str) -> None:
if not ArtistStore.artist_exists(artist.artisthash):
ArtistStore.add_artist(Artist(artist.name))
extract_thumb(filepath, track.image)
def remove_track(filepath: str) -> None:
"""
@ -277,7 +308,7 @@ class Handler(PatternMatchingEventHandler):
if current_size == previous_size:
# Wait for a short duration to ensure the file write operation is complete
time.sleep(0.5)
time.sleep(5)
# Check the file size again
current_size = os.path.getsize(event.src_path)

View File

@ -157,7 +157,6 @@ class Album:
return self.title.strip().endswith(" EP")
def check_is_single(self, tracks: list[Track]):
"""
Checks if the album is a single.
"""

View File

@ -1,5 +1,5 @@
from dataclasses import asdict
from app.models.album import Album
from app.models import Album
def album_serializer(album: Album, to_remove: set[str]) -> dict:

View File

@ -4,7 +4,7 @@ import random
from tqdm import tqdm
from app.models import Album, Track
from app.db.sqlite.albums import SQLiteAlbumMethods as aldb
from app.db.sqlite.albumcolors import SQLiteAlbumMethods as aldb
from .tracks import TrackStore
from ..utils.hashing import create_hash
@ -133,6 +133,13 @@ class AlbumStore:
"""
return albumhash in "-".join([a.albumhash for a in cls.albums])
@classmethod
def remove_album(cls, album: Album):
"""
Removes an album from the store.
"""
cls.albums.remove(album)
@classmethod
def remove_album_by_hash(cls, albumhash: str):
"""

View File

@ -2,7 +2,7 @@ import json
from tqdm import tqdm
from app.db.sqlite.artists import SQLiteArtistMethods as ardb
from app.db.sqlite.artistcolors import SQLiteArtistMethods as ardb
from app.lib.artistlib import get_all_artists
from app.models import Artist
from app.utils.bisection import UseBisection

41
poetry.lock generated
View File

@ -1,5 +1,31 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]]
name = "about-time"
version = "4.2.1"
description = "Easily measure timing and throughput of code blocks, with beautiful human friendly representations."
optional = false
python-versions = ">=3.7, <4"
files = [
{file = "about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece"},
{file = "about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"},
]
[[package]]
name = "alive-progress"
version = "3.1.4"
description = "A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!"
optional = false
python-versions = ">=3.7, <4"
files = [
{file = "alive-progress-3.1.4.tar.gz", hash = "sha256:74a95d8d0d42bc99d3a3725dbd06ebb852245f1b64e301a7c375b92b22663f7b"},
{file = "alive_progress-3.1.4-py3-none-any.whl", hash = "sha256:c80ad87ce9c1054b01135a87fae69ecebbfc2107497ae87cbe6aec7e534903db"},
]
[package.dependencies]
about-time = "4.2.1"
grapheme = "0.6.0"
[[package]]
name = "altgraph"
version = "0.17.3"
@ -280,6 +306,19 @@ files = [
Flask = ">=0.9"
Six = "*"
[[package]]
name = "grapheme"
version = "0.6.0"
description = "Unicode grapheme helpers"
optional = false
python-versions = "*"
files = [
{file = "grapheme-0.6.0.tar.gz", hash = "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca"},
]
[package.extras]
test = ["pytest", "sphinx", "sphinx-autobuild", "twine", "wheel"]
[[package]]
name = "gunicorn"
version = "20.1.0"
@ -1329,4 +1368,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.12"
content-hash = "81941c92f1b4a468b554fc7eeae857897f352142cc3c03393349297ac11dcaae"
content-hash = "da0e11b5066258d0a56917ea1143fa7196c6de88bd7d6b9f2fc060d84e6bf36f"

View File

@ -20,6 +20,7 @@ Unidecode = "^1.3.6"
psutil = "^5.9.4"
show-in-file-manager = "^1.1.4"
pendulum = "^2.1.2"
alive-progress = "^3.1.4"
[tool.poetry.dev-dependencies]
pylint = "^2.15.5"

View File

@ -1,7 +1,7 @@
import json
import sqlite3
import os
from app.db.sqlite.artists import SQLiteArtistMethods
from app.db.sqlite.artistcolors import SQLiteArtistMethods
from app.db.sqlite.queries import CREATE_APPDB_TABLES
from app.db.sqlite.utils import SQLiteManager