diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index 7258c8f..7b86f1c 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -86,6 +86,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): session: tidalapi.Session token_from_storage: bool = False settings: Settings + is_pkce: bool def __init__(self, settings: Settings = None): self.cls_model = ModelToken @@ -110,9 +111,13 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): return True - def login_token(self) -> bool: + def login_token(self, do_pkce: bool = True) -> bool: result = False + # Do not login via PKCE, if HiRes LOSSLESS is not set as global audio quality. + if self.session.audio_quality != tidalapi.media.Quality.hi_res_lossless: + do_pkce = False + if self.token_from_storage: try: result = self.session.load_oauth_session( @@ -120,8 +125,9 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): self.data.access_token, self.data.refresh_token, self.data.expiry_time, - is_pkce=True, + is_pkce=do_pkce, ) + self.is_pkce = do_pkce except HTTPError: result = False diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index be7fb13..da3d3a0 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -12,9 +12,9 @@ import requests from requests.exceptions import HTTPError from rich.progress import Progress, TaskID from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video -from tidalapi.media import AudioExtensions, StreamManifest, VideoExtensions +from tidalapi.media import AudioExtensions, Quality, StreamManifest, VideoExtensions -from tidal_dl_ng.config import Settings +from tidal_dl_ng.config import Settings, Tidal from tidal_dl_ng.constants import EXTENSION_LYRICS, REQUESTS_TIMEOUT_SEC, MediaType, SkipExisting from tidal_dl_ng.helper.decryption import decrypt_file, decrypt_security_token from tidal_dl_ng.helper.exceptions import MediaMissing @@ -174,6 +174,7 @@ class Download: media_type: MediaType = None, video_download: bool = True, download_delay: bool = False, + quality: Quality | None = None, ) -> (bool, str): try: if media_id and media_type: @@ -202,6 +203,10 @@ class Download: return False, "" + # If a quality is explicitly set, change it. + if quality: + quality_old: Quality = self.quality_adjust(quality) + # Get extension. file_extension: str @@ -251,6 +256,10 @@ class Download: status_download: bool = not file_exists + if quality: + # Set quality back to the global user value + self.quality_adjust(quality_old) + # Whether a file was downloaded or skipped and the download delay is enabled, wait until the next download. # Only use this, if you have a list of several Track items. if download_delay: @@ -261,6 +270,20 @@ class Download: return status_download, path_file + def quality_adjust(self, quality) -> Quality: + # Save original quality settings + quality_old: Quality = self.session.audio_quality + self.session.audio_quality = quality + tidal: Tidal = Tidal() + + # If track is not requested as hires_lossless do not use PKCE, because BTS downloads are faster than MPD. + if quality == Quality.hi_res_lossless and not tidal.is_pkce: + tidal.login_token(do_pkce=True) + elif quality != Quality.hi_res_lossless and tidal.is_pkce: + tidal.login_token(do_pkce=False) + + return quality_old + def _move_lyrics(self, file_media_dst: str, file_media_src: str): # Build tmp lyrics filename tmp_lyrics_file_path: str = file_media_src + EXTENSION_LYRICS @@ -340,6 +363,7 @@ class Download: media_type: MediaType = None, video_download: bool = False, download_delay: bool = True, + quality: Quality | None = None, ): # If no media instance is provided, we need to create the media instance. if media_id and media_type: @@ -381,8 +405,7 @@ class Download: for media in items: # Download the item. status_download, result_path_file = self.item( - media=media, - file_template=file_name_relative, + media=media, file_template=file_name_relative, quality=quality ) # Advance progress bar. diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index dd500fc..903e8e8 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -9,6 +9,7 @@ from tidal_dl_ng import __version__, update_available from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion from tidal_dl_ng.helper.gui import ( get_queue_download_media, + get_queue_download_quality, get_results_media_item, get_user_list_media_item, set_queue_download_media, @@ -542,7 +543,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ) -> QueueDownloadItem | bool: result: QueueDownloadItem | False name: str = "" - quality: str = "" + quality: Quality explicit: str = "" # Check if item is available on TIDAL. @@ -798,10 +799,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): result: QueueDownloadStatus item: QtWidgets.QTreeWidgetItem = items[0] media: Track | Album | Playlist | Video | Mix | Artist = get_queue_download_media(item) + quality: Quality = get_queue_download_quality(item) try: self.s_queue_download_item_downloading.emit(item) - result = self.on_queue_download(media) + result = self.on_queue_download(media, quality=quality) if result == QueueDownloadStatus.Finished: self.s_queue_download_item_finished.emit(item) @@ -827,7 +829,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def queue_download_item_status(self, item: QtWidgets.QTreeWidgetItem, status: str) -> None: item.setText(0, status) - def on_queue_download(self, media: Track | Album | Playlist | Video | Mix | Artist) -> QueueDownloadStatus: + def on_queue_download( + self, media: Track | Album | Playlist | Video | Mix | Artist, quality: Quality | None = None + ) -> QueueDownloadStatus: result: QueueDownloadStatus items_media: [Track | Album | Playlist | Video | Mix | Artist] @@ -839,12 +843,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): download_delay: bool = bool(isinstance(media, Track | Video) and self.settings.data.download_delay) for item_media in items_media: - result = self.download(item_media, self.dl, delay_track=download_delay) + result = self.download(item_media, self.dl, delay_track=download_delay, quality=quality) return result def download( - self, media: Track | Album | Playlist | Video | Mix | Artist, dl: Download, delay_track: bool = False + self, + media: Track | Album | Playlist | Video | Mix | Artist, + dl: Download, + delay_track: bool = False, + quality: Quality | None = None, ) -> QueueDownloadStatus: result_dl: bool path_file: str @@ -855,13 +863,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): file_template = get_format_template(media, self.settings) if isinstance(media, Track | Video): - result_dl, path_file = dl.item(media=media, file_template=file_template, download_delay=delay_track) + result_dl, path_file = dl.item( + media=media, file_template=file_template, download_delay=delay_track, quality=quality + ) elif isinstance(media, Album | Playlist | Mix): dl.items( media=media, file_template=file_template, video_download=self.settings.data.video_download, download_delay=self.settings.data.download_delay, + quality=quality, ) # Dummy values diff --git a/tidal_dl_ng/helper/gui.py b/tidal_dl_ng/helper/gui.py index b18c24e..610be80 100644 --- a/tidal_dl_ng/helper/gui.py +++ b/tidal_dl_ng/helper/gui.py @@ -1,6 +1,7 @@ from PySide6 import QtCore, QtWidgets from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video from tidalapi.artist import Artist +from tidalapi.media import Quality def get_table_data( @@ -11,6 +12,12 @@ def get_table_data( return result +def get_table_text(item: QtWidgets.QTreeWidgetItem, column: int) -> str: + result: str = item.text(column) + + return result + + def get_results_media_item(item: QtWidgets.QTreeWidgetItem) -> Track | Video | Album | Artist | Playlist | Mix: result: Track | Video | Album | Artist | Playlist | Mix = get_table_data(item, 1) @@ -31,6 +38,14 @@ def get_queue_download_media( return result +def get_queue_download_quality( + item: QtWidgets.QTreeWidgetItem, +) -> Quality: + result: Quality = get_table_text(item, 4) + + return result + + def set_table_data( item: QtWidgets.QTreeWidgetItem, data: Track | Video | Album | Artist | Mix | Playlist | UserPlaylist, column: int ): diff --git a/tidal_dl_ng/helper/tidal.py b/tidal_dl_ng/helper/tidal.py index 052f721..dbc31aa 100644 --- a/tidal_dl_ng/helper/tidal.py +++ b/tidal_dl_ng/helper/tidal.py @@ -154,13 +154,13 @@ def instantiate_media( return media -def quality_audio_highest(media: Track | Album) -> str: - quality: str +def quality_audio_highest(media: Track | Album) -> Quality: + quality: Quality if MediaMetadataTags.hires_lossless in media.media_metadata_tags: quality = Quality.hi_res_lossless elif MediaMetadataTags.mqa in media.media_metadata_tags: - quality = f"{Quality.hi_res}" + quality = Quality.hi_res elif MediaMetadataTags.lossless in media.media_metadata_tags: quality = Quality.high_lossless else: diff --git a/tidal_dl_ng/model/gui_data.py b/tidal_dl_ng/model/gui_data.py index 56b5e2b..8c7c30e 100644 --- a/tidal_dl_ng/model/gui_data.py +++ b/tidal_dl_ng/model/gui_data.py @@ -1,5 +1,7 @@ from dataclasses import dataclass +from tidalapi.media import Quality + try: from PySide6 import QtCore @@ -39,5 +41,5 @@ class QueueDownloadItem: status: str name: str type_media: str - quality: str + quality: Quality obj: object