diff --git a/backend/config.py b/backend/config.py index 4cfa230..964f0b9 100644 --- a/backend/config.py +++ b/backend/config.py @@ -22,6 +22,9 @@ class Config: # 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.save_lyrics = _parse_bool(os.getenv('SAVE_LYRICS'), True) + self.save_cover = _parse_bool(os.getenv('SAVE_COVER'), True) + _config: Config | None = None @@ -31,3 +34,11 @@ def get() -> Config: if _config is None: _config = 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) diff --git a/backend/cover.py b/backend/cover.py new file mode 100644 index 0000000..89465e2 --- /dev/null +++ b/backend/cover.py @@ -0,0 +1,44 @@ +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) + ext = mimetypes.guess_extension(resp.headers['content-type']) or '.jpg' + + path = album_path / ('cover' + ext) + if path.exists(): + return + + with (album_path / ('cover' + ext)).open('wb') as f: + for chunk in resp.read_chunked(): + f.write(chunk) diff --git a/backend/id3pp.py b/backend/id3pp.py index 1418222..7f2fd77 100644 --- a/backend/id3pp.py +++ b/backend/id3pp.py @@ -1,6 +1,9 @@ +from pathlib import Path from mutagen import mp3, id3 from yt_dlp.postprocessor import PostProcessor +import config +import cover import genius @@ -44,6 +47,10 @@ class ID3TagsPP(PostProcessor): '''Inserts ID3 tags after all PPs (for YT: InfoYouTubePP and FFmpegExtractAudioPP), triggers searching and parsing lyrics from Genius''' + def __init__(self) -> None: + self.cfg = config.get() + super().__init__() + def run(self, information): file = mp3.MP3(information['filepath']) @@ -62,13 +69,24 @@ class ID3TagsPP(PostProcessor): if 'genre' in information: file['TCON'] = id3.TCON(encoding=ENC_UTF8, text=information['genre']) - try: - lyr_title, lyr_url = genius.search(title, artists[0]) - genius.raise_on_irrelevant_result(lyr_title, title, artists[0]) - file['USLT'] = id3.USLT(encoding=ENC_UTF8, text=genius.parse(lyr_url)) - except: - pass + if self.cfg.save_lyrics: + try: + lyr_title, lyr_url = genius.search(title, artists[0]) + genius.raise_on_irrelevant_result(lyr_title, title, artists[0]) + file['USLT'] = id3.USLT(encoding=ENC_UTF8, text=genius.parse(lyr_url)) + except: + pass 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 diff --git a/backend/test_pp.py b/backend/test_pp.py index 6ad0205..faabc41 100644 --- a/backend/test_pp.py +++ b/backend/test_pp.py @@ -5,8 +5,9 @@ import subprocess from mutagen import mp3 -import id3pp +import config import http_pool +import id3pp TEST_MP3 = 'music/test.mp3' @@ -35,6 +36,10 @@ class TestPostProcessorsOnFakeData(TestCase): def setUp(self) -> None: + cfg = config.get() + cfg.save_lyrics = False + cfg.save_cover = False + http_pool.get() def test_infoytpp(self) -> None: