mirror of
https://github.com/oskvr37/tiddl.git
synced 2026-06-13 04:05:08 +03:00
🏷️ remove models.track, add models.constants
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
import unittest
|
||||
|
||||
from tiddl.models import Track
|
||||
from tiddl.models.resource import Track
|
||||
from tiddl.utils import TidalResource, formatTrack
|
||||
|
||||
|
||||
|
||||
+23
-13
@@ -4,8 +4,18 @@ import json
|
||||
from pathlib import Path
|
||||
from requests import Session
|
||||
|
||||
from tiddl import models
|
||||
from .exceptions import AuthError, ApiError
|
||||
from tiddl.exceptions import AuthError, ApiError
|
||||
from tiddl.models.api import (
|
||||
AlbumItems,
|
||||
ArtistAlbumsItems,
|
||||
Favorites,
|
||||
PlaylistItems,
|
||||
SessionResponse,
|
||||
TrackStream,
|
||||
)
|
||||
from tiddl.models.constants import TrackQuality
|
||||
from tiddl.models.resource import Track, Album, Playlist
|
||||
from tiddl.models.search import Search
|
||||
|
||||
DEBUG = False
|
||||
API_URL = "https://api.tidal.com/v1"
|
||||
@@ -52,14 +62,14 @@ class TidalApi:
|
||||
return data
|
||||
|
||||
def getSession(self):
|
||||
return models.SessionResponse(
|
||||
return SessionResponse(
|
||||
**self._request(
|
||||
"sessions",
|
||||
)
|
||||
)
|
||||
|
||||
def getTrackStream(self, id: str | int, quality: models.TrackQuality):
|
||||
return models.TrackStream(
|
||||
def getTrackStream(self, id: str | int, quality: TrackQuality):
|
||||
return TrackStream(
|
||||
**self._request(
|
||||
f"tracks/{id}/playbackinfo",
|
||||
{
|
||||
@@ -71,7 +81,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def getTrack(self, id: str | int):
|
||||
return models.Track(
|
||||
return Track(
|
||||
**self._request(f"tracks/{id}", {"countryCode": self.country_code})
|
||||
)
|
||||
|
||||
@@ -83,7 +93,7 @@ class TidalApi:
|
||||
if onlyNonAlbum:
|
||||
params.update({"filter": "EPSANDSINGLES"})
|
||||
|
||||
return models.AristAlbumsItems(
|
||||
return ArtistAlbumsItems(
|
||||
**self._request(
|
||||
f"artists/{id}/albums",
|
||||
params,
|
||||
@@ -91,7 +101,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def getAlbum(self, id: str | int):
|
||||
return models.Album(
|
||||
return Album(
|
||||
**self._request(f"albums/{id}", {"countryCode": self.country_code})
|
||||
)
|
||||
|
||||
@@ -102,7 +112,7 @@ class TidalApi:
|
||||
logging.warning(f"Too big page, max page size is {MAX_LIMIT}")
|
||||
limit = MAX_LIMIT
|
||||
|
||||
return models.AlbumItems(
|
||||
return AlbumItems(
|
||||
**self._request(
|
||||
f"albums/{id}/items",
|
||||
{"countryCode": self.country_code, "limit": limit, "offset": offset},
|
||||
@@ -110,7 +120,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def getPlaylist(self, uuid: str):
|
||||
return models.Playlist(
|
||||
return Playlist(
|
||||
**self._request(
|
||||
f"playlists/{uuid}",
|
||||
{"countryCode": self.country_code},
|
||||
@@ -118,7 +128,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def getPlaylistItems(self, uuid: str, limit=PLAYLIST_LIMIT, offset=0):
|
||||
return models.PlaylistItems(
|
||||
return PlaylistItems(
|
||||
**self._request(
|
||||
f"playlists/{uuid}/items",
|
||||
{"countryCode": self.country_code, "limit": limit, "offset": offset},
|
||||
@@ -126,7 +136,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def getFavorites(self):
|
||||
return models.Favorites(
|
||||
return Favorites(
|
||||
**self._request(
|
||||
f"users/{self.user_id}/favorites/ids",
|
||||
{"countryCode": self.country_code},
|
||||
@@ -134,7 +144,7 @@ class TidalApi:
|
||||
)
|
||||
|
||||
def search(self, query: str):
|
||||
return models.Search(
|
||||
return Search(
|
||||
**self._request(
|
||||
"search", {"countryCode": self.country_code, "query": query}
|
||||
)
|
||||
|
||||
@@ -8,10 +8,12 @@ from .url import UrlGroup
|
||||
from ..ctx import Context, passContext
|
||||
|
||||
from tiddl.download import downloadTrackStream
|
||||
from tiddl.models import TrackArg, ARG_TO_QUALITY, Track, PlaylistItems, Album
|
||||
from tiddl.utils import formatTrack, trackExists, TidalResource
|
||||
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
|
||||
|
||||
|
||||
@click.command("download")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
from pydantic import BaseModel
|
||||
from pathlib import Path
|
||||
|
||||
from tiddl.models import TrackArg
|
||||
from tiddl.models.constants import TrackArg
|
||||
|
||||
|
||||
CONFIG_PATH = Path.home() / "tiddl.json"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ from pydantic import BaseModel
|
||||
from base64 import b64decode
|
||||
from xml.etree.ElementTree import fromstring
|
||||
|
||||
from tiddl.models import TrackStream
|
||||
from tiddl.models.api import TrackStream
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ from mutagen.flac import FLAC as MutagenFLAC, Picture
|
||||
from mutagen.easymp4 import EasyMP4 as MutagenEasyMP4
|
||||
from mutagen.mp4 import MP4Cover, MP4 as MutagenMP4
|
||||
|
||||
from tiddl.models import Track
|
||||
from tiddl.models.resource import Track
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
from typing import TypedDict, Literal
|
||||
|
||||
from .api import *
|
||||
from .track import *
|
||||
from .search import *
|
||||
|
||||
TrackArg = Literal["low", "normal", "high", "master"]
|
||||
|
||||
ARG_TO_QUALITY: dict[TrackArg, TrackQuality] = {
|
||||
"low": "LOW",
|
||||
"normal": "HIGH",
|
||||
"high": "LOSSLESS",
|
||||
"master": "HI_RES_LOSSLESS",
|
||||
}
|
||||
|
||||
QUALITY_TO_ARG = {v: k for k, v in ARG_TO_QUALITY.items()}
|
||||
|
||||
+28
-3
@@ -1,8 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Literal, Union
|
||||
|
||||
from .track import Track
|
||||
from .resource import Video, Album
|
||||
from .resource import Video, Album, Track, TrackQuality
|
||||
|
||||
__all__ = [
|
||||
"Client",
|
||||
"SessionResponse",
|
||||
"ArtistAlbumsItems",
|
||||
"AlbumItems",
|
||||
"PlaylistItems",
|
||||
"Favorites",
|
||||
"TrackStream",
|
||||
]
|
||||
|
||||
|
||||
class Client(BaseModel):
|
||||
@@ -27,7 +36,7 @@ class Items(BaseModel):
|
||||
totalNumberOfItems: int
|
||||
|
||||
|
||||
class AristAlbumsItems(Items):
|
||||
class ArtistAlbumsItems(Items):
|
||||
items: List[Album]
|
||||
|
||||
|
||||
@@ -80,3 +89,19 @@ class Favorites(BaseModel):
|
||||
VIDEO: List[str]
|
||||
TRACK: List[str]
|
||||
ARTIST: List[str]
|
||||
|
||||
|
||||
class TrackStream(BaseModel):
|
||||
trackId: int
|
||||
assetPresentation: Literal["FULL"]
|
||||
audioMode: Literal["STEREO"]
|
||||
audioQuality: TrackQuality
|
||||
manifestMimeType: Literal["application/dash+xml", "application/vnd.tidal.bts"]
|
||||
manifestHash: str
|
||||
manifest: str
|
||||
albumReplayGain: float
|
||||
albumPeakAmplitude: float
|
||||
trackReplayGain: float
|
||||
trackPeakAmplitude: float
|
||||
bitDepth: Optional[int] = None
|
||||
sampleRate: Optional[int] = None
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import Literal
|
||||
|
||||
TrackQuality = Literal["LOW", "HIGH", "LOSSLESS", "HI_RES_LOSSLESS"]
|
||||
TrackArg = Literal["low", "normal", "high", "master"]
|
||||
|
||||
ARG_TO_QUALITY: dict[TrackArg, TrackQuality] = {
|
||||
"low": "LOW",
|
||||
"normal": "HIGH",
|
||||
"high": "LOSSLESS",
|
||||
"master": "HI_RES_LOSSLESS",
|
||||
}
|
||||
|
||||
QUALITY_TO_ARG = {v: k for k, v in ARG_TO_QUALITY.items()}
|
||||
@@ -1,6 +1,57 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Literal, Dict
|
||||
from .constants import TrackQuality
|
||||
|
||||
|
||||
__all__ = ["Track", "Video", "Album", "Playlist"]
|
||||
|
||||
|
||||
class Track(BaseModel):
|
||||
|
||||
class Artist(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
type: str
|
||||
picture: Optional[str] = None
|
||||
|
||||
class Album(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
cover: Optional[str] = None
|
||||
vibrantColor: Optional[str] = None
|
||||
videoCover: Optional[str] = None
|
||||
|
||||
id: int
|
||||
title: str
|
||||
duration: int
|
||||
replayGain: float
|
||||
peak: float
|
||||
allowStreaming: bool
|
||||
streamReady: bool
|
||||
adSupportedStreamReady: bool
|
||||
djReady: bool
|
||||
stemReady: bool
|
||||
streamStartDate: Optional[datetime] = None
|
||||
premiumStreamingOnly: bool
|
||||
trackNumber: int
|
||||
volumeNumber: int
|
||||
version: Optional[str] = None
|
||||
popularity: int
|
||||
copyright: str
|
||||
bpm: Optional[int] = None
|
||||
url: str
|
||||
isrc: str
|
||||
editable: bool
|
||||
explicit: bool
|
||||
audioQuality: TrackQuality
|
||||
audioModes: List[str]
|
||||
mediaMetadata: Dict[str, List[str]]
|
||||
# for real, artist can be None?
|
||||
artist: Optional[Artist] = None
|
||||
artists: List[Artist]
|
||||
album: Album
|
||||
mixes: Dict[str, str]
|
||||
|
||||
|
||||
class Video(BaseModel):
|
||||
@@ -53,7 +104,7 @@ class Album(BaseModel):
|
||||
picture: Optional[str] = None
|
||||
|
||||
class MediaMetadata(BaseModel):
|
||||
tags: List[Literal['LOSSLESS', 'HIRES_LOSSLESS']]
|
||||
tags: List[Literal["LOSSLESS", "HIRES_LOSSLESS"]]
|
||||
|
||||
id: int
|
||||
title: str
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Literal, Dict, Union
|
||||
|
||||
from .track import Track
|
||||
from .resource import Playlist, Album, Video
|
||||
from .resource import Track, Playlist, Album, Video
|
||||
from .api import Items
|
||||
|
||||
|
||||
class SearchAlbum(Album):
|
||||
# TODO: remove the artist field instead of making it None
|
||||
artist: None = None
|
||||
|
||||
|
||||
class Artist(BaseModel):
|
||||
|
||||
class Role(BaseModel):
|
||||
@@ -40,10 +34,15 @@ class Artist(BaseModel):
|
||||
|
||||
|
||||
class Search(BaseModel):
|
||||
|
||||
class Artists(Items):
|
||||
items: List[Artist]
|
||||
|
||||
class Albums(Items):
|
||||
class SearchAlbum(Album):
|
||||
# TODO: remove the artist field instead of making it None
|
||||
artist: None = None
|
||||
|
||||
items: List[SearchAlbum]
|
||||
|
||||
class Playlists(Items):
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict, Literal
|
||||
|
||||
|
||||
TrackQuality = Literal["LOW", "HIGH", "LOSSLESS", "HI_RES_LOSSLESS"]
|
||||
ManifestMimeType = Literal["application/dash+xml", "application/vnd.tidal.bts"]
|
||||
|
||||
|
||||
class TrackStream(BaseModel):
|
||||
trackId: int
|
||||
assetPresentation: Literal["FULL"]
|
||||
audioMode: Literal["STEREO"]
|
||||
audioQuality: TrackQuality
|
||||
manifestMimeType: ManifestMimeType
|
||||
manifestHash: str
|
||||
manifest: str
|
||||
albumReplayGain: float
|
||||
albumPeakAmplitude: float
|
||||
trackReplayGain: float
|
||||
trackPeakAmplitude: float
|
||||
bitDepth: Optional[int] = None
|
||||
sampleRate: Optional[int] = None
|
||||
|
||||
|
||||
class TrackArtist(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
type: str
|
||||
picture: Optional[str] = None
|
||||
|
||||
|
||||
class TrackAlbum(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
cover: Optional[str] = None
|
||||
vibrantColor: Optional[str] = None
|
||||
videoCover: Optional[str] = None
|
||||
|
||||
|
||||
class Track(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
duration: int
|
||||
replayGain: float
|
||||
peak: float
|
||||
allowStreaming: bool
|
||||
streamReady: bool
|
||||
adSupportedStreamReady: bool
|
||||
djReady: bool
|
||||
stemReady: bool
|
||||
streamStartDate: Optional[datetime] = None
|
||||
premiumStreamingOnly: bool
|
||||
trackNumber: int
|
||||
volumeNumber: int
|
||||
version: Optional[str] = None
|
||||
popularity: int
|
||||
copyright: str
|
||||
bpm: Optional[int] = None
|
||||
url: str
|
||||
isrc: str
|
||||
editable: bool
|
||||
explicit: bool
|
||||
audioQuality: TrackQuality
|
||||
audioModes: List[str]
|
||||
mediaMetadata: Dict[str, List[str]]
|
||||
# for real, artist can be None?
|
||||
artist: Optional[TrackArtist] = None
|
||||
artists: List[TrackArtist]
|
||||
album: TrackAlbum
|
||||
mixes: Dict[str, str]
|
||||
+5
-2
@@ -6,7 +6,8 @@ from pathlib import Path
|
||||
|
||||
from typing import Literal, get_args
|
||||
|
||||
from tiddl.models import Track, TrackQuality, QUALITY_TO_ARG
|
||||
from tiddl.models.constants import TrackQuality, QUALITY_TO_ARG
|
||||
from tiddl.models.resource import Track
|
||||
|
||||
ResourceTypeLiteral = Literal["track", "album", "playlist", "artist"]
|
||||
|
||||
@@ -49,7 +50,9 @@ def sanitizeString(string: str) -> str:
|
||||
return re.sub(pattern, "", string)
|
||||
|
||||
|
||||
def formatTrack(template: str, track: Track, album_artist="", playlist_title="", playlist_index=0) -> str:
|
||||
def formatTrack(
|
||||
template: str, track: Track, album_artist="", playlist_title="", playlist_index=0
|
||||
) -> str:
|
||||
artist = sanitizeString(track.artist.name) if track.artist else ""
|
||||
features = [
|
||||
sanitizeString(track_artist.name)
|
||||
|
||||
Reference in New Issue
Block a user