From 7b3427fc2c5bcc499d38181635f69da9e07a7a1a Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 12 Dec 2021 22:10:29 -0600 Subject: [PATCH] ripper: Add support for fetching album art If the `PIL` module is available, which can be installed by activating the *art* extra for Rupert, the ripper will now download the album art for the CD and store it in a file named `folder.jpg`. --- .gitignore | 2 ++ pyproject.toml | 2 ++ src/rupert/musicbrainz.py | 13 +++++++++++++ src/rupert/ripper.py | 21 ++++++++++++++++++++- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e79e5a4..0c8a237 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.venv /dist +__pycache__/ +*.py[co] diff --git a/pyproject.toml b/pyproject.toml index 3704eea..5b7c61a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ pydantic = "^1.7.3" python-libdiscid = "^2.0.1" musicbrainzngs = "^0.7.1" mutagen = "^1.45.1" +Pillow = {version = "^8.4.0", optional = true} [tool.poetry.dev-dependencies] pylint = "^2.6.0" @@ -24,6 +25,7 @@ rope = "^0.18.0" ripper = "rupert.main:main" [tool.poetry.extras] +art = ["pillow"] udev = ["pyudev"] [tool.black] diff --git a/src/rupert/musicbrainz.py b/src/rupert/musicbrainz.py index be19a76..a049bf8 100644 --- a/src/rupert/musicbrainz.py +++ b/src/rupert/musicbrainz.py @@ -10,6 +10,10 @@ from .disc import Disc musicbrainzngs.set_useragent('DCPlayer', '0.0.1', 'https://dcplayer.audio/') +class AlbumArtNotFound(Exception): + ... + + class Artist(pydantic.BaseModel): id: str name: str @@ -58,6 +62,15 @@ class ReleaseResponse(pydantic.BaseModel): release_count: int = pydantic.Field(alias='release-count') +def fetch_album_art(release: Release) -> bytes: + try: + return musicbrainzngs.get_image_front(release.id) + except OSError as e: + raise AlbumArtNotFound( + 'No album art found for release %s', release.id + ) from e + + def get_releases_from_disc(disc: Disc) -> ReleaseResponse: res = musicbrainzngs.get_releases_by_discid( disc.disc_id, diff --git a/src/rupert/ripper.py b/src/rupert/ripper.py index 3360d17..08366c2 100644 --- a/src/rupert/ripper.py +++ b/src/rupert/ripper.py @@ -2,6 +2,7 @@ from __future__ import annotations import enum import glob +import io import logging import os import queue @@ -25,7 +26,12 @@ import mutagen from . import inotify from .disc import DiscDrive -from .musicbrainz import Release +from .musicbrainz import AlbumArtNotFound, Release, fetch_album_art + +try: + from PIL import Image +except ImportError: + Image = None # type: ignore log = logging.getLogger(__name__) @@ -310,6 +316,8 @@ class Ripper: for filename in glob.glob('track*.cdda.wav'): os.unlink(filename) + if Image is not None: + threading.Thread(target=self.fetch_album_art).start() self._process_thread.start() self._encode_thread.start() self._rip_thread.start() @@ -336,6 +344,17 @@ class Ripper: def on_complete(self, message: str, is_err: bool) -> None: self._status_queue.put((None, (message, is_err))) + def fetch_album_art(self) -> None: + try: + data = fetch_album_art(self.release) + except AlbumArtNotFound: + log.error('No album art found for %s', self.release.title) + buf = io.BytesIO(data) + img = Image.open(buf) + img.thumbnail((1000, 1000)) + with open('folder.jpg', 'wb') as f: + img.save(f) + def safe_name(name: str) -> str: for k, v in FILENAME_SAFE_MAP.items():