forked from MiczFlor/RPi-Jukebox-RFID
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix CoverartCacheManager (MiczFlor#2325)
* Fix CoverartCacheManager for songs with no art Previously, an ERROR was logged for each song without cover art when the Web UI was open. This commit avoids the error, caches the no-cover-art result and saves a roundtrip to mpd for all no-cover-art songs. * refactor: Reducing code and simplifying some logical statements * fix: flake8 error * refactor: reducing complexity for cache filename * refactor: introduce queuing for saving cache files * fix: remove slugify * feat: Use mutagen instead of MPD to retrieve cover art, include cache flush, and thread * fix: flake8 error * Update src/jukebox/components/playermpd/__init__.py Co-authored-by: Christian Hoffmann <[email protected]> --------- Co-authored-by: pabera <[email protected]>
- Loading branch information
Showing
5 changed files
with
97 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 77 additions & 13 deletions
90
src/jukebox/components/playermpd/coverart_cache_manager.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,90 @@ | ||
import os | ||
from mutagen.mp3 import MP3 | ||
from mutagen.id3 import ID3, APIC | ||
from pathlib import Path | ||
import hashlib | ||
import logging | ||
from queue import Queue | ||
from threading import Thread | ||
import jukebox.cfghandler | ||
|
||
COVER_PREFIX = 'cover' | ||
NO_COVER_ART_EXTENSION = 'no-art' | ||
NO_CACHE = '' | ||
CACHE_PENDING = 'CACHE_PENDING' | ||
|
||
logger = logging.getLogger('jb.CoverartCacheManager') | ||
cfg = jukebox.cfghandler.get_handler('jukebox') | ||
|
||
|
||
class CoverartCacheManager: | ||
def __init__(self): | ||
coverart_cache_path = cfg.setndefault('webapp', 'coverart_cache_path', value='../../src/webapp/build/cover-cache') | ||
self.cache_folder_path = os.path.expanduser(coverart_cache_path) | ||
self.cache_folder_path = Path(coverart_cache_path).expanduser() | ||
self.write_queue = Queue() | ||
self.worker_thread = Thread(target=self.process_write_requests) | ||
self.worker_thread.daemon = True # Ensure the thread closes with the program | ||
self.worker_thread.start() | ||
|
||
def generate_cache_key(self, base_filename: str) -> str: | ||
return f"{COVER_PREFIX}-{hashlib.sha256(base_filename.encode()).hexdigest()}" | ||
|
||
def get_cache_filename(self, mp3_file_path: str) -> str: | ||
base_filename = Path(mp3_file_path).stem | ||
cache_key = self.generate_cache_key(base_filename) | ||
|
||
for path in self.cache_folder_path.iterdir(): | ||
if path.stem == cache_key: | ||
if path.suffix == f".{NO_COVER_ART_EXTENSION}": | ||
return NO_CACHE | ||
return path.name | ||
|
||
self.save_to_cache(mp3_file_path) | ||
return CACHE_PENDING | ||
|
||
def save_to_cache(self, mp3_file_path: str): | ||
self.write_queue.put(mp3_file_path) | ||
|
||
def find_file_by_hash(self, hash_value): | ||
for filename in os.listdir(self.cache_folder_path): | ||
if filename.startswith(hash_value): | ||
return filename | ||
return None | ||
def _save_to_cache(self, mp3_file_path: str): | ||
base_filename = Path(mp3_file_path).stem | ||
cache_key = self.generate_cache_key(base_filename) | ||
file_extension, data = self._extract_album_art(mp3_file_path) | ||
|
||
def save_to_cache(self, base_filename, album_art_data): | ||
mime_type = album_art_data['type'] | ||
file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] | ||
cache_filename = f"{base_filename}.{file_extension}" | ||
cache_filename = f"{cache_key}.{file_extension}" | ||
full_path = self.cache_folder_path / cache_filename # Works due to Pathlib | ||
|
||
with open(os.path.join(self.cache_folder_path, cache_filename), 'wb') as file: | ||
file.write(album_art_data['binary']) | ||
with full_path.open('wb') as file: | ||
file.write(data) | ||
logger.debug(f"Created file: {cache_filename}") | ||
|
||
return cache_filename | ||
|
||
def _extract_album_art(self, mp3_file_path: str) -> tuple: | ||
try: | ||
audio_file = MP3(mp3_file_path, ID3=ID3) | ||
except Exception as e: | ||
logger.error(f"Error reading MP3 file {mp3_file_path}: {e}") | ||
return (NO_COVER_ART_EXTENSION, b'') | ||
|
||
for tag in audio_file.tags.values(): | ||
if isinstance(tag, APIC): | ||
mime_type = tag.mime | ||
file_extension = 'jpg' if mime_type == 'image/jpeg' else mime_type.split('/')[-1] | ||
return (file_extension, tag.data) | ||
|
||
return (NO_COVER_ART_EXTENSION, b'') | ||
|
||
def process_write_requests(self): | ||
while True: | ||
mp3_file_path = self.write_queue.get() | ||
try: | ||
self._save_to_cache(mp3_file_path) | ||
except Exception as e: | ||
logger.error(f"Error processing write request: {e}") | ||
self.write_queue.task_done() | ||
|
||
def flush_cache(self): | ||
for path in self.cache_folder_path.iterdir(): | ||
if path.is_file(): | ||
path.unlink() | ||
logger.debug(f"Deleted cached file: {path.name}") | ||
logger.info("Cache flushed successfully.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters