Add cover downloader, add cfg.save_{lyrics,cover} (mainly for testing)
This commit is contained in:
parent
60497cf29d
commit
38cc43b84f
4 changed files with 85 additions and 7 deletions
|
@ -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)
|
||||||
|
|
44
backend/cover.py
Normal file
44
backend/cover.py
Normal file
|
@ -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)
|
|
@ -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,6 +69,7 @@ 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'])
|
||||||
|
|
||||||
|
if self.cfg.save_lyrics:
|
||||||
try:
|
try:
|
||||||
lyr_title, lyr_url = genius.search(title, artists[0])
|
lyr_title, lyr_url = genius.search(title, artists[0])
|
||||||
genius.raise_on_irrelevant_result(lyr_title, title, artists[0])
|
genius.raise_on_irrelevant_result(lyr_title, title, artists[0])
|
||||||
|
@ -71,4 +79,14 @@ class ID3TagsPP(PostProcessor):
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Reference in a new issue