mirror of
https://github.com/oskvr37/tiddl.git
synced 2026-06-13 04:05:08 +03:00
@@ -52,6 +52,10 @@ class TestApi(unittest.TestCase):
|
||||
album_items = self.api.getAlbumItems(103805723, limit=10, offset=10)
|
||||
self.assertEqual(len(album_items.items), 4)
|
||||
|
||||
def test_album_items_credits(self):
|
||||
album_items = self.api.getAlbumItemsCredits(103805723, limit=10)
|
||||
self.assertEqual(len(album_items.items), 10)
|
||||
|
||||
def test_playlist(self):
|
||||
playlist = self.api.getPlaylist("84974059-76af-406a-aede-ece2b78fa372")
|
||||
self.assertEqual(playlist.title, "Kanye West Essentials")
|
||||
|
||||
@@ -9,6 +9,7 @@ from requests import Session
|
||||
from tiddl.models.api import (
|
||||
Album,
|
||||
AlbumItems,
|
||||
AlbumItemsCredits,
|
||||
Artist,
|
||||
ArtistAlbumsItems,
|
||||
Favorites,
|
||||
@@ -107,6 +108,19 @@ class TidalApi:
|
||||
},
|
||||
)
|
||||
|
||||
def getAlbumItemsCredits(
|
||||
self, album_id: str | int, limit=LIMITS.ALBUM_ITEMS, offset=0
|
||||
):
|
||||
return self.fetch(
|
||||
AlbumItemsCredits,
|
||||
f"albums/{album_id}/items/credits",
|
||||
{
|
||||
"countryCode": self.country_code,
|
||||
"limit": ensureLimit(limit, self.LIMITS.ALBUM_ITEMS_MAX),
|
||||
"offset": offset,
|
||||
},
|
||||
)
|
||||
|
||||
def getArtist(self, artist_id: str | int):
|
||||
return self.fetch(
|
||||
Artist, f"artists/{artist_id}", {"countryCode": self.country_code}
|
||||
|
||||
@@ -20,7 +20,7 @@ from tiddl.metadata import addMetadata, Cover
|
||||
from tiddl.exceptions import ApiError, AuthError
|
||||
from tiddl.models.constants import TrackArg, ARG_TO_QUALITY
|
||||
from tiddl.models.resource import Track, Album
|
||||
from tiddl.models.api import PlaylistItems, AlbumItems
|
||||
from tiddl.models.api import PlaylistItems, AlbumItemsCredits
|
||||
|
||||
SinglesFilter = Literal["none", "only", "include"]
|
||||
|
||||
@@ -60,7 +60,12 @@ def DownloadCommand(
|
||||
|
||||
api = ctx.obj.getApi()
|
||||
|
||||
def downloadTrack(track: Track, file_name: str, cover_data=b""):
|
||||
def downloadTrack(
|
||||
track: Track,
|
||||
file_name: str,
|
||||
cover_data=b"",
|
||||
credits: List[AlbumItemsCredits.ItemWithCredits.CreditsEntry] = [],
|
||||
):
|
||||
if not track.allowStreaming:
|
||||
click.echo(
|
||||
f"{click.style('✖', 'yellow')} Track {click.style(file_name, 'yellow')} does not allow streaming"
|
||||
@@ -109,16 +114,18 @@ def DownloadCommand(
|
||||
if not cover_data and track.album.cover:
|
||||
cover_data = Cover(track.album.cover).content
|
||||
|
||||
addMetadata(full_path, track, cover_data)
|
||||
addMetadata(full_path, track, cover_data=cover_data, credits=credits)
|
||||
|
||||
def downloadAlbum(album: Album):
|
||||
click.echo(f"★ Album {album.title}")
|
||||
|
||||
all_items: List[Union[AlbumItems.VideoItem, AlbumItems.TrackItem]] = []
|
||||
all_items: List[
|
||||
Union[AlbumItemsCredits.VideoItem, AlbumItemsCredits.TrackItem]
|
||||
] = []
|
||||
offset = 0
|
||||
|
||||
while True:
|
||||
album_items = api.getAlbumItems(album.id, offset=offset)
|
||||
album_items = api.getAlbumItemsCredits(album.id, offset=offset)
|
||||
all_items.extend(album_items.items)
|
||||
|
||||
if (
|
||||
@@ -142,7 +149,10 @@ def DownloadCommand(
|
||||
)
|
||||
|
||||
downloadTrack(
|
||||
track=track, file_name=file_name, cover_data=cover_data
|
||||
track=track,
|
||||
file_name=file_name,
|
||||
cover_data=cover_data,
|
||||
credits=item.credits,
|
||||
)
|
||||
|
||||
def handleResource(resource: TidalResource):
|
||||
|
||||
+72
-22
@@ -3,17 +3,26 @@ import requests
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mutagen.flac import FLAC as MutagenFLAC, Picture
|
||||
from mutagen.easymp4 import EasyMP4 as MutagenEasyMP4
|
||||
from mutagen.mp4 import MP4Cover, MP4 as MutagenMP4
|
||||
from mutagen.flac import FLAC as MutagenFLAC
|
||||
from mutagen.flac import Picture
|
||||
from mutagen.mp4 import MP4 as MutagenMP4
|
||||
from mutagen.mp4 import MP4Cover
|
||||
|
||||
from tiddl.models.resource import Track
|
||||
from tiddl.models.api import AlbumItemsCredits
|
||||
|
||||
from typing import List
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def addMetadata(track_path: Path, track: Track, cover_data=b""):
|
||||
def addMetadata(
|
||||
track_path: Path,
|
||||
track: Track,
|
||||
cover_data=b"",
|
||||
credits: List[AlbumItemsCredits.ItemWithCredits.CreditsEntry] = [],
|
||||
):
|
||||
extension = track_path.suffix
|
||||
|
||||
if extension == ".flac":
|
||||
@@ -23,32 +32,73 @@ def addMetadata(track_path: Path, track: Track, cover_data=b""):
|
||||
picture.data = cover_data
|
||||
picture.mime = "image/jpeg"
|
||||
metadata.add_picture(picture)
|
||||
|
||||
metadata["TITLE"] = track.title
|
||||
metadata["WORK"] = track.title
|
||||
metadata["TRACKNUMBER"] = str(track.trackNumber)
|
||||
metadata["DISCNUMBER"] = str(track.volumeNumber)
|
||||
|
||||
if track.artist:
|
||||
metadata["ARTIST"] = track.artist.name
|
||||
|
||||
metadata["ARTISTS"] = [artist.name for artist in track.artists]
|
||||
metadata["ALBUM"] = track.album.title
|
||||
metadata["ALBUMARTIST"] = ", ".join(
|
||||
[artist.name.strip() for artist in track.artists]
|
||||
)
|
||||
|
||||
if track.streamStartDate:
|
||||
metadata["DATE"] = track.streamStartDate.strftime("%Y-%m-%d")
|
||||
metadata["ORIGINALDATE"] = track.streamStartDate.strftime(
|
||||
"%Y-%m-%d"
|
||||
)
|
||||
metadata["ORIGINALYEAR"] = str(track.streamStartDate.strftime("%Y"))
|
||||
|
||||
metadata["COPYRIGHT"] = track.copyright
|
||||
metadata["ISRC"] = track.isrc
|
||||
|
||||
if track.bpm:
|
||||
metadata["BPM"] = str(track.bpm)
|
||||
|
||||
for entry in credits:
|
||||
metadata[entry.type.upper()] = [
|
||||
contributor.name for contributor in entry.contributors
|
||||
]
|
||||
|
||||
elif extension == ".m4a":
|
||||
if cover_data:
|
||||
metadata = MutagenMP4(track_path)
|
||||
metadata["covr"] = [MP4Cover(cover_data, imageformat=MP4Cover.FORMAT_JPEG)]
|
||||
metadata["covr"] = [
|
||||
MP4Cover(cover_data, imageformat=MP4Cover.FORMAT_JPEG)
|
||||
]
|
||||
metadata.save(track_path)
|
||||
|
||||
metadata = MutagenEasyMP4(track_path)
|
||||
metadata.update(
|
||||
{
|
||||
"title": track.title,
|
||||
"tracknumber": str(track.trackNumber),
|
||||
"discnumber": str(track.volumeNumber),
|
||||
"copyright": track.copyright,
|
||||
"albumartist": track.artist.name if track.artist else "",
|
||||
"artist": ";".join(
|
||||
[artist.name.strip() for artist in track.artists]
|
||||
),
|
||||
"album": track.album.title,
|
||||
"date": str(track.streamStartDate)
|
||||
if track.streamStartDate
|
||||
else "",
|
||||
"bpm": str(track.bpm or ""),
|
||||
}
|
||||
)
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown file extension: {extension}")
|
||||
|
||||
new_metadata: dict[str, str] = {
|
||||
"title": track.title,
|
||||
"trackNumber": str(track.trackNumber),
|
||||
"discnumber": str(track.volumeNumber),
|
||||
"copyright": track.copyright,
|
||||
"albumartist": track.artist.name if track.artist else "",
|
||||
"artist": ";".join([artist.name.strip() for artist in track.artists]),
|
||||
"album": track.album.title,
|
||||
"date": str(track.streamStartDate) if track.streamStartDate else "",
|
||||
}
|
||||
|
||||
metadata.update(new_metadata)
|
||||
|
||||
try:
|
||||
metadata.save(track_path)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set metadata for {extension}: {e}")
|
||||
logger.error(f"Failed to add metadata to {track_path}: {e}")
|
||||
|
||||
|
||||
class Cover:
|
||||
@@ -62,9 +112,7 @@ class Cover:
|
||||
self.uid = uid
|
||||
|
||||
formatted_uid = uid.replace("-", "/")
|
||||
self.url = (
|
||||
f"https://resources.tidal.com/images/{formatted_uid}/{size}x{size}.jpg"
|
||||
)
|
||||
self.url = f"https://resources.tidal.com/images/{formatted_uid}/{size}x{size}.jpg"
|
||||
|
||||
logger.debug((self.uid, self.url))
|
||||
|
||||
@@ -74,7 +122,9 @@ class Cover:
|
||||
req = requests.get(self.url)
|
||||
|
||||
if req.status_code != 200:
|
||||
logger.error(f"could not download cover. ({req.status_code}) {self.url}")
|
||||
logger.error(
|
||||
f"could not download cover. ({req.status_code}) {self.url}"
|
||||
)
|
||||
return b""
|
||||
|
||||
logger.debug(f"got cover: {self.uid}")
|
||||
|
||||
@@ -54,6 +54,29 @@ class AlbumItems(Items):
|
||||
items: List[Union[TrackItem, VideoItem]]
|
||||
|
||||
|
||||
class AlbumItemsCredits(Items):
|
||||
class ItemWithCredits(BaseModel):
|
||||
class CreditsEntry(BaseModel):
|
||||
class Contributor(BaseModel):
|
||||
name: str
|
||||
id: Optional[int] = None
|
||||
|
||||
type: str
|
||||
contributors: List[Contributor]
|
||||
|
||||
credits: List[CreditsEntry]
|
||||
|
||||
class VideoItem(ItemWithCredits):
|
||||
item: Video
|
||||
type: ItemType = "video"
|
||||
|
||||
class TrackItem(ItemWithCredits):
|
||||
item: Track
|
||||
type: ItemType = "track"
|
||||
|
||||
items: List[Union[TrackItem, VideoItem]]
|
||||
|
||||
|
||||
class PlaylistItems(Items):
|
||||
class PlaylistVideoItem(BaseModel):
|
||||
class PlaylistVideo(Video):
|
||||
|
||||
Reference in New Issue
Block a user