✨ PKCE / OAuth login will be chosen on the fly, depending on the desired audio quality of the track to speed up non HIRES_LOSSLESS downloads. Quality for tracks in download queue can be chosen individually. Fixes #73

This commit is contained in:
Robert Honz
2024-04-21 15:58:31 +02:00
parent 1224e42689
commit f9862e0646
6 changed files with 73 additions and 16 deletions
+8 -2
View File
@@ -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
+27 -4
View File
@@ -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.
+17 -6
View File
@@ -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
+15
View File
@@ -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
):
+3 -3
View File
@@ -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:
+3 -1
View File
@@ -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