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`.pull/3/head
parent
943cf4dbd2
commit
7b3427fc2c
|
@ -1,2 +1,4 @@
|
|||
/.venv
|
||||
/dist
|
||||
__pycache__/
|
||||
*.py[co]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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():
|
||||
|
|
Loading…
Reference in New Issue