From 9bdbad75cd29880b63f1ebbaccb0aa1a03bed492 Mon Sep 17 00:00:00 2001 From: oskvr37 Date: Sat, 27 Jul 2024 17:15:30 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20add=20vnd.tidal.bts=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tiddl/__init__.py | 23 ++++++++++++++--------- tiddl/download.py | 34 ++++++++++++++++++++++++++++++---- tiddl/types.py | 5 ++++- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tiddl/__init__.py b/tiddl/__init__.py index 0a9b7cd..67d1e59 100644 --- a/tiddl/__init__.py +++ b/tiddl/__init__.py @@ -61,18 +61,23 @@ def main(): MASTER_QUALITIES: list[TrackQuality] = ["HI_RES_LOSSLESS", "LOSSLESS"] if track["audioQuality"] in MASTER_QUALITIES: - print("▶️ {0} quality - {1} bit, {2:.1f} kHz" - .format(TRACK_QUALITY[track['audioQuality']]['name'], - track['bitDepth'], - track['sampleRate'] / 1000)) + print( + "▶️ {0} quality - {1} bit, {2:.1f} kHz".format( + TRACK_QUALITY[track["audioQuality"]]["name"], + track["bitDepth"], + track["sampleRate"] / 1000, + ) + ) else: print(f"▶️ {TRACK_QUALITY[track['audioQuality']]['name']}") - if track["manifestMimeType"] == "application/dash+xml": - track_path = downloadTrack(config["settings"]["download_path"], track_id, track["manifest"]) - print(f"✨ Track saved in {track_path}") - else: - print(f"🚨 Mime type `{track["manifestMimeType"]}` not supported yet") + track_path = downloadTrack( + config["settings"]["download_path"], + track_id, + track["manifest"], + track["manifestMimeType"], + ) + print(f"✨ Track saved in {track_path}") if __name__ == "__main__": diff --git a/tiddl/download.py b/tiddl/download.py index 2d5c5d4..8001749 100644 --- a/tiddl/download.py +++ b/tiddl/download.py @@ -1,14 +1,29 @@ import requests +import json from os import makedirs from xml.etree.ElementTree import fromstring from base64 import b64decode +from typing import TypedDict, List + +from .types import ManifestMimeType def decodeManifest(manifest: str): return b64decode(manifest).decode() -def parseTrackManifest(xml_content: str): +def parseManifest(manifest: str): + class AudioFileInfo(TypedDict): + mimeType: str + codecs: str + encryptionType: str + urls: List[str] + + data: AudioFileInfo = json.loads(manifest) + return data + + +def parseManifestXML(xml_content: str): """ Parses XML manifest file of the track. """ @@ -61,9 +76,20 @@ def threadDownload(urls: list[str]) -> bytes: return data -def downloadTrack(path: str, file_name: str, manifest: str): - decoded_manifest = decodeManifest(manifest) - track_urls, codecs = parseTrackManifest(decoded_manifest) +def downloadTrack( + path: str, file_name: str, encoded_manifest: str, mime_type: ManifestMimeType +): + manifest = decodeManifest(encoded_manifest) + + match mime_type: + case "application/dash+xml": + track_urls, codecs = parseManifestXML(manifest) + case "application/vnd.tidal.bts": + data = parseManifest(manifest) + track_urls, codecs = data["urls"], data["codecs"] + case _: + raise ValueError(f"Unknown `mime_type`: {mime_type}") + track_data = threadDownload(track_urls) makedirs(path, exist_ok=True) diff --git a/tiddl/types.py b/tiddl/types.py index 53990bc..18f0697 100644 --- a/tiddl/types.py +++ b/tiddl/types.py @@ -115,12 +115,15 @@ class PlaylistResponse(TypedDict): items: List[PlaylistItem] +ManifestMimeType = Literal["application/dash+xml", "application/vnd.tidal.bts"] + + class TrackResponse(TypedDict): trackId: int assetPresentation: Literal["FULL"] audioMode: Literal["STEREO"] audioQuality: TrackQuality - manifestMimeType: Literal["application/dash+xml", "application/vnd.tidal.bts"] + manifestMimeType: ManifestMimeType manifestHash: str manifest: str albumReplayGain: float