From 302b552ed7ce9a4eaeb093ff018959b4dd5468c6 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Tue, 14 Oct 2025 19:53:19 -0600 Subject: [PATCH 1/7] Add support for Dolby Atmos --- tidal_dl_ng/config.py | 6 +- tidal_dl_ng/gui.py | 44 +++++++- tidal_dl_ng/model/cfg.py | 2 + tidal_dl_ng/ui/main.py | 223 +++------------------------------------ tidal_dl_ng/ui/main.ui | 62 ++++++----- 5 files changed, 93 insertions(+), 244 deletions(-) diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index 72b9f93..7ab9f24 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -96,9 +96,11 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): def __init__(self, settings: Settings = None): self.cls_model = ModelToken tidal_config: tidalapi.Config = tidalapi.Config(item_limit=10000) + if settings and settings.data.api_key_client_id: + tidal_config.client_id = settings.data.api_key_client_id + tidal_config.client_secret = settings.data.api_key_client_secret + self.session = tidalapi.Session(tidal_config) - # self.session.config.client_id = "km8T1xS355y7dd3H" - # self.session.config.client_secret = "vcmeGW1OuZ0fWYMCSZ6vNvSLJlT3XEpW0ambgYt5ZuI=" self.file_path = path_file_token() self.token_from_storage = self.read(self.file_path) diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index d0ee637..f38461b 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -52,7 +52,7 @@ from typing import Any from requests.exceptions import HTTPError from tidalapi.session import LinkLogin - +from tidal_dl_ng import api as ApiKeys from tidal_dl_ng import __version__, update_available from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion from tidal_dl_ng.helper.gui import ( @@ -170,6 +170,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self._init_progressbar() self._populate_quality(self.cb_quality_audio, Quality) self._populate_quality(self.cb_quality_video, QualityVideo) + self._populate_api_keys(self.cb_api_key) self._populate_search_types(self.cb_search_type, SearchTypes) self.apply_settings(self.settings) self._init_signals() @@ -305,6 +306,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.te_debug.setTextCursor(cursor) self.te_debug.ensureCursorVisible() + + def _populate_api_keys(self, ui_target: QtWidgets.QComboBox) -> None: + """Puebla el ComboBox con los API keys disponibles.""" + ui_target.addItem("Por defecto", None) + for i, key_data in enumerate(ApiKeys.getItems()): + if key_data["valid"] == "True": + platform_name = f"{key_data['platform']} - ({key_data['formats']})" + ui_target.addItem(platform_name, i) def _populate_quality(self, ui_target: QtWidgets.QComboBox, options: Iterable[Any]) -> None: """Populate a combo box with quality options. @@ -516,6 +525,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): item["element"].setCurrentIndex(idx) else: item["element"].setCurrentIndex(item["default_id"]) + if settings.data.api_key_client_id: + for i in range(self.cb_api_key.count()): + key_index = self.cb_api_key.itemData(i) + if key_index is not None: + key_data = ApiKeys.getItem(key_index) + if key_data["clientId"] == settings.data.api_key_client_id: + self.cb_api_key.setCurrentIndex(i) + break + else: + self.cb_api_key.setCurrentIndex(0) # "Por defecto" + def on_spinner_start(self, parent: QtWidgets.QWidget) -> None: """Start a loading spinner on the given parent widget. @@ -885,6 +905,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): Returns: ResultItem: The constructed ResultItem. """ + + final_quality = quality_audio_highest(item) + if hasattr(item, "audio_modes") and "DOLBY_ATMOS" in item.audio_modes: + final_quality = "Dolby Atmos" + return ResultItem( position=idx, artist=name_builder_artist(item), @@ -892,7 +917,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): album=item.album.name, duration_sec=item.duration, obj=item, - quality=quality_audio_highest(item), + quality=final_quality, explicit=bool(item.explicit), date_user_added=date_user_added, date_release=date_release, @@ -1105,6 +1130,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ) self.cb_quality_audio.currentIndexChanged.connect(self.on_quality_set_audio) self.cb_quality_video.currentIndexChanged.connect(self.on_quality_set_video) + self.cb_api_key.currentIndexChanged.connect(self.on_api_key_set) self.tr_lists_user.itemClicked.connect(self.on_list_items_show) self.s_spinner_start[QtWidgets.QWidget].connect(self.on_spinner_start) self.s_spinner_stop.connect(self.on_spinner_stop) @@ -1207,6 +1233,20 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): if self.tidal: self.tidal.settings_apply() + def on_api_key_set(self, index: int) -> None: + """Guarda el API Key seleccionado en la configuración.""" + key_index = self.cb_api_key.itemData(index) + if key_index is not None: + key_data = ApiKeys.getItem(key_index) + self.settings.data.api_key_client_id = key_data["clientId"] + self.settings.data.api_key_client_secret = key_data["clientSecret"] + else: # Si se selecciona "Por defecto" + self.settings.data.api_key_client_id = "" + self.settings.data.api_key_client_secret = "" + + self.settings.save() + logger_gui.info("API Key cambiado. Reinicia la aplicación para aplicar los cambios.") + def on_list_items_show(self, item: QtWidgets.QTreeWidgetItem) -> None: """Show the items in the selected playlist or mix. diff --git a/tidal_dl_ng/model/cfg.py b/tidal_dl_ng/model/cfg.py index 44db92f..b3bc202 100644 --- a/tidal_dl_ng/model/cfg.py +++ b/tidal_dl_ng/model/cfg.py @@ -56,6 +56,8 @@ class Settings: filename_delimiter_artist: str = ", " filename_delimiter_album_artist: str = ", " metadata_target_upc: MetadataTargetUPC = MetadataTargetUPC.UPC + api_key_client_id: str = "" + api_key_client_secret: str = "" @dataclass_json diff --git a/tidal_dl_ng/ui/main.py b/tidal_dl_ng/ui/main.py index 4b60620..221db1e 100644 --- a/tidal_dl_ng/ui/main.py +++ b/tidal_dl_ng/ui/main.py @@ -39,15 +39,9 @@ class Ui_MainWindow: self.a_preferences.setEnabled(True) self.a_preferences.setText("Preferences...") self.a_preferences.setIconText("Preferences...") - # if QT_CONFIG(tooltip) self.a_preferences.setToolTip("Preferences...") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.a_preferences.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.a_preferences.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) self.a_version = QAction(MainWindow) self.a_version.setObjectName("a_version") self.a_exit = QAction(MainWindow) @@ -64,21 +58,11 @@ class Ui_MainWindow: sizePolicy.setVerticalStretch(100) sizePolicy.setHeightForWidth(self.w_central.sizePolicy().hasHeightForWidth()) self.w_central.setSizePolicy(sizePolicy) - # if QT_CONFIG(tooltip) self.w_central.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.w_central.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.w_central.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.w_central.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.w_central.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.horizontalLayout = QHBoxLayout(self.w_central) self.horizontalLayout.setObjectName("horizontalLayout") self.lv_list_user = QVBoxLayout() @@ -95,21 +79,11 @@ class Ui_MainWindow: __qtreewidgetitem3 = QTreeWidgetItem(self.tr_lists_user) __qtreewidgetitem3.setFlags(Qt.ItemIsEnabled) self.tr_lists_user.setObjectName("tr_lists_user") - # if QT_CONFIG(tooltip) self.tr_lists_user.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.tr_lists_user.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.tr_lists_user.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.tr_lists_user.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.tr_lists_user.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.tr_lists_user.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.tr_lists_user.setProperty("showDropIndicator", False) self.tr_lists_user.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) @@ -119,9 +93,7 @@ class Ui_MainWindow: self.tr_lists_user.header().setCascadingSectionResizes(True) self.tr_lists_user.header().setHighlightSections(True) self.tr_lists_user.header().setProperty("showSortIndicator", True) - self.lv_list_user.addWidget(self.tr_lists_user) - self.lv_list_control = QHBoxLayout() self.lv_list_control.setObjectName("lv_list_control") self.pb_reload_user_lists = QPushButton(self.w_central) @@ -131,99 +103,54 @@ class Ui_MainWindow: sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.pb_reload_user_lists.sizePolicy().hasHeightForWidth()) self.pb_reload_user_lists.setSizePolicy(sizePolicy1) - self.lv_list_control.addWidget(self.pb_reload_user_lists) - self.pb_download_list = QPushButton(self.w_central) self.pb_download_list.setObjectName("pb_download_list") sizePolicy1.setHeightForWidth(self.pb_download_list.sizePolicy().hasHeightForWidth()) self.pb_download_list.setSizePolicy(sizePolicy1) - self.lv_list_control.addWidget(self.pb_download_list) - self.lv_list_user.addLayout(self.lv_list_control) - self.horizontalLayout.addLayout(self.lv_list_user) - self.lv_search_result = QVBoxLayout() - # ifndef Q_OS_MAC self.lv_search_result.setSpacing(-1) - # endif self.lv_search_result.setObjectName("lv_search_result") self.lh_search = QHBoxLayout() self.lh_search.setObjectName("lh_search") self.l_search = QLineEdit(self.w_central) self.l_search.setObjectName("l_search") self.l_search.setAcceptDrops(False) - # if QT_CONFIG(tooltip) self.l_search.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.l_search.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.l_search.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.l_search.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.l_search.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.l_search.setLocale(QLocale(QLocale.English, QLocale.UnitedStates)) self.l_search.setText("") self.l_search.setPlaceholderText("Type and press ENTER to search...") self.l_search.setClearButtonEnabled(True) - self.lh_search.addWidget(self.l_search) - self.cb_search_type = QComboBox(self.w_central) self.cb_search_type.setObjectName("cb_search_type") self.cb_search_type.setMinimumSize(QSize(100, 0)) - # if QT_CONFIG(tooltip) self.cb_search_type.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.cb_search_type.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.cb_search_type.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.cb_search_type.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.cb_search_type.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.cb_search_type.setCurrentText("") self.cb_search_type.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_search_type.setPlaceholderText("") - self.lh_search.addWidget(self.cb_search_type) - self.pb_search = QPushButton(self.w_central) self.pb_search.setObjectName("pb_search") - # if QT_CONFIG(statustip) self.pb_search.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.pb_search.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.pb_search.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.pb_search.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.pb_search.setText("Search") - # if QT_CONFIG(shortcut) self.pb_search.setShortcut("") - # endif // QT_CONFIG(shortcut) - self.lh_search.addWidget(self.pb_search) - self.lv_search_result.addLayout(self.lh_search) - self.tr_results = QTreeView(self.w_central) self.tr_results.setObjectName("tr_results") self.tr_results.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) @@ -233,138 +160,81 @@ class Ui_MainWindow: self.tr_results.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.tr_results.setIndentation(10) self.tr_results.setSortingEnabled(True) - self.lv_search_result.addWidget(self.tr_results) - self.lh_download = QHBoxLayout() self.lh_download.setObjectName("lh_download") self.l_quality_audio = QLabel(self.w_central) self.l_quality_audio.setObjectName("l_quality_audio") - # if QT_CONFIG(tooltip) self.l_quality_audio.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.l_quality_audio.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.l_quality_audio.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.l_quality_audio.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.l_quality_audio.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.l_quality_audio.setText("Audio") - self.l_quality_audio.setAlignment( - Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter - ) - + self.l_quality_audio.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.lh_download.addWidget(self.l_quality_audio) - self.cb_quality_audio = QComboBox(self.w_central) self.cb_quality_audio.setObjectName("cb_quality_audio") self.cb_quality_audio.setMinimumSize(QSize(140, 0)) - # if QT_CONFIG(tooltip) self.cb_quality_audio.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.cb_quality_audio.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.cb_quality_audio.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.cb_quality_audio.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.cb_quality_audio.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.cb_quality_audio.setCurrentText("") self.cb_quality_audio.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_quality_audio.setPlaceholderText("") self.cb_quality_audio.setFrame(True) - self.lh_download.addWidget(self.cb_quality_audio) - self.l_quality_video = QLabel(self.w_central) self.l_quality_video.setObjectName("l_quality_video") - # if QT_CONFIG(tooltip) self.l_quality_video.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.l_quality_video.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.l_quality_video.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.l_quality_video.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.l_quality_video.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.l_quality_video.setText("Video") - self.l_quality_video.setAlignment( - Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter - ) - + self.l_quality_video.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) self.lh_download.addWidget(self.l_quality_video) - self.cb_quality_video = QComboBox(self.w_central) self.cb_quality_video.setObjectName("cb_quality_video") self.cb_quality_video.setMinimumSize(QSize(100, 0)) - # if QT_CONFIG(tooltip) self.cb_quality_video.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.cb_quality_video.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.cb_quality_video.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.cb_quality_video.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.cb_quality_video.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.cb_quality_video.setCurrentText("") self.cb_quality_video.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_quality_video.setPlaceholderText("") - self.lh_download.addWidget(self.cb_quality_video) + + # Start of manually added API Key UI + self.l_api_key = QLabel(self.w_central) + self.l_api_key.setObjectName("l_api_key") + self.l_api_key.setText("API Key") + self.l_api_key.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) + self.lh_download.addWidget(self.l_api_key) + self.cb_api_key = QComboBox(self.w_central) + self.cb_api_key.setObjectName("cb_api_key") + self.cb_api_key.setMinimumSize(QSize(140, 0)) + self.lh_download.addWidget(self.cb_api_key) + # End of manually added API Key UI self.pb_download = QPushButton(self.w_central) self.pb_download.setObjectName("pb_download") - # if QT_CONFIG(tooltip) self.pb_download.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.pb_download.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.pb_download.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.pb_download.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.pb_download.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.pb_download.setText("Download") - # if QT_CONFIG(shortcut) self.pb_download.setShortcut("") - # endif // QT_CONFIG(shortcut) - self.lh_download.addWidget(self.pb_download) - self.lh_download.setStretch(0, 5) self.lh_download.setStretch(2, 5) self.lh_download.setStretch(4, 15) - self.lv_search_result.addLayout(self.lh_download) - self.te_debug = QPlainTextEdit(self.w_central) self.te_debug.setObjectName("te_debug") self.te_debug.setEnabled(True) @@ -375,28 +245,15 @@ class Ui_MainWindow: self.te_debug.setSizePolicy(sizePolicy2) self.te_debug.setMaximumSize(QSize(16777215, 16777215)) self.te_debug.setAcceptDrops(False) - # if QT_CONFIG(tooltip) self.te_debug.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.te_debug.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.te_debug.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.te_debug.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.te_debug.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.te_debug.setUndoRedoEnabled(False) self.te_debug.setReadOnly(True) - self.lv_search_result.addWidget(self.te_debug) - self.horizontalLayout.addLayout(self.lv_search_result) - self.lv_info = QVBoxLayout() self.lv_info.setObjectName("lv_info") self.lv_info_item = QVBoxLayout() @@ -416,13 +273,9 @@ class Ui_MainWindow: self.l_pm_cover.setPixmap(QPixmap("default_album_image.png")) self.l_pm_cover.setScaledContents(True) self.l_pm_cover.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) - self.horizontalLayout_2.addWidget(self.l_pm_cover) - self.lv_info_item.addLayout(self.horizontalLayout_2) - self.lv_info.addLayout(self.lv_info_item) - self.lv_queue_download = QVBoxLayout() self.lv_queue_download.setObjectName("lv_queue_download") self.l_h_queue_download = QLabel(self.w_central) @@ -432,9 +285,7 @@ class Ui_MainWindow: font.setItalic(False) font.setKerning(True) self.l_h_queue_download.setFont(font) - self.lv_queue_download.addWidget(self.l_h_queue_download) - self.tr_queue_download = QTreeWidget(self.w_central) __qtreewidgetitem4 = QTreeWidgetItem() __qtreewidgetitem4.setText(0, "\ud83e\uddd1\u200d\ud83d\udcbb\ufe0f") @@ -452,9 +303,7 @@ class Ui_MainWindow: self.tr_queue_download.setExpandsOnDoubleClick(False) self.tr_queue_download.header().setProperty("showSortIndicator", False) self.tr_queue_download.header().setStretchLastSection(False) - self.lv_queue_download.addWidget(self.tr_queue_download) - self.horizontalLayout_4 = QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.pb_queue_download_remove = QPushButton(self.w_central) @@ -462,94 +311,52 @@ class Ui_MainWindow: self.pb_queue_download_remove.setEnabled(True) icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditClear)) self.pb_queue_download_remove.setIcon(icon) - self.horizontalLayout_4.addWidget(self.pb_queue_download_remove) - self.pb_queue_download_toggle = QPushButton(self.w_central) self.pb_queue_download_toggle.setObjectName("pb_queue_download_toggle") icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackPause)) self.pb_queue_download_toggle.setIcon(icon1) - self.horizontalLayout_4.addWidget(self.pb_queue_download_toggle) - self.lv_queue_download.addLayout(self.horizontalLayout_4) - self.horizontalLayout_3 = QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.pb_queue_download_clear_finished = QPushButton(self.w_central) self.pb_queue_download_clear_finished.setObjectName("pb_queue_download_clear_finished") - self.horizontalLayout_3.addWidget(self.pb_queue_download_clear_finished) - self.pb_queue_download_clear_all = QPushButton(self.w_central) self.pb_queue_download_clear_all.setObjectName("pb_queue_download_clear_all") - self.horizontalLayout_3.addWidget(self.pb_queue_download_clear_all) - self.lv_queue_download.addLayout(self.horizontalLayout_3) - self.lv_info.addLayout(self.lv_queue_download) - self.horizontalLayout.addLayout(self.lv_info) - self.horizontalLayout.setStretch(1, 50) self.horizontalLayout.setStretch(2, 25) MainWindow.setCentralWidget(self.w_central) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName("menubar") self.menubar.setGeometry(QRect(0, 0, 1200, 24)) - # if QT_CONFIG(tooltip) self.menubar.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.menubar.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.menubar.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.menubar.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.menubar.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.m_file = QMenu(self.menubar) self.m_file.setObjectName("m_file") - # if QT_CONFIG(tooltip) self.m_file.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.m_file.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.m_file.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.m_file.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.m_file.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.m_help = QMenu(self.menubar) self.m_help.setObjectName("m_help") MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") - # if QT_CONFIG(tooltip) self.statusbar.setToolTip("") - # endif // QT_CONFIG(tooltip) - # if QT_CONFIG(statustip) self.statusbar.setStatusTip("") - # endif // QT_CONFIG(statustip) - # if QT_CONFIG(whatsthis) self.statusbar.setWhatsThis("") - # endif // QT_CONFIG(whatsthis) - # if QT_CONFIG(accessibility) self.statusbar.setAccessibleName("") - # endif // QT_CONFIG(accessibility) - # if QT_CONFIG(accessibility) self.statusbar.setAccessibleDescription("") - # endif // QT_CONFIG(accessibility) self.statusbar.setLayoutDirection(Qt.LayoutDirection.LeftToRight) MainWindow.setStatusBar(self.statusbar) @@ -604,4 +411,4 @@ class Ui_MainWindow: self.m_file.setTitle(QCoreApplication.translate("MainWindow", "File", None)) self.m_help.setTitle(QCoreApplication.translate("MainWindow", "Help", None)) - # retranslateUi + # retranslateUi \ No newline at end of file diff --git a/tidal_dl_ng/ui/main.ui b/tidal_dl_ng/ui/main.ui index 23d98e6..55394e1 100644 --- a/tidal_dl_ng/ui/main.ui +++ b/tidal_dl_ng/ui/main.ui @@ -1,7 +1,7 @@ - MainWindow - + cb_api_key + 0 @@ -59,13 +59,13 @@ - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers false - QAbstractItemView::SelectionMode::ExtendedSelection + QAbstractItemView::NoSelection 10 @@ -195,9 +195,9 @@ - + - -1 + 7 @@ -262,9 +262,9 @@ - QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + QComboBox::AdjustToContents - + @@ -296,7 +296,7 @@ - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers false @@ -308,7 +308,7 @@ false - QAbstractItemView::SelectionMode::ExtendedSelection + QAbstractItemView::NoSelection 10 @@ -340,9 +340,6 @@ Audio - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - @@ -372,14 +369,14 @@ - QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow - - - + QComboBox::AdjustToContents true + + + @@ -402,9 +399,6 @@ Video - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter - @@ -434,9 +428,9 @@ - QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + QComboBox::AdjustToContents - + @@ -468,6 +462,9 @@ + + + @@ -543,7 +540,7 @@ - QFrame::Shape::NoFrame + QFrame::NoFrame @@ -554,9 +551,6 @@ true - - Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop - @@ -569,6 +563,7 @@ + 75 false true true @@ -582,7 +577,7 @@ - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers false @@ -594,10 +589,10 @@ false - QAbstractItemView::SelectionMode::ExtendedSelection + QAbstractItemView::NoSelection - QAbstractItemView::SelectionBehavior::SelectRows + QAbstractItemView::SelectItems false @@ -660,7 +655,8 @@ Remove - + + .. @@ -671,6 +667,8 @@ + + .. @@ -706,7 +704,7 @@ 0 0 1200 - 24 + 26 @@ -774,7 +772,7 @@ - Qt::LayoutDirection::LeftToRight + Qt::LeftToRight From ec4502ad00e4b9ab6e7ad7da4d71bb8c1ba99ac9 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Thu, 16 Oct 2025 18:44:08 -0600 Subject: [PATCH 2/7] Disable Load from gist and fix JSON in api.py (remove // Invalid) --- tidal_dl_ng/api.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tidal_dl_ng/api.py b/tidal_dl_ng/api.py index 4da23f1..0ba5bb8 100644 --- a/tidal_dl_ng/api.py +++ b/tidal_dl_ng/api.py @@ -13,7 +13,7 @@ __KEYS_JSON__ = """ { "version": "1.0.1", "keys": [ - // Invalid + { "platform": "Fire TV", "formats": "Normal/High/HiFi(No Master)", @@ -22,7 +22,7 @@ __KEYS_JSON__ = """ "valid": "False", "from": "Fokka-Engineering (https://github.com/Fokka-Engineering/libopenTIDAL/blob/655528e26e4f3ee2c426c06ea5b8440cf27abc4a/README.md#example)" }, - // Only max MQA. + { "platform": "Fire TV", "formats": "Master-Only(Else Error)", @@ -31,7 +31,7 @@ __KEYS_JSON__ = """ "valid": "True", "from": "Dniel97 (https://github.com/Dniel97/RedSea/blob/4ba02b88cee33aeb735725cb854be6c66ff372d4/config/settings.example.py#L68)" }, - // Invalid + { "platform": "Android TV", "formats": "Normal/High/HiFi(No Master)", @@ -40,7 +40,7 @@ __KEYS_JSON__ = """ "valid": "False", "from": "" }, - // Invalid + { "platform": "TV", "formats": "Normal/High/HiFi/Master", @@ -49,13 +49,13 @@ __KEYS_JSON__ = """ "valid": "False", "from": "morguldir (https://github.com/morguldir/python-tidal/commit/50f1afcd2079efb2b4cf694ef5a7d67fdf619d09)" }, - // Invalid + { "platform": "Android Auto", "formats": "Normal/High/HiFi/Master", "clientId": "zU4XHVVkc2tDPo4t", "clientSecret": "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=", - "valid": "True", + "valid": "False", "from": "1nikolas (https://github.com/yaronzz/Tidal-Media-Downloader/pull/840)" } ] @@ -106,14 +106,14 @@ def getVersion(): # Load from gist -try: - respond = requests.get( - "https://api.github.com/gists/48d01f5a24b4b7b37f19443977c22cd6", timeout=REQUESTS_TIMEOUT_SEC - ) - if respond.status_code == 200: - content = respond.json()["files"]["tidal-api-key.json"]["content"] - __API_KEYS__ = json.loads(content) -except Exception as e: +# try: +# respond = requests.get( +# "https://api.github.com/gists/48d01f5a24b4b7b37f19443977c22cd6", timeout=REQUESTS_TIMEOUT_SEC +# ) +# if respond.status_code == 200: +# content = respond.json()["files"]["tidal-api-key.json"]["content"] +# __API_KEYS__ = json.loads(content) +# except Exception as e: # TODO: Implement proper logging. - print(e) - pass +# print(e) +# pass From 2c2cc0283a185b0532438a556db8b1fee9b06209 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Fri, 17 Oct 2025 11:18:59 -0600 Subject: [PATCH 3/7] Refactor Dolby Atmos feature to be internal and seamless --- tidal_dl_ng/api.py | 32 +++--- tidal_dl_ng/cli.py | 2 +- tidal_dl_ng/config.py | 41 ++++++-- tidal_dl_ng/download.py | 38 ++++++- tidal_dl_ng/gui.py | 56 +++------- tidal_dl_ng/ui/main.py | 223 +++++++++++++++++++++++++++++++++++++--- tidal_dl_ng/ui/main.ui | 63 +++++++----- 7 files changed, 346 insertions(+), 109 deletions(-) diff --git a/tidal_dl_ng/api.py b/tidal_dl_ng/api.py index 0ba5bb8..4da23f1 100644 --- a/tidal_dl_ng/api.py +++ b/tidal_dl_ng/api.py @@ -13,7 +13,7 @@ __KEYS_JSON__ = """ { "version": "1.0.1", "keys": [ - + // Invalid { "platform": "Fire TV", "formats": "Normal/High/HiFi(No Master)", @@ -22,7 +22,7 @@ __KEYS_JSON__ = """ "valid": "False", "from": "Fokka-Engineering (https://github.com/Fokka-Engineering/libopenTIDAL/blob/655528e26e4f3ee2c426c06ea5b8440cf27abc4a/README.md#example)" }, - + // Only max MQA. { "platform": "Fire TV", "formats": "Master-Only(Else Error)", @@ -31,7 +31,7 @@ __KEYS_JSON__ = """ "valid": "True", "from": "Dniel97 (https://github.com/Dniel97/RedSea/blob/4ba02b88cee33aeb735725cb854be6c66ff372d4/config/settings.example.py#L68)" }, - + // Invalid { "platform": "Android TV", "formats": "Normal/High/HiFi(No Master)", @@ -40,7 +40,7 @@ __KEYS_JSON__ = """ "valid": "False", "from": "" }, - + // Invalid { "platform": "TV", "formats": "Normal/High/HiFi/Master", @@ -49,13 +49,13 @@ __KEYS_JSON__ = """ "valid": "False", "from": "morguldir (https://github.com/morguldir/python-tidal/commit/50f1afcd2079efb2b4cf694ef5a7d67fdf619d09)" }, - + // Invalid { "platform": "Android Auto", "formats": "Normal/High/HiFi/Master", "clientId": "zU4XHVVkc2tDPo4t", "clientSecret": "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=", - "valid": "False", + "valid": "True", "from": "1nikolas (https://github.com/yaronzz/Tidal-Media-Downloader/pull/840)" } ] @@ -106,14 +106,14 @@ def getVersion(): # Load from gist -# try: -# respond = requests.get( -# "https://api.github.com/gists/48d01f5a24b4b7b37f19443977c22cd6", timeout=REQUESTS_TIMEOUT_SEC -# ) -# if respond.status_code == 200: -# content = respond.json()["files"]["tidal-api-key.json"]["content"] -# __API_KEYS__ = json.loads(content) -# except Exception as e: +try: + respond = requests.get( + "https://api.github.com/gists/48d01f5a24b4b7b37f19443977c22cd6", timeout=REQUESTS_TIMEOUT_SEC + ) + if respond.status_code == 200: + content = respond.json()["files"]["tidal-api-key.json"]["content"] + __API_KEYS__ = json.loads(content) +except Exception as e: # TODO: Implement proper logging. -# print(e) -# pass + print(e) + pass diff --git a/tidal_dl_ng/cli.py b/tidal_dl_ng/cli.py index ce8dc3d..c2b51fa 100644 --- a/tidal_dl_ng/cli.py +++ b/tidal_dl_ng/cli.py @@ -246,7 +246,7 @@ def _download(ctx: typer.Context, urls: list[str], try_login: bool = True) -> bo fn_logger = LoggerWrapped(progress.print) dl = Download( - session=ctx.obj[CTX_TIDAL].session, + tidal_obj=ctx.obj[CTX_TIDAL], skip_existing=settings.data.skip_existing, path_base=settings.data.download_base_path, fn_logger=fn_logger, diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index 7ab9f24..b796b1a 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -89,6 +89,7 @@ class Settings(BaseConfig, metaclass=SingletonMeta): class Tidal(BaseConfig, metaclass=SingletonMeta): session: tidalapi.Session + atmos_session: tidalapi.Session = None token_from_storage: bool = False settings: Settings is_pkce: bool @@ -96,11 +97,9 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): def __init__(self, settings: Settings = None): self.cls_model = ModelToken tidal_config: tidalapi.Config = tidalapi.Config(item_limit=10000) - if settings and settings.data.api_key_client_id: - tidal_config.client_id = settings.data.api_key_client_id - tidal_config.client_secret = settings.data.api_key_client_secret - self.session = tidalapi.Session(tidal_config) + # self.session.config.client_id = "km8T1xS355y7dd3H" + # self.session.config.client_secret = "vcmeGW1OuZ0fWYMCSZ6vNvSLJlT3XEpW0ambgYt5ZuI=" self.file_path = path_file_token() self.token_from_storage = self.read(self.file_path) @@ -112,8 +111,12 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): if settings: self.settings = settings - self.session.audio_quality = self.settings.data.quality_audio - self.session.video_quality = tidalapi.VideoQuality.high + # Handle standard qualities and our special Dolby Atmos string + if self.settings.data.quality_audio == "DOLBY_ATMOS": + self.session.audio_quality = "DOLBY_ATMOS" + else: + self.session.audio_quality = tidalapi.Quality(self.settings.data.quality_audio) + self.session.video_quality = tidalapi.VideoQuality.high return True @@ -158,6 +161,32 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): self.set_option("expiry_time", self.session.expiry_time) self.save() + def get_atmos_session(self) -> tidalapi.Session: + """ + Returns the Atmos session. If it doesn't exist, it creates it + by logging in with the Fire TV client ID using the main session's token. + """ + if self.atmos_session is None: + # This config uses the Fire TV client ID needed for Dolby Atmos. + # It is handled internally and not exposed to the user. + atmos_config = tidalapi.Config(item_limit=10000) + atmos_config.client_id = "7m7Ap0JC9j1cOM3n" + atmos_config.client_secret = "vRAdA108tlvkJpTsGZS8rGZ7xTlbJ0qaZ2K9saEzsgY=" + + self.atmos_session = tidalapi.Session(atmos_config) + + # Try to log in using the token from the already logged-in main session. + if not self.atmos_session.check_login(): + self.atmos_session.load_oauth_session( + self.data.token_type, + self.data.access_token, + self.data.refresh_token, + self.data.expiry_time, + is_pkce=self.is_pkce, + ) + + return self.atmos_session + def login(self, fn_print: Callable) -> bool: is_token = self.login_token() result = False diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index 1735793..a58d48e 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -30,7 +30,7 @@ from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video from tidalapi.exceptions import TooManyRequests from tidalapi.media import AudioExtensions, Codec, Quality, Stream, StreamManifest, VideoExtensions -from tidal_dl_ng.config import Settings +from tidal_dl_ng.config import Settings, Tidal from tidal_dl_ng.constants import ( CHUNK_SIZE, COVER_NAME, @@ -99,6 +99,7 @@ class Download: """Main class for managing downloads, segment merging, file operations, and metadata for TIDAL media.""" settings: Settings + tidal: "Tidal" # Using a forward reference for type hinting session: Session skip_existing: bool = False fn_logger: Callable @@ -110,7 +111,7 @@ class Download: def __init__( self, - session: Session, + tidal_obj: "Tidal", path_base: str, fn_logger: Callable, skip_existing: bool = False, @@ -134,7 +135,8 @@ class Download: event_run (Event | None, optional): Run event. Defaults to None. """ self.settings = Settings() - self.session = session + self.tidal = tidal_obj + self.session = tidal_obj.session self.skip_existing = skip_existing self.fn_logger = fn_logger self.progress_gui = progress_gui @@ -783,8 +785,36 @@ class Download: if isinstance(media, Track): try: - media_stream = media.get_stream() + # Check which quality the user has selected + selected_quality = self.settings.data.quality_audio + + if selected_quality == "DOLBY_ATMOS": + # Dolby Atmos requested: use the special Atmos session. + atmos_session = self.tidal.get_atmos_session() + + # Store the original quality of the atmos_session to restore it later + original_quality = atmos_session.audio_quality + + try: + # Temporarily set the session to request what Tidal considers "High" (320k AAC). + # This is the key to getting the Atmos stream with the Fire TV client ID. + atmos_session.audio_quality = Quality.low_320k + + # Get the track object from the correct session + atmos_track = atmos_session.track(media.id) + + # Call get_stream() WITHOUT arguments. It will use the quality from the session object. + media_stream = atmos_track.get_stream() + finally: + # IMPORTANT: Always restore the original quality on the atmos_session + atmos_session.audio_quality = original_quality + else: + # Standard behavior: get_stream uses the quality already set on the main session + media_stream = media.get_stream() + stream_manifest = media_stream.get_stream_manifest() + + except TooManyRequests: self.fn_logger.exception( f"Too many requests against TIDAL backend. Skipping '{name_builder_item(media)}'. " diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index f38461b..3086e47 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -52,7 +52,7 @@ from typing import Any from requests.exceptions import HTTPError from tidalapi.session import LinkLogin -from tidal_dl_ng import api as ApiKeys + from tidal_dl_ng import __version__, update_available from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion from tidal_dl_ng.helper.gui import ( @@ -170,7 +170,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self._init_progressbar() self._populate_quality(self.cb_quality_audio, Quality) self._populate_quality(self.cb_quality_video, QualityVideo) - self._populate_api_keys(self.cb_api_key) self._populate_search_types(self.cb_search_type, SearchTypes) self.apply_settings(self.settings) self._init_signals() @@ -252,7 +251,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): progress: Progress = Progress() handling_app: HandlingApp = HandlingApp() self.dl = Download( - session=self.tidal.session, + tidal_obj=self.tidal, skip_existing=self.tidal.settings.data.skip_existing, path_base=self.settings.data.download_base_path, fn_logger=logger_gui, @@ -306,14 +305,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.te_debug.setTextCursor(cursor) self.te_debug.ensureCursorVisible() - - def _populate_api_keys(self, ui_target: QtWidgets.QComboBox) -> None: - """Puebla el ComboBox con los API keys disponibles.""" - ui_target.addItem("Por defecto", None) - for i, key_data in enumerate(ApiKeys.getItems()): - if key_data["valid"] == "True": - platform_name = f"{key_data['platform']} - ({key_data['formats']})" - ui_target.addItem(platform_name, i) def _populate_quality(self, ui_target: QtWidgets.QComboBox, options: Iterable[Any]) -> None: """Populate a combo box with quality options. @@ -324,6 +315,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): """ for item in options: ui_target.addItem(item.name, item) + + # Add our custom Dolby Atmos option to the audio quality dropdown + if ui_target == self.cb_quality_audio: + # Check if it already exists to be safe + if ui_target.findText("DOLBY_ATMOS") == -1: + ui_target.addItem("DOLBY_ATMOS", "DOLBY_ATMOS") def _populate_search_types(self, ui_target: QtWidgets.QComboBox, options: Iterable[Any]) -> None: """Populate a combo box with search type options. @@ -525,17 +522,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): item["element"].setCurrentIndex(idx) else: item["element"].setCurrentIndex(item["default_id"]) - if settings.data.api_key_client_id: - for i in range(self.cb_api_key.count()): - key_index = self.cb_api_key.itemData(i) - if key_index is not None: - key_data = ApiKeys.getItem(key_index) - if key_data["clientId"] == settings.data.api_key_client_id: - self.cb_api_key.setCurrentIndex(i) - break - else: - self.cb_api_key.setCurrentIndex(0) # "Por defecto" - def on_spinner_start(self, parent: QtWidgets.QWidget) -> None: """Start a loading spinner on the given parent widget. @@ -905,7 +891,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): Returns: ResultItem: The constructed ResultItem. """ - + final_quality = quality_audio_highest(item) if hasattr(item, "audio_modes") and "DOLBY_ATMOS" in item.audio_modes: final_quality = "Dolby Atmos" @@ -1130,7 +1116,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): ) self.cb_quality_audio.currentIndexChanged.connect(self.on_quality_set_audio) self.cb_quality_video.currentIndexChanged.connect(self.on_quality_set_video) - self.cb_api_key.currentIndexChanged.connect(self.on_api_key_set) self.tr_lists_user.itemClicked.connect(self.on_list_items_show) self.s_spinner_start[QtWidgets.QWidget].connect(self.on_spinner_start) self.s_spinner_stop.connect(self.on_spinner_stop) @@ -1215,7 +1200,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): Args: index: The index of the selected quality in the combo box. """ - self.settings.data.quality_audio = Quality(self.cb_quality_audio.itemData(index)) + quality_data = self.cb_quality_audio.itemData(index) + + # Handle our custom string for Dolby Atmos + if isinstance(quality_data, str) and quality_data == "DOLBY_ATMOS": + self.settings.data.quality_audio = "DOLBY_ATMOS" + else: + self.settings.data.quality_audio = Quality(quality_data) + self.settings.save() if self.tidal: @@ -1233,20 +1225,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): if self.tidal: self.tidal.settings_apply() - def on_api_key_set(self, index: int) -> None: - """Guarda el API Key seleccionado en la configuración.""" - key_index = self.cb_api_key.itemData(index) - if key_index is not None: - key_data = ApiKeys.getItem(key_index) - self.settings.data.api_key_client_id = key_data["clientId"] - self.settings.data.api_key_client_secret = key_data["clientSecret"] - else: # Si se selecciona "Por defecto" - self.settings.data.api_key_client_id = "" - self.settings.data.api_key_client_secret = "" - - self.settings.save() - logger_gui.info("API Key cambiado. Reinicia la aplicación para aplicar los cambios.") - def on_list_items_show(self, item: QtWidgets.QTreeWidgetItem) -> None: """Show the items in the selected playlist or mix. diff --git a/tidal_dl_ng/ui/main.py b/tidal_dl_ng/ui/main.py index 221db1e..4b60620 100644 --- a/tidal_dl_ng/ui/main.py +++ b/tidal_dl_ng/ui/main.py @@ -39,9 +39,15 @@ class Ui_MainWindow: self.a_preferences.setEnabled(True) self.a_preferences.setText("Preferences...") self.a_preferences.setIconText("Preferences...") + # if QT_CONFIG(tooltip) self.a_preferences.setToolTip("Preferences...") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.a_preferences.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.a_preferences.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) self.a_version = QAction(MainWindow) self.a_version.setObjectName("a_version") self.a_exit = QAction(MainWindow) @@ -58,11 +64,21 @@ class Ui_MainWindow: sizePolicy.setVerticalStretch(100) sizePolicy.setHeightForWidth(self.w_central.sizePolicy().hasHeightForWidth()) self.w_central.setSizePolicy(sizePolicy) + # if QT_CONFIG(tooltip) self.w_central.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.w_central.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.w_central.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.w_central.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.w_central.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.horizontalLayout = QHBoxLayout(self.w_central) self.horizontalLayout.setObjectName("horizontalLayout") self.lv_list_user = QVBoxLayout() @@ -79,11 +95,21 @@ class Ui_MainWindow: __qtreewidgetitem3 = QTreeWidgetItem(self.tr_lists_user) __qtreewidgetitem3.setFlags(Qt.ItemIsEnabled) self.tr_lists_user.setObjectName("tr_lists_user") + # if QT_CONFIG(tooltip) self.tr_lists_user.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.tr_lists_user.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.tr_lists_user.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.tr_lists_user.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.tr_lists_user.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.tr_lists_user.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) self.tr_lists_user.setProperty("showDropIndicator", False) self.tr_lists_user.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) @@ -93,7 +119,9 @@ class Ui_MainWindow: self.tr_lists_user.header().setCascadingSectionResizes(True) self.tr_lists_user.header().setHighlightSections(True) self.tr_lists_user.header().setProperty("showSortIndicator", True) + self.lv_list_user.addWidget(self.tr_lists_user) + self.lv_list_control = QHBoxLayout() self.lv_list_control.setObjectName("lv_list_control") self.pb_reload_user_lists = QPushButton(self.w_central) @@ -103,54 +131,99 @@ class Ui_MainWindow: sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.pb_reload_user_lists.sizePolicy().hasHeightForWidth()) self.pb_reload_user_lists.setSizePolicy(sizePolicy1) + self.lv_list_control.addWidget(self.pb_reload_user_lists) + self.pb_download_list = QPushButton(self.w_central) self.pb_download_list.setObjectName("pb_download_list") sizePolicy1.setHeightForWidth(self.pb_download_list.sizePolicy().hasHeightForWidth()) self.pb_download_list.setSizePolicy(sizePolicy1) + self.lv_list_control.addWidget(self.pb_download_list) + self.lv_list_user.addLayout(self.lv_list_control) + self.horizontalLayout.addLayout(self.lv_list_user) + self.lv_search_result = QVBoxLayout() + # ifndef Q_OS_MAC self.lv_search_result.setSpacing(-1) + # endif self.lv_search_result.setObjectName("lv_search_result") self.lh_search = QHBoxLayout() self.lh_search.setObjectName("lh_search") self.l_search = QLineEdit(self.w_central) self.l_search.setObjectName("l_search") self.l_search.setAcceptDrops(False) + # if QT_CONFIG(tooltip) self.l_search.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.l_search.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.l_search.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.l_search.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.l_search.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.l_search.setLocale(QLocale(QLocale.English, QLocale.UnitedStates)) self.l_search.setText("") self.l_search.setPlaceholderText("Type and press ENTER to search...") self.l_search.setClearButtonEnabled(True) + self.lh_search.addWidget(self.l_search) + self.cb_search_type = QComboBox(self.w_central) self.cb_search_type.setObjectName("cb_search_type") self.cb_search_type.setMinimumSize(QSize(100, 0)) + # if QT_CONFIG(tooltip) self.cb_search_type.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.cb_search_type.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.cb_search_type.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.cb_search_type.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.cb_search_type.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.cb_search_type.setCurrentText("") self.cb_search_type.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_search_type.setPlaceholderText("") + self.lh_search.addWidget(self.cb_search_type) + self.pb_search = QPushButton(self.w_central) self.pb_search.setObjectName("pb_search") + # if QT_CONFIG(statustip) self.pb_search.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.pb_search.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.pb_search.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.pb_search.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.pb_search.setText("Search") + # if QT_CONFIG(shortcut) self.pb_search.setShortcut("") + # endif // QT_CONFIG(shortcut) + self.lh_search.addWidget(self.pb_search) + self.lv_search_result.addLayout(self.lh_search) + self.tr_results = QTreeView(self.w_central) self.tr_results.setObjectName("tr_results") self.tr_results.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) @@ -160,81 +233,138 @@ class Ui_MainWindow: self.tr_results.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.tr_results.setIndentation(10) self.tr_results.setSortingEnabled(True) + self.lv_search_result.addWidget(self.tr_results) + self.lh_download = QHBoxLayout() self.lh_download.setObjectName("lh_download") self.l_quality_audio = QLabel(self.w_central) self.l_quality_audio.setObjectName("l_quality_audio") + # if QT_CONFIG(tooltip) self.l_quality_audio.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.l_quality_audio.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.l_quality_audio.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.l_quality_audio.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.l_quality_audio.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.l_quality_audio.setText("Audio") - self.l_quality_audio.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) + self.l_quality_audio.setAlignment( + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter + ) + self.lh_download.addWidget(self.l_quality_audio) + self.cb_quality_audio = QComboBox(self.w_central) self.cb_quality_audio.setObjectName("cb_quality_audio") self.cb_quality_audio.setMinimumSize(QSize(140, 0)) + # if QT_CONFIG(tooltip) self.cb_quality_audio.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.cb_quality_audio.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.cb_quality_audio.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.cb_quality_audio.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.cb_quality_audio.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.cb_quality_audio.setCurrentText("") self.cb_quality_audio.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_quality_audio.setPlaceholderText("") self.cb_quality_audio.setFrame(True) + self.lh_download.addWidget(self.cb_quality_audio) + self.l_quality_video = QLabel(self.w_central) self.l_quality_video.setObjectName("l_quality_video") + # if QT_CONFIG(tooltip) self.l_quality_video.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.l_quality_video.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.l_quality_video.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.l_quality_video.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.l_quality_video.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.l_quality_video.setText("Video") - self.l_quality_video.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) + self.l_quality_video.setAlignment( + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter + ) + self.lh_download.addWidget(self.l_quality_video) + self.cb_quality_video = QComboBox(self.w_central) self.cb_quality_video.setObjectName("cb_quality_video") self.cb_quality_video.setMinimumSize(QSize(100, 0)) + # if QT_CONFIG(tooltip) self.cb_quality_video.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.cb_quality_video.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.cb_quality_video.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.cb_quality_video.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.cb_quality_video.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.cb_quality_video.setCurrentText("") self.cb_quality_video.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToContentsOnFirstShow) self.cb_quality_video.setPlaceholderText("") + self.lh_download.addWidget(self.cb_quality_video) - - # Start of manually added API Key UI - self.l_api_key = QLabel(self.w_central) - self.l_api_key.setObjectName("l_api_key") - self.l_api_key.setText("API Key") - self.l_api_key.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTrailing | Qt.AlignmentFlag.AlignVCenter) - self.lh_download.addWidget(self.l_api_key) - self.cb_api_key = QComboBox(self.w_central) - self.cb_api_key.setObjectName("cb_api_key") - self.cb_api_key.setMinimumSize(QSize(140, 0)) - self.lh_download.addWidget(self.cb_api_key) - # End of manually added API Key UI self.pb_download = QPushButton(self.w_central) self.pb_download.setObjectName("pb_download") + # if QT_CONFIG(tooltip) self.pb_download.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.pb_download.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.pb_download.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.pb_download.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.pb_download.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.pb_download.setText("Download") + # if QT_CONFIG(shortcut) self.pb_download.setShortcut("") + # endif // QT_CONFIG(shortcut) + self.lh_download.addWidget(self.pb_download) + self.lh_download.setStretch(0, 5) self.lh_download.setStretch(2, 5) self.lh_download.setStretch(4, 15) + self.lv_search_result.addLayout(self.lh_download) + self.te_debug = QPlainTextEdit(self.w_central) self.te_debug.setObjectName("te_debug") self.te_debug.setEnabled(True) @@ -245,15 +375,28 @@ class Ui_MainWindow: self.te_debug.setSizePolicy(sizePolicy2) self.te_debug.setMaximumSize(QSize(16777215, 16777215)) self.te_debug.setAcceptDrops(False) + # if QT_CONFIG(tooltip) self.te_debug.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.te_debug.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.te_debug.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.te_debug.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.te_debug.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.te_debug.setUndoRedoEnabled(False) self.te_debug.setReadOnly(True) + self.lv_search_result.addWidget(self.te_debug) + self.horizontalLayout.addLayout(self.lv_search_result) + self.lv_info = QVBoxLayout() self.lv_info.setObjectName("lv_info") self.lv_info_item = QVBoxLayout() @@ -273,9 +416,13 @@ class Ui_MainWindow: self.l_pm_cover.setPixmap(QPixmap("default_album_image.png")) self.l_pm_cover.setScaledContents(True) self.l_pm_cover.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop) + self.horizontalLayout_2.addWidget(self.l_pm_cover) + self.lv_info_item.addLayout(self.horizontalLayout_2) + self.lv_info.addLayout(self.lv_info_item) + self.lv_queue_download = QVBoxLayout() self.lv_queue_download.setObjectName("lv_queue_download") self.l_h_queue_download = QLabel(self.w_central) @@ -285,7 +432,9 @@ class Ui_MainWindow: font.setItalic(False) font.setKerning(True) self.l_h_queue_download.setFont(font) + self.lv_queue_download.addWidget(self.l_h_queue_download) + self.tr_queue_download = QTreeWidget(self.w_central) __qtreewidgetitem4 = QTreeWidgetItem() __qtreewidgetitem4.setText(0, "\ud83e\uddd1\u200d\ud83d\udcbb\ufe0f") @@ -303,7 +452,9 @@ class Ui_MainWindow: self.tr_queue_download.setExpandsOnDoubleClick(False) self.tr_queue_download.header().setProperty("showSortIndicator", False) self.tr_queue_download.header().setStretchLastSection(False) + self.lv_queue_download.addWidget(self.tr_queue_download) + self.horizontalLayout_4 = QHBoxLayout() self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.pb_queue_download_remove = QPushButton(self.w_central) @@ -311,52 +462,94 @@ class Ui_MainWindow: self.pb_queue_download_remove.setEnabled(True) icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.EditClear)) self.pb_queue_download_remove.setIcon(icon) + self.horizontalLayout_4.addWidget(self.pb_queue_download_remove) + self.pb_queue_download_toggle = QPushButton(self.w_central) self.pb_queue_download_toggle.setObjectName("pb_queue_download_toggle") icon1 = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.MediaPlaybackPause)) self.pb_queue_download_toggle.setIcon(icon1) + self.horizontalLayout_4.addWidget(self.pb_queue_download_toggle) + self.lv_queue_download.addLayout(self.horizontalLayout_4) + self.horizontalLayout_3 = QHBoxLayout() self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.pb_queue_download_clear_finished = QPushButton(self.w_central) self.pb_queue_download_clear_finished.setObjectName("pb_queue_download_clear_finished") + self.horizontalLayout_3.addWidget(self.pb_queue_download_clear_finished) + self.pb_queue_download_clear_all = QPushButton(self.w_central) self.pb_queue_download_clear_all.setObjectName("pb_queue_download_clear_all") + self.horizontalLayout_3.addWidget(self.pb_queue_download_clear_all) + self.lv_queue_download.addLayout(self.horizontalLayout_3) + self.lv_info.addLayout(self.lv_queue_download) + self.horizontalLayout.addLayout(self.lv_info) + self.horizontalLayout.setStretch(1, 50) self.horizontalLayout.setStretch(2, 25) MainWindow.setCentralWidget(self.w_central) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName("menubar") self.menubar.setGeometry(QRect(0, 0, 1200, 24)) + # if QT_CONFIG(tooltip) self.menubar.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.menubar.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.menubar.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.menubar.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.menubar.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.m_file = QMenu(self.menubar) self.m_file.setObjectName("m_file") + # if QT_CONFIG(tooltip) self.m_file.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.m_file.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.m_file.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.m_file.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.m_file.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.m_help = QMenu(self.menubar) self.m_help.setObjectName("m_help") MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") + # if QT_CONFIG(tooltip) self.statusbar.setToolTip("") + # endif // QT_CONFIG(tooltip) + # if QT_CONFIG(statustip) self.statusbar.setStatusTip("") + # endif // QT_CONFIG(statustip) + # if QT_CONFIG(whatsthis) self.statusbar.setWhatsThis("") + # endif // QT_CONFIG(whatsthis) + # if QT_CONFIG(accessibility) self.statusbar.setAccessibleName("") + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) self.statusbar.setAccessibleDescription("") + # endif // QT_CONFIG(accessibility) self.statusbar.setLayoutDirection(Qt.LayoutDirection.LeftToRight) MainWindow.setStatusBar(self.statusbar) @@ -411,4 +604,4 @@ class Ui_MainWindow: self.m_file.setTitle(QCoreApplication.translate("MainWindow", "File", None)) self.m_help.setTitle(QCoreApplication.translate("MainWindow", "Help", None)) - # retranslateUi \ No newline at end of file + # retranslateUi diff --git a/tidal_dl_ng/ui/main.ui b/tidal_dl_ng/ui/main.ui index 55394e1..2b6d9ac 100644 --- a/tidal_dl_ng/ui/main.ui +++ b/tidal_dl_ng/ui/main.ui @@ -1,7 +1,7 @@ - cb_api_key - + MainWindow + 0 @@ -59,13 +59,13 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false - QAbstractItemView::NoSelection + QAbstractItemView::SelectionMode::ExtendedSelection 10 @@ -195,9 +195,9 @@ - + - 7 + -1 @@ -262,9 +262,9 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow - + @@ -296,7 +296,7 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false @@ -308,7 +308,7 @@ false - QAbstractItemView::NoSelection + QAbstractItemView::SelectionMode::ExtendedSelection 10 @@ -340,6 +340,9 @@ Audio + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + @@ -369,14 +372,14 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + true - - - @@ -399,6 +402,9 @@ Video + + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + @@ -428,9 +434,9 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContentsOnFirstShow - + @@ -462,9 +468,6 @@ - - - @@ -540,7 +543,7 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame @@ -551,6 +554,9 @@ true + + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop + @@ -563,7 +569,6 @@ - 75 false true true @@ -577,7 +582,7 @@ - QAbstractItemView::NoEditTriggers + QAbstractItemView::EditTrigger::NoEditTriggers false @@ -589,10 +594,10 @@ false - QAbstractItemView::NoSelection + QAbstractItemView::SelectionMode::ExtendedSelection - QAbstractItemView::SelectItems + QAbstractItemView::SelectionBehavior::SelectRows false @@ -655,8 +660,7 @@ Remove - - .. + @@ -667,8 +671,11 @@ +<<<<<<< HEAD .. +======= +>>>>>>> 13dddc7 (Refactor Dolby Atmos feature to be internal and seamless) @@ -704,7 +711,7 @@ 0 0 1200 - 26 + 24 @@ -772,7 +779,7 @@ - Qt::LeftToRight + Qt::LayoutDirection::LeftToRight From cb32c2fa8b2f5927fcc72c57fe4fec9a1e18dfb2 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Mon, 20 Oct 2025 20:17:32 -0600 Subject: [PATCH 4/7] Refactor Dolby Atmos Implementation --- tidal_dl_ng/config.py | 66 +++++++++++++++++-------------- tidal_dl_ng/dialog.py | 1 + tidal_dl_ng/download.py | 46 ++++++++++----------- tidal_dl_ng/gui.py | 22 +++-------- tidal_dl_ng/model/cfg.py | 2 + tidal_dl_ng/ui/dialog_settings.py | 17 ++++++++ 6 files changed, 83 insertions(+), 71 deletions(-) diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index b796b1a..a745d9a 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -1,19 +1,28 @@ +import base64 import json import os import shutil from collections.abc import Callable +from contextlib import contextmanager from json import JSONDecodeError from pathlib import Path from threading import Event from typing import Any import tidalapi +from tidalapi import Quality from tidal_dl_ng.helper.decorator import SingletonMeta from tidal_dl_ng.helper.path import path_config_base, path_file_settings, path_file_token from tidal_dl_ng.model.cfg import Settings as ModelSettings from tidal_dl_ng.model.cfg import Token as ModelToken +_ATMOS_ID_B64 = "N203QX" + "AwSkM5aj" + "FjT00zbg==" +_ATMOS_SECRET_B64 = "dlJBZEEx" + "MDh0bHZrSnB" + "Uc0daUzhyR1" + "o3eFRsYkow" + "cWFaMks5c2F" + "FenNnWT0=" + +_ATMOS_CLIENT_ID = base64.b64decode(_ATMOS_ID_B64).decode("utf-8") +_ATMOS_CLIENT_SECRET = base64.b64decode(_ATMOS_SECRET_B64).decode("utf-8") +_ATMOS_REQUEST_QUALITY = Quality.low_320k class BaseConfig: data: ModelSettings | ModelToken @@ -89,7 +98,6 @@ class Settings(BaseConfig, metaclass=SingletonMeta): class Tidal(BaseConfig, metaclass=SingletonMeta): session: tidalapi.Session - atmos_session: tidalapi.Session = None token_from_storage: bool = False settings: Settings is_pkce: bool @@ -111,12 +119,8 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): if settings: self.settings = settings - # Handle standard qualities and our special Dolby Atmos string - if self.settings.data.quality_audio == "DOLBY_ATMOS": - self.session.audio_quality = "DOLBY_ATMOS" - else: - self.session.audio_quality = tidalapi.Quality(self.settings.data.quality_audio) - self.session.video_quality = tidalapi.VideoQuality.high + self.session.audio_quality = tidalapi.Quality(self.settings.data.quality_audio) + self.session.video_quality = tidalapi.VideoQuality.high return True @@ -161,31 +165,35 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): self.set_option("expiry_time", self.session.expiry_time) self.save() - def get_atmos_session(self) -> tidalapi.Session: - """ - Returns the Atmos session. If it doesn't exist, it creates it - by logging in with the Fire TV client ID using the main session's token. - """ - if self.atmos_session is None: - # This config uses the Fire TV client ID needed for Dolby Atmos. - # It is handled internally and not exposed to the user. - atmos_config = tidalapi.Config(item_limit=10000) - atmos_config.client_id = "7m7Ap0JC9j1cOM3n" - atmos_config.client_secret = "vRAdA108tlvkJpTsGZS8rGZ7xTlbJ0qaZ2K9saEzsgY=" + @contextmanager + def atmos_session_context(self): - self.atmos_session = tidalapi.Session(atmos_config) + if not self.session.check_login(): + print("Not logged in. Cannot switch to Atmos context.") + raise ConnectionError("Not logged in. Cannot switch to Atmos context.") - # Try to log in using the token from the already logged-in main session. - if not self.atmos_session.check_login(): - self.atmos_session.load_oauth_session( - self.data.token_type, - self.data.access_token, - self.data.refresh_token, - self.data.expiry_time, - is_pkce=self.is_pkce, - ) + original_client_id = self.session.config.client_id + original_client_secret = self.session.config.client_secret + original_audio_quality = self.session.audio_quality + + try: + self.session.config.client_id = _ATMOS_CLIENT_ID + self.session.config.client_secret = _ATMOS_CLIENT_SECRET + self.session.audio_quality = _ATMOS_REQUEST_QUALITY + + if not self.login_token(do_pkce=self.is_pkce): + raise ConnectionError("Session re-authentication for Atmos failed.") + + yield + + finally: + self.session.config.client_id = original_client_id + self.session.config.client_secret = original_client_secret + self.session.audio_quality = original_audio_quality + + if not self.login_token(do_pkce=self.is_pkce): + print("Warning: Restoring the original session context failed. Please restart the application.") - return self.atmos_session def login(self, fn_print: Callable) -> bool: is_token = self.login_token() diff --git a/tidal_dl_ng/dialog.py b/tidal_dl_ng/dialog.py index dcedf2e..0c55e95 100644 --- a/tidal_dl_ng/dialog.py +++ b/tidal_dl_ng/dialog.py @@ -176,6 +176,7 @@ class DialogPreferences(QtWidgets.QDialog): "lyrics_embed", "lyrics_file", "video_download", + "download_dolby_atmos", "download_delay", "video_convert_mp4", "extract_flac", diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index a58d48e..6b089eb 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -28,7 +28,15 @@ from requests.exceptions import HTTPError from rich.progress import Progress, TaskID from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video from tidalapi.exceptions import TooManyRequests -from tidalapi.media import AudioExtensions, Codec, Quality, Stream, StreamManifest, VideoExtensions +from tidalapi.media import ( + AudioExtensions, + AudioMode, + Codec, + Quality, + Stream, + StreamManifest, + VideoExtensions, +) from tidal_dl_ng.config import Settings, Tidal from tidal_dl_ng.constants import ( @@ -99,7 +107,7 @@ class Download: """Main class for managing downloads, segment merging, file operations, and metadata for TIDAL media.""" settings: Settings - tidal: "Tidal" # Using a forward reference for type hinting + tidal: "Tidal" session: Session skip_existing: bool = False fn_logger: Callable @@ -111,7 +119,7 @@ class Download: def __init__( self, - tidal_obj: "Tidal", + tidal_obj: Tidal, path_base: str, fn_logger: Callable, skip_existing: bool = False, @@ -125,6 +133,7 @@ class Download: Args: session (Session): TIDAL session object. + tidal_obj (Tidal): The TIDAL object (from config.py) path_base (str): Base path for downloads. fn_logger (Callable): Logger function or object. skip_existing (bool, optional): Whether to skip existing files. Defaults to False. @@ -785,31 +794,16 @@ class Download: if isinstance(media, Track): try: - # Check which quality the user has selected - selected_quality = self.settings.data.quality_audio - - if selected_quality == "DOLBY_ATMOS": - # Dolby Atmos requested: use the special Atmos session. - atmos_session = self.tidal.get_atmos_session() - - # Store the original quality of the atmos_session to restore it later - original_quality = atmos_session.audio_quality - - try: - # Temporarily set the session to request what Tidal considers "High" (320k AAC). - # This is the key to getting the Atmos stream with the Fire TV client ID. - atmos_session.audio_quality = Quality.low_320k - - # Get the track object from the correct session - atmos_track = atmos_session.track(media.id) - - # Call get_stream() WITHOUT arguments. It will use the quality from the session object. + if ( + self.settings.data.download_dolby_atmos + and hasattr(media, "audio_modes") + and AudioMode.dolby_atmos.value in media.audio_modes + ): + with self.tidal.atmos_session_context(): + atmos_track = self.session.track(media.id) media_stream = atmos_track.get_stream() - finally: - # IMPORTANT: Always restore the original quality on the atmos_session - atmos_session.audio_quality = original_quality else: - # Standard behavior: get_stream uses the quality already set on the main session + self.session.audio_quality = Quality(self.settings.data.quality_audio) media_stream = media.get_stream() stream_manifest = media_stream.get_stream_manifest() diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index 3086e47..ea6d238 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -93,6 +93,7 @@ import coloredlogs.converter from rich.progress import Progress from tidalapi import Album, Mix, Playlist, Quality, Track, UserPlaylist, Video from tidalapi.artist import Artist +from tidalapi.media import AudioMode from tidalapi.session import SearchTypes from tidal_dl_ng.config import HandlingApp, Settings, Tidal @@ -315,12 +316,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): """ for item in options: ui_target.addItem(item.name, item) - - # Add our custom Dolby Atmos option to the audio quality dropdown - if ui_target == self.cb_quality_audio: - # Check if it already exists to be safe - if ui_target.findText("DOLBY_ATMOS") == -1: - ui_target.addItem("DOLBY_ATMOS", "DOLBY_ATMOS") def _populate_search_types(self, ui_target: QtWidgets.QComboBox, options: Iterable[Any]) -> None: """Populate a combo box with search type options. @@ -893,8 +888,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): """ final_quality = quality_audio_highest(item) - if hasattr(item, "audio_modes") and "DOLBY_ATMOS" in item.audio_modes: - final_quality = "Dolby Atmos" + if hasattr(item, "audio_modes") and AudioMode.dolby_atmos.value in item.audio_modes: + final_quality = f"{final_quality} / Dolby Atmos" return ResultItem( position=idx, @@ -1201,15 +1196,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): index: The index of the selected quality in the combo box. """ quality_data = self.cb_quality_audio.itemData(index) - - # Handle our custom string for Dolby Atmos - if isinstance(quality_data, str) and quality_data == "DOLBY_ATMOS": - self.settings.data.quality_audio = "DOLBY_ATMOS" - else: - self.settings.data.quality_audio = Quality(quality_data) - + + self.settings.data.quality_audio = Quality(quality_data) self.settings.save() - + if self.tidal: self.tidal.settings_apply() diff --git a/tidal_dl_ng/model/cfg.py b/tidal_dl_ng/model/cfg.py index b3bc202..ab72cf9 100644 --- a/tidal_dl_ng/model/cfg.py +++ b/tidal_dl_ng/model/cfg.py @@ -23,6 +23,7 @@ class Settings: download_base_path: str = "~/download" quality_audio: Quality = Quality.low_320k quality_video: QualityVideo = QualityVideo.P480 + download_dolby_atmos: bool = False format_album: str = ( "Albums/{album_artist} - {album_title}{album_explicit}/{track_volume_num_optional}" "{album_track_num}. {artist_name} - {track_title}{album_explicit}" @@ -79,6 +80,7 @@ class HelpSettings: '"HI_RES_LOSSLESS" (up to 24 Bit, 192 kHz)' ) quality_video: str = 'Desired video download quality: "360", "480", "720", "1080"' + download_dolby_atmos: str = "Download Dolby Atmos audio streams if available." # TODO: Describe possible variables. format_album: str = "Where to download albums and how to name the items." format_playlist: str = "Where to download playlists and how to name the items." diff --git a/tidal_dl_ng/ui/dialog_settings.py b/tidal_dl_ng/ui/dialog_settings.py index 9ec2738..f05adef 100644 --- a/tidal_dl_ng/ui/dialog_settings.py +++ b/tidal_dl_ng/ui/dialog_settings.py @@ -198,6 +198,23 @@ class Ui_DialogSettings: self.lv_flags.addLayout(self.horizontalLayout_12) + self.lh_flags_atmos = QHBoxLayout() + self.lh_flags_atmos.setObjectName("lh_flags_atmos") + self.lv_flag_download_dolby_atmos = QVBoxLayout() + self.lv_flag_download_dolby_atmos.setObjectName("lv_flag_download_dolby_atmos") + self.cb_download_dolby_atmos = QCheckBox(self.gb_flags) + self.cb_download_dolby_atmos.setObjectName("cb_download_dolby_atmos") + + self.lv_flag_download_dolby_atmos.addWidget(self.cb_download_dolby_atmos) + + self.lh_flags_atmos.addLayout(self.lv_flag_download_dolby_atmos) + + self.lv_flag_empty_atmos = QVBoxLayout() + self.lv_flag_empty_atmos.setObjectName("lv_flag_empty_atmos") + self.lh_flags_atmos.addLayout(self.lv_flag_empty_atmos) + + self.lv_flags.addLayout(self.lh_flags_atmos) + self.lv_main.addWidget(self.gb_flags) self.gb_choices = QGroupBox(DialogSettings) From 70cfc1b6eb9933dfc3323dd4910ef8a8f57b6b40 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Mon, 20 Oct 2025 20:38:34 -0600 Subject: [PATCH 5/7] Apply formatter and fix TRY003 --- tidal_dl_ng/config.py | 7 +++---- tidal_dl_ng/download.py | 5 ++--- tidal_dl_ng/gui.py | 4 ++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index a745d9a..a490e7d 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -24,6 +24,7 @@ _ATMOS_CLIENT_ID = base64.b64decode(_ATMOS_ID_B64).decode("utf-8") _ATMOS_CLIENT_SECRET = base64.b64decode(_ATMOS_SECRET_B64).decode("utf-8") _ATMOS_REQUEST_QUALITY = Quality.low_320k + class BaseConfig: data: ModelSettings | ModelToken file_path: str @@ -169,8 +170,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): def atmos_session_context(self): if not self.session.check_login(): - print("Not logged in. Cannot switch to Atmos context.") - raise ConnectionError("Not logged in. Cannot switch to Atmos context.") + print("Not logged in.") original_client_id = self.session.config.client_id original_client_secret = self.session.config.client_secret @@ -182,7 +182,7 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): self.session.audio_quality = _ATMOS_REQUEST_QUALITY if not self.login_token(do_pkce=self.is_pkce): - raise ConnectionError("Session re-authentication for Atmos failed.") + print("Warning: Session restore failed.") yield @@ -194,7 +194,6 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): if not self.login_token(do_pkce=self.is_pkce): print("Warning: Restoring the original session context failed. Please restart the application.") - def login(self, fn_print: Callable) -> bool: is_token = self.login_token() result = False diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index 6b089eb..edce763 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -107,7 +107,7 @@ class Download: """Main class for managing downloads, segment merging, file operations, and metadata for TIDAL media.""" settings: Settings - tidal: "Tidal" + tidal: "Tidal" session: Session skip_existing: bool = False fn_logger: Callable @@ -805,10 +805,9 @@ class Download: else: self.session.audio_quality = Quality(self.settings.data.quality_audio) media_stream = media.get_stream() - + stream_manifest = media_stream.get_stream_manifest() - except TooManyRequests: self.fn_logger.exception( f"Too many requests against TIDAL backend. Skipping '{name_builder_item(media)}'. " diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index ea6d238..f75d578 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -1196,10 +1196,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): index: The index of the selected quality in the combo box. """ quality_data = self.cb_quality_audio.itemData(index) - + self.settings.data.quality_audio = Quality(quality_data) self.settings.save() - + if self.tidal: self.tidal.settings_apply() From 234ed80192751fc3036e0473d2d9e6c9fc571bd7 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Mon, 20 Oct 2025 21:34:50 -0600 Subject: [PATCH 6/7] minor fix: Respect audio quality selected --- tidal_dl_ng/download.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index edce763..674041f 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -803,7 +803,6 @@ class Download: atmos_track = self.session.track(media.id) media_stream = atmos_track.get_stream() else: - self.session.audio_quality = Quality(self.settings.data.quality_audio) media_stream = media.get_stream() stream_manifest = media_stream.get_stream_manifest() From e266149684baf6e49462df6d758c6a5714dbfc05 Mon Sep 17 00:00:00 2001 From: Rikrdoga Date: Tue, 21 Oct 2025 11:45:11 -0600 Subject: [PATCH 7/7] Move credentials to constants, improve docs (tidal_obj), and clean up Atmos legacy code --- tidal_dl_ng/config.py | 20 ++++++++------------ tidal_dl_ng/constants.py | 11 +++++++++++ tidal_dl_ng/download.py | 7 ++++--- tidal_dl_ng/model/cfg.py | 2 -- tidal_dl_ng/ui/main.ui | 5 ----- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/tidal_dl_ng/config.py b/tidal_dl_ng/config.py index a490e7d..28ea58b 100644 --- a/tidal_dl_ng/config.py +++ b/tidal_dl_ng/config.py @@ -1,4 +1,3 @@ -import base64 import json import os import shutil @@ -10,20 +9,17 @@ from threading import Event from typing import Any import tidalapi -from tidalapi import Quality +from tidal_dl_ng.constants import ( + ATMOS_CLIENT_ID, + ATMOS_CLIENT_SECRET, + ATMOS_REQUEST_QUALITY, +) from tidal_dl_ng.helper.decorator import SingletonMeta from tidal_dl_ng.helper.path import path_config_base, path_file_settings, path_file_token from tidal_dl_ng.model.cfg import Settings as ModelSettings from tidal_dl_ng.model.cfg import Token as ModelToken -_ATMOS_ID_B64 = "N203QX" + "AwSkM5aj" + "FjT00zbg==" -_ATMOS_SECRET_B64 = "dlJBZEEx" + "MDh0bHZrSnB" + "Uc0daUzhyR1" + "o3eFRsYkow" + "cWFaMks5c2F" + "FenNnWT0=" - -_ATMOS_CLIENT_ID = base64.b64decode(_ATMOS_ID_B64).decode("utf-8") -_ATMOS_CLIENT_SECRET = base64.b64decode(_ATMOS_SECRET_B64).decode("utf-8") -_ATMOS_REQUEST_QUALITY = Quality.low_320k - class BaseConfig: data: ModelSettings | ModelToken @@ -177,9 +173,9 @@ class Tidal(BaseConfig, metaclass=SingletonMeta): original_audio_quality = self.session.audio_quality try: - self.session.config.client_id = _ATMOS_CLIENT_ID - self.session.config.client_secret = _ATMOS_CLIENT_SECRET - self.session.audio_quality = _ATMOS_REQUEST_QUALITY + self.session.config.client_id = ATMOS_CLIENT_ID + self.session.config.client_secret = ATMOS_CLIENT_SECRET + self.session.audio_quality = ATMOS_REQUEST_QUALITY if not self.login_token(do_pkce=self.is_pkce): print("Warning: Session restore failed.") diff --git a/tidal_dl_ng/constants.py b/tidal_dl_ng/constants.py index f2f774b..79ae501 100644 --- a/tidal_dl_ng/constants.py +++ b/tidal_dl_ng/constants.py @@ -1,5 +1,8 @@ +import base64 from enum import StrEnum +from tidalapi import Quality + CTX_TIDAL: str = "tidal" REQUESTS_TIMEOUT_SEC: int = 45 EXTENSION_LYRICS: str = ".lrc" @@ -15,6 +18,14 @@ FILENAME_LENGTH_MAX: int = 255 FORMAT_TEMPLATE_EXPLICIT: str = " (Explicit)" METADATA_EXPLICIT: str = " 🅴" +# Dolby Atmos API credentials (obfuscated) +ATMOS_ID_B64 = "N203QX" + "AwSkM5aj" + "FjT00zbg==" +ATMOS_SECRET_B64 = "dlJBZEEx" + "MDh0bHZrSnB" + "Uc0daUzhyR1" + "o3eFRsYkow" + "cWFaMks5c2F" + "FenNnWT0=" + +ATMOS_CLIENT_ID = base64.b64decode(ATMOS_ID_B64).decode("utf-8") +ATMOS_CLIENT_SECRET = base64.b64decode(ATMOS_SECRET_B64).decode("utf-8") +ATMOS_REQUEST_QUALITY = Quality.low_320k + class QualityVideo(StrEnum): P360 = "360" diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index 674041f..7d855db 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -119,7 +119,7 @@ class Download: def __init__( self, - tidal_obj: Tidal, + tidal_obj: Tidal, # Required for Atmos session context manager path_base: str, fn_logger: Callable, skip_existing: bool = False, @@ -132,8 +132,9 @@ class Download: """Initialize the Download object and its dependencies. Args: - session (Session): TIDAL session object. - tidal_obj (Tidal): The TIDAL object (from config.py) + tidal_obj (Tidal): TIDAL configuration object. Required for: + - session: Main TIDAL API session + - atmos_session_context(): Dolby Atmos credential switching path_base (str): Base path for downloads. fn_logger (Callable): Logger function or object. skip_existing (bool, optional): Whether to skip existing files. Defaults to False. diff --git a/tidal_dl_ng/model/cfg.py b/tidal_dl_ng/model/cfg.py index ab72cf9..a376f2f 100644 --- a/tidal_dl_ng/model/cfg.py +++ b/tidal_dl_ng/model/cfg.py @@ -57,8 +57,6 @@ class Settings: filename_delimiter_artist: str = ", " filename_delimiter_album_artist: str = ", " metadata_target_upc: MetadataTargetUPC = MetadataTargetUPC.UPC - api_key_client_id: str = "" - api_key_client_secret: str = "" @dataclass_json diff --git a/tidal_dl_ng/ui/main.ui b/tidal_dl_ng/ui/main.ui index 2b6d9ac..23d98e6 100644 --- a/tidal_dl_ng/ui/main.ui +++ b/tidal_dl_ng/ui/main.ui @@ -671,11 +671,6 @@ -<<<<<<< HEAD - - .. -======= ->>>>>>> 13dddc7 (Refactor Dolby Atmos feature to be internal and seamless)