Compare commits

...

2 commits

Author SHA1 Message Date
03458db829
Fix: cover.download()
- resp.read returns nothing, contents are already in resp.data
- add mime type safety check
2024-05-09 18:33:13 +04:00
38cc43b84f
Add cover downloader, add cfg.save_{lyrics,cover} (mainly for testing) 2024-05-09 18:05:39 +04:00
4 changed files with 87 additions and 7 deletions

View file

@ -22,6 +22,9 @@ class Config:
# Proxy URL for yt_proxied downloader (can be used for geo-restricted content) # Proxy URL for yt_proxied downloader (can be used for geo-restricted content)
self.yt_proxy = os.getenv('YT_PROXY') or 'http://127.0.0.1:1080' self.yt_proxy = os.getenv('YT_PROXY') or 'http://127.0.0.1:1080'
self.save_lyrics = _parse_bool(os.getenv('SAVE_LYRICS'), True)
self.save_cover = _parse_bool(os.getenv('SAVE_COVER'), True)
_config: Config | None = None _config: Config | None = None
@ -31,3 +34,11 @@ def get() -> Config:
if _config is None: if _config is None:
_config = Config() _config = Config()
return _config return _config
def _parse_bool(val: str | None, default: bool) -> bool:
if val is None:
return default
if val in {'false', 'off', 'no', '0'}:
return False
return bool(val)

46
backend/cover.py Normal file
View file

@ -0,0 +1,46 @@
import mimetypes
import subprocess
from pathlib import Path
import http_pool
def download_from_yt(url: str, album_path: Path) -> None:
'''YouTube-specific cover art downloader.
See https://git.dc09.ru/DarkCat09/musicdlp/issues/1#issuecomment-202'''
path = album_path / 'cover.webp'
if path.exists():
return
ret = subprocess.call(
[
'ffmpeg',
'-nostdin',
'-i', url,
'-vf', 'crop=ih:ih',
str(album_path / 'cover.webp'),
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if ret != 0:
raise RuntimeError(f'FFmpeg returned {ret} exit code')
def download(url: str, album_path: Path) -> None:
'''General cover art downloader'''
resp = http_pool.get().request('GET', url)
ct = resp.headers['content-type']
if not ct.startswith('image'):
raise ValueError('thumbnail is not an image')
ext = mimetypes.guess_extension(ct) or '.jpg'
path = album_path / ('cover' + ext)
if path.exists():
return
with path.open('wb') as f:
f.write(resp.data)

View file

@ -1,6 +1,9 @@
from pathlib import Path
from mutagen import mp3, id3 from mutagen import mp3, id3
from yt_dlp.postprocessor import PostProcessor from yt_dlp.postprocessor import PostProcessor
import config
import cover
import genius import genius
@ -44,6 +47,10 @@ class ID3TagsPP(PostProcessor):
'''Inserts ID3 tags after all PPs (for YT: InfoYouTubePP and FFmpegExtractAudioPP), '''Inserts ID3 tags after all PPs (for YT: InfoYouTubePP and FFmpegExtractAudioPP),
triggers searching and parsing lyrics from Genius''' triggers searching and parsing lyrics from Genius'''
def __init__(self) -> None:
self.cfg = config.get()
super().__init__()
def run(self, information): def run(self, information):
file = mp3.MP3(information['filepath']) file = mp3.MP3(information['filepath'])
@ -62,13 +69,24 @@ class ID3TagsPP(PostProcessor):
if 'genre' in information: if 'genre' in information:
file['TCON'] = id3.TCON(encoding=ENC_UTF8, text=information['genre']) file['TCON'] = id3.TCON(encoding=ENC_UTF8, text=information['genre'])
try: if self.cfg.save_lyrics:
lyr_title, lyr_url = genius.search(title, artists[0]) try:
genius.raise_on_irrelevant_result(lyr_title, title, artists[0]) lyr_title, lyr_url = genius.search(title, artists[0])
file['USLT'] = id3.USLT(encoding=ENC_UTF8, text=genius.parse(lyr_url)) genius.raise_on_irrelevant_result(lyr_title, title, artists[0])
except: file['USLT'] = id3.USLT(encoding=ENC_UTF8, text=genius.parse(lyr_url))
pass except:
pass
file.save() file.save()
if self.cfg.save_cover:
try:
album_path = Path(information['filepath']).parent
if 'youtube' in information['extractor']:
cover.download_from_yt(information['thumbnail'], album_path)
else:
cover.download(information['thumbnail'], album_path)
except:
pass
return [], information return [], information

View file

@ -5,8 +5,9 @@ import subprocess
from mutagen import mp3 from mutagen import mp3
import id3pp import config
import http_pool import http_pool
import id3pp
TEST_MP3 = 'music/test.mp3' TEST_MP3 = 'music/test.mp3'
@ -35,6 +36,10 @@ class TestPostProcessorsOnFakeData(TestCase):
def setUp(self) -> None: def setUp(self) -> None:
cfg = config.get()
cfg.save_lyrics = False
cfg.save_cover = False
http_pool.get() http_pool.get()
def test_infoytpp(self) -> None: def test_infoytpp(self) -> None: