diff --git a/docs/config.example.toml b/docs/config.example.toml index 7edbb5d..0cc93cc 100644 --- a/docs/config.example.toml +++ b/docs/config.example.toml @@ -91,6 +91,10 @@ embed_lyrics = false # embed track cover in the track file cover = false +# embed album review text to track COMMENT metadata field. +# only works when downloading album +album_review = false + [cover] # please don't confuse the cover from metadata with cover as a distinct file. diff --git a/tests/core/api/test_api_models_review.py b/tests/core/api/test_api_models_review.py new file mode 100644 index 0000000..59614aa --- /dev/null +++ b/tests/core/api/test_api_models_review.py @@ -0,0 +1,8 @@ +from tiddl.core.api.models.review import normalize_review_text + + +def test_normalize_review_text(): + text_before = 'Dropping on Halloween of 2017 with only a single day\'s advance notice, [wimpLink albumId="80611906"]Without Warning[/wimpLink] is a collaborative full-length between [wimpLink artistId="7279286"]21 Savage[/wimpLink], [wimpLink artistId="3958646"]Offset[/wimpLink] (of [wimpLink artistId="5024748"]Migos[/wimpLink]), and producer [wimpLink artistId="5012586"]Metro Boomin[/wimpLink], three of the most successful rap artists of the year. The release plays up its Halloween theme, with [wimpLink artistId="5012586"]Metro Boomin[/wimpLink] filling the tracks with scary sound effects and ominous beats, and the MCs delivering ghastly, violent lyrics. [wimpLink artistId="5198891"]Travis Scott[/wimpLink] and [wimpLink artistId="5906497"]Quavo[/wimpLink] contribute guest verses, and additional producers include Dre Moon, [wimpLink artistId="25917"]Southside[/wimpLink], and Cubeatz. [wimpLink albumId="80611906"]Without Warning[/wimpLink] was an immediate success, hitting the Top Five of the Billboard 200 albums chart following its release.' + text_after = "Dropping on Halloween of 2017 with only a single day's advance notice, Without Warning is a collaborative full-length between 21 Savage, Offset (of Migos), and producer Metro Boomin, three of the most successful rap artists of the year. The release plays up its Halloween theme, with Metro Boomin filling the tracks with scary sound effects and ominous beats, and the MCs delivering ghastly, violent lyrics. Travis Scott and Quavo contribute guest verses, and additional producers include Dre Moon, Southside, and Cubeatz. Without Warning was an immediate success, hitting the Top Five of the Billboard 200 albums chart following its release." + + assert normalize_review_text(text=text_before) == text_after diff --git a/tiddl/cli/commands/download/__init__.py b/tiddl/cli/commands/download/__init__.py index 4d60ae0..42efee3 100644 --- a/tiddl/cli/commands/download/__init__.py +++ b/tiddl/cli/commands/download/__init__.py @@ -172,11 +172,13 @@ def download_callback( artist: str = "", credits: list[AlbumItemsCredits.ItemWithCredits.CreditsEntry] = [], cover_data: bytes | None = None, + album_review: str = "", ) -> None: self.date = date self.artist = artist self.credits = credits self.cover_data = cover_data + self.album_review = album_review async def handle_resource(resource: TidalResource): async def handle_item( @@ -222,6 +224,7 @@ def download_callback( cover_data=cover_data, date=track_metadata.date, credits=track_metadata.credits, + comment=track_metadata.album_review, ) elif isinstance(item, Video): @@ -245,6 +248,16 @@ def download_callback( if album.cover and (CONFIG.metadata.cover or save_cover): cover = Cover(album.cover, size=CONFIG.cover.size) + album_review = "" + + if CONFIG.metadata.album_review: + try: + album_review = ctx.obj.api.get_album_review( + album_id=resource.id + ).normalized_text() + except Exception as e: + log.error(e) + while True: album_items = ctx.obj.api.get_album_items_credits( album_id=album.id, offset=offset @@ -264,6 +277,7 @@ def download_callback( date=str(album.releaseDate), artist=album.artist.name if album.artist else "", credits=album_item.credits, + album_review=album_review, ), ) ) diff --git a/tiddl/cli/config.py b/tiddl/cli/config.py index 76b41fe..a1ad493 100644 --- a/tiddl/cli/config.py +++ b/tiddl/cli/config.py @@ -27,6 +27,7 @@ class Config(BaseModel): enable: bool = True lyrics: bool = False cover: bool = False + album_review: bool = False metadata: MetadataConfig = MetadataConfig() diff --git a/tiddl/core/api/api.py b/tiddl/core/api/api.py index 178f2c3..165da45 100644 --- a/tiddl/core/api/api.py +++ b/tiddl/core/api/api.py @@ -26,6 +26,7 @@ from .models.base import ( TrackStream, VideoStream, ) +from .models.review import AlbumReview ID: TypeAlias = str | int @@ -96,6 +97,14 @@ class TidalAPI: expire_after=3600, ) + def get_album_review(self, album_id: ID): + return self.client.fetch( + AlbumReview, + f"albums/{album_id}/review", + {"countryCode": self.country_code}, + expire_after=3600, + ) + def get_artist(self, artist_id: ID): return self.client.fetch( Artist, diff --git a/tiddl/core/api/models/base.py b/tiddl/core/api/models/base.py index 60665dd..e4904ce 100644 --- a/tiddl/core/api/models/base.py +++ b/tiddl/core/api/models/base.py @@ -28,9 +28,11 @@ class Items(BaseModel): class ArtistAlbumsItems(Items): items: List[Album] + class ArtistVideosItems(Items): items: List[Video] + ItemType = Literal["track", "video"] diff --git a/tiddl/core/api/models/review.py b/tiddl/core/api/models/review.py new file mode 100644 index 0000000..12ba36b --- /dev/null +++ b/tiddl/core/api/models/review.py @@ -0,0 +1,30 @@ +import re + +from datetime import datetime +from pydantic import BaseModel + + +def normalize_review_text(text: str | None = None) -> str: + if not text: + return "" + + text = re.sub( + r"\[wimpLink\b[^\]]*\](.*?)\[/wimpLink\]", + r"\1", + text, + flags=re.DOTALL | re.IGNORECASE, + ) + + text = re.sub(r"\[/?wimpLink\b[^\]]*\]", "", text, flags=re.IGNORECASE) + + return text.strip() + + +class AlbumReview(BaseModel): + source: str + lastUpdated: datetime + text: str + summary: str + + def normalized_text(self) -> str: + return normalize_review_text(self.text) diff --git a/tiddl/core/metadata/track.py b/tiddl/core/metadata/track.py index e9d5642..c42f3cc 100644 --- a/tiddl/core/metadata/track.py +++ b/tiddl/core/metadata/track.py @@ -26,6 +26,7 @@ class Metadata: default_factory=list ) cover_data: bytes | None = None + comment: str = "" def add_flac_metadata(track_path: Path, metadata: Metadata) -> None: @@ -55,6 +56,7 @@ def add_flac_metadata(track_path: Path, metadata: Metadata) -> None: "YEAR": (str(date.year) if date else ""), "COPYRIGHT": metadata.copyright or "", "ISRC": metadata.isrc, + "COMMENT": metadata.comment, } ) @@ -94,6 +96,7 @@ def add_m4a_metadata(track_path: Path, metadata: Metadata) -> None: "artist": metadata.artists, "date": metadata.date, "copyright": metadata.copyright or "", + "comment": metadata.comment, } ) @@ -111,6 +114,7 @@ def add_track_metadata( lyrics: str = "", cover_data: bytes | None = None, credits: list[AlbumItemsCredits.ItemWithCredits.CreditsEntry] | None = None, + comment: str = "", ) -> None: """Add FLAC or M4A metadata based on file extension.""" @@ -128,6 +132,7 @@ def add_track_metadata( lyrics=lyrics or None, cover_data=cover_data, credits=credits or [], + comment=comment, ) ext = path.suffix.lower() diff --git a/tiddl/core/utils/format.py b/tiddl/core/utils/format.py index f3c26f1..5037245 100644 --- a/tiddl/core/utils/format.py +++ b/tiddl/core/utils/format.py @@ -63,6 +63,8 @@ def generate_template_data( copyright_ = item.copyright or "" bpm = item.bpm or 0 isrc = item.isrc or "" + # FIX audio quality should be returned from `get_existing_track_filename`. + # `item.audioQuality` tells highest quality of track - not quality we downloaded quality = item.audioQuality or "" else: # Video version = ""