Skip to content

Commit

Permalink
Added support for Tidal lyrics and fixed parsing error when preferred…
Browse files Browse the repository at this point in the history
… quality too high (#736)

* fixed json error when preferred quality not found for track

* added tidal lyrics support

* improve mp3 lyrics support

* handle error when there are no lyrics (404)

* change error to warning
  • Loading branch information
M4TH1EU authored Dec 7, 2024
1 parent 1aad9f0 commit 4c9baf9
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 2 deletions.
19 changes: 17 additions & 2 deletions streamrip/client/tidal.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import re
import time
from json import JSONDecodeError

import aiohttp

Expand Down Expand Up @@ -101,6 +102,17 @@ async def get_metadata(self, item_id: str, media_type: str) -> dict:

item["albums"] = album_resp["items"]
item["albums"].extend(ep_resp["items"])
elif media_type == "track":
try:
resp = await self._api_request(f"tracks/{str(item_id)}/lyrics", base="https://listen.tidal.com/v1")

# Use unsynced lyrics for MP3, synced for others (FLAC, OPUS, etc)
if self.global_config.session.conversion.enabled and self.global_config.session.conversion.codec.upper() == "MP3":
item["lyrics"] = resp.get("lyrics") or ''
else:
item["lyrics"] = resp.get("subtitles") or resp.get("lyrics") or ''
except TypeError as e:
logger.warning(f"Failed to get lyrics for {item_id}: {e}")

logger.debug(item)
return item
Expand Down Expand Up @@ -140,6 +152,9 @@ async def get_downloadable(self, track_id: str, quality: int):
manifest = json.loads(base64.b64decode(resp["manifest"]).decode("utf-8"))
except KeyError:
raise Exception(resp["userMessage"])
except JSONDecodeError:
logger.warning(f"Failed to get manifest for {track_id}. Retrying with lower quality.")
return await self.get_downloadable(track_id, quality - 1)

logger.debug(manifest)
enc_key = manifest.get("keyId")
Expand Down Expand Up @@ -306,7 +321,7 @@ async def _api_post(self, url, data, auth: aiohttp.BasicAuth | None = None) -> d
async with self.session.post(url, data=data, auth=auth) as resp:
return await resp.json()

async def _api_request(self, path: str, params=None) -> dict:
async def _api_request(self, path: str, params=None, base: str = BASE) -> dict:
"""Handle Tidal API requests.
:param path:
Expand All @@ -321,7 +336,7 @@ async def _api_request(self, path: str, params=None) -> dict:
params["limit"] = 100

async with self.rate_limiter:
async with self.session.get(f"{BASE}/{path}", params=params) as resp:
async with self.session.get(f"{base}/{path}", params=params) as resp:
if resp.status == 404:
logger.warning("TIDAL: track not found", resp)
raise NonStreamableError("TIDAL: Track not found")
Expand Down
1 change: 1 addition & 0 deletions streamrip/metadata/tagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def _attr_from_meta(self, meta: TrackMetadata, attr: str) -> str | None:
"discnumber",
"composer",
"isrc",
"lyrics",
}
if attr in in_trackmetadata:
if attr == "album":
Expand Down
4 changes: 4 additions & 0 deletions streamrip/metadata/track.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class TrackMetadata:
discnumber: int
composer: str | None
isrc: str | None = None
lyrics: str | None = ""

@classmethod
def from_qobuz(cls, album: AlbumMetadata, resp: dict) -> TrackMetadata | None:
Expand Down Expand Up @@ -170,6 +171,8 @@ def from_tidal(cls, album: AlbumMetadata, track) -> TrackMetadata:
else:
artist = track["artist"]["name"]

lyrics = track.get("lyrics", "")

quality_map: dict[str, int] = {
"LOW": 0,
"HIGH": 1,
Expand Down Expand Up @@ -209,6 +212,7 @@ def from_tidal(cls, album: AlbumMetadata, track) -> TrackMetadata:
discnumber=discnumber,
composer=None,
isrc=isrc,
lyrics=lyrics
)

@classmethod
Expand Down

0 comments on commit 4c9baf9

Please sign in to comment.