feat: add lrclib and genius providers (#5)
* feat: lrclib and genius providers * revert `prepend_header` to true * change providers order --------- Co-authored-by: mrsobakin <68982655+mrsobakin@users.noreply.github.com>
This commit is contained in:
parent
d6b36886ba
commit
bad4a355e8
8 changed files with 103 additions and 19 deletions
|
@ -1,7 +1,7 @@
|
|||
[providers]
|
||||
order = ["musixmatch", "kugou"]
|
||||
order = ["musixmatch", "kugou", "lrclib", "genius"]
|
||||
delay = 10
|
||||
prepend_header = true
|
||||
|
||||
[providers.musixmatch]
|
||||
token = "123456"
|
||||
token = ""
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import time
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Initialize classes from lrc_dl/providers
|
||||
import lrc_dl.providers
|
||||
import lrc_dl.providers as _
|
||||
from lrc_dl.core import Song
|
||||
from lrc_dl.registry import Registry
|
||||
from lrc_dl.config import LyricsDlConfig
|
||||
|
@ -23,11 +23,12 @@ class LyricsDl:
|
|||
self.providers = []
|
||||
|
||||
for name in config.order:
|
||||
Provider = providers_classes[name]
|
||||
provider_config = config.providers_configs.get(name)
|
||||
Provider = providers_classes.get(name)
|
||||
|
||||
if not provider_config:
|
||||
provider_config = {}
|
||||
if not Provider:
|
||||
continue
|
||||
|
||||
provider_config = config.providers_configs.get(name, {})
|
||||
|
||||
try:
|
||||
provider = Provider(**provider_config)
|
||||
|
@ -57,7 +58,7 @@ class LyricsDl:
|
|||
|
||||
return lyrics
|
||||
|
||||
self.logger.info(f"[{provider.name}] No lyrics was found!")
|
||||
self.logger.info(f"[{provider.name}] No lyrics were found!")
|
||||
|
||||
return None
|
||||
|
||||
|
@ -78,7 +79,7 @@ class LyricsDl:
|
|||
lyrics = self.fetch_lyrics(song)
|
||||
|
||||
if not lyrics:
|
||||
self.logger.error("[lrc-dl] No lyrics was found!")
|
||||
self.logger.error("[lrc-dl] No lyrics were found!")
|
||||
return True
|
||||
|
||||
with open(lyrics_path, "w") as f:
|
||||
|
|
|
@ -21,7 +21,7 @@ CONFIG_PATH = _get_config_file()
|
|||
|
||||
@dataclass
|
||||
class LyricsDlConfig:
|
||||
order: list[str] = field(default_factory=lambda: ["kugou", "youtube"])
|
||||
order: list[str] = field(default_factory=lambda: ["kugou", "lrclib", "genius"])
|
||||
delay: float | None = 10
|
||||
prepend_header: bool = True
|
||||
providers_configs: dict[str, dict] = field(default_factory=lambda: {})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from lrc_dl.providers import musixmatch
|
||||
from lrc_dl.providers import kugou
|
||||
from lrc_dl.providers import lrclib
|
||||
from lrc_dl.providers import musixmatch
|
||||
from lrc_dl.providers import genius
|
||||
from lrc_dl.providers import youtube
|
||||
|
|
62
lrc_dl/providers/genius.py
Normal file
62
lrc_dl/providers/genius.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# https://github.com/jeffvli/feishin/blob/development/src/main/features/core/lyrics/genius.ts
|
||||
import re
|
||||
from typing import Optional
|
||||
import httpx
|
||||
import bs4
|
||||
|
||||
from lrc_dl.core import Song, AbstractProvider
|
||||
from lrc_dl.registry import lyrics_provider
|
||||
|
||||
|
||||
def _format_div(div: bs4.Tag):
|
||||
for br in div.find_all('br'):
|
||||
br.replace_with('\n') # type: ignore
|
||||
text = div.get_text().strip()
|
||||
text = re.sub(r"\[.*\]\n", '', text).strip()
|
||||
|
||||
# remove extra newlines
|
||||
return '\n\n'.join(['\n'.join(x.split('\n')) for x in text.split('\n\n')])
|
||||
|
||||
|
||||
@lyrics_provider
|
||||
class Genius(AbstractProvider):
|
||||
name = "genius"
|
||||
|
||||
def fetch_lyrics(self, song: Song) -> Optional[str]:
|
||||
r = httpx.get('https://genius.com/api/search/song', params={
|
||||
'per_page': 1,
|
||||
'q': f'{song.artist} {song.title}'
|
||||
})
|
||||
|
||||
if r.status_code != 200 or 'application/json' not in r.headers.get('content-type', ''):
|
||||
return
|
||||
|
||||
hits = r.json().get('response', {}).get('sections', [{}])[0].get('hits')
|
||||
|
||||
if not hits:
|
||||
return
|
||||
|
||||
url: str = hits[0].get('result', {}).get('url')
|
||||
|
||||
if not url:
|
||||
return
|
||||
|
||||
r = httpx.get(url, headers={
|
||||
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0'
|
||||
})
|
||||
|
||||
if r.status_code != 200:
|
||||
return
|
||||
|
||||
soup = bs4.BeautifulSoup(r.text, features='html.parser')
|
||||
div = soup.select_one('div.lyrics')
|
||||
|
||||
if div:
|
||||
return _format_div(div)
|
||||
|
||||
div = soup.select_one('div[class^=Lyrics__Container]')
|
||||
|
||||
if not div:
|
||||
return
|
||||
|
||||
return _format_div(div)
|
18
lrc_dl/providers/lrclib.py
Normal file
18
lrc_dl/providers/lrclib.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from typing import Optional
|
||||
import httpx
|
||||
|
||||
from lrc_dl.core import Song, AbstractProvider
|
||||
from lrc_dl.registry import lyrics_provider
|
||||
|
||||
@lyrics_provider
|
||||
class LrcLib(AbstractProvider):
|
||||
name = "lrclib"
|
||||
|
||||
def fetch_lyrics(self, song: Song, with_album = True) -> Optional[str]:
|
||||
r = httpx.get("https://lrclib.net/api/get", params={
|
||||
'track_name': song.title,
|
||||
'artist_name': song.artist,
|
||||
'album_name': song.album if with_album else None,
|
||||
}).json()
|
||||
|
||||
return r.get('syncedLyrics') or r.get('plainLyrics') or (self.fetch_lyrics(song, False) if with_album else None)
|
|
@ -4,14 +4,14 @@ from lrc_dl.core import AbstractProvider
|
|||
class Registry:
|
||||
providers: dict[str, type[AbstractProvider]] = {}
|
||||
|
||||
@staticmethod
|
||||
def get_synced_providers() -> dict[str, type[AbstractProvider]]:
|
||||
@classmethod
|
||||
def get_synced_providers(cls) -> dict[str, type[AbstractProvider]]:
|
||||
# TODO: stub
|
||||
return dict(Registry.providers)
|
||||
return dict(cls.providers)
|
||||
|
||||
@staticmethod
|
||||
def register_provider(provider_class: type[AbstractProvider]) -> None:
|
||||
Registry.providers[provider_class.name] = provider_class
|
||||
@classmethod
|
||||
def register_provider(cls, provider_class: type[AbstractProvider]) -> None:
|
||||
cls.providers[provider_class.name] = provider_class
|
||||
|
||||
|
||||
def lyrics_provider(cls: type[AbstractProvider]) -> type[AbstractProvider]:
|
||||
|
|
1
setup.py
1
setup.py
|
@ -15,6 +15,7 @@ setup(
|
|||
},
|
||||
install_requires=[
|
||||
"httpx>=0.24.1",
|
||||
"beautifulsoup4>=4.13.3",
|
||||
"mutagen>=1.46.0",
|
||||
"yt-dlp>=2023.10.13",
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue