1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
|
# tidal_dl_ng/queue.py
import time
from typing import TYPE_CHECKING
from PySide6 import QtCore, QtGui, QtWidgets
from tidalapi import Album, Artist, Mix, Playlist, Quality, Track, Video
from tidal_dl_ng.config import HandlingApp, Settings
from tidal_dl_ng.constants import QualityVideo, QueueDownloadStatus
from tidal_dl_ng.download import Download
from tidal_dl_ng.helper.gui import (
get_queue_download_media,
get_queue_download_quality_audio,
get_queue_download_quality_video,
set_queue_download_media,
)
from tidal_dl_ng.helper.path import get_format_template
from tidal_dl_ng.helper.tidal import items_results_all, name_builder_artist, name_builder_title, quality_audio_highest
from tidal_dl_ng.logger import logger_gui
from tidal_dl_ng.model.gui_data import QueueDownloadItem, StatusbarMessage
if TYPE_CHECKING:
from tidal_dl_ng.gui import MainWindow
class GuiQueueManager:
"""Manages the download queue GUI and logic."""
def __init__(self, main_window: "MainWindow"):
"""Initialize the queue manager."""
self.main_window: "MainWindow" = main_window
self.settings: Settings = main_window.settings
def init_ui(self):
"""Initialize UI elements related to the queue."""
self._init_tree_queue(self.main_window.tr_queue_download)
self.pb_queue_download_run()
def connect_signals(self):
"""Connect signals for queue-related widgets."""
self.main_window.pb_queue_download_clear_all.clicked.connect(self.on_queue_download_clear_all)
self.main_window.pb_queue_download_clear_finished.clicked.connect(self.on_queue_download_clear_finished)
self.main_window.pb_queue_download_remove.clicked.connect(self.on_queue_download_remove)
self.main_window.pb_queue_download_toggle.clicked.connect(self.on_pb_queue_download_toggle)
self.main_window.tr_queue_download.itemClicked.connect(self.on_queue_download_item_clicked)
self.main_window.tr_queue_download.customContextMenuRequested.connect(self.menu_context_queue_download)
self.main_window.s_queue_download_item_downloading.connect(self.on_queue_download_item_downloading)
self.main_window.s_queue_download_item_finished.connect(self.on_queue_download_item_finished)
self.main_window.s_queue_download_item_failed.connect(self.on_queue_download_item_failed)
self.main_window.s_queue_download_item_skipped.connect(self.on_queue_download_item_skipped)
def _init_tree_queue(self, tree: QtWidgets.QTableWidget) -> None:
"""Initialize the download queue table widget."""
tree.setColumnHidden(1, True)
tree.setColumnWidth(2, 200)
header = tree.header()
if hasattr(header, "setSectionResizeMode"):
header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
def menu_context_queue_download(self, point: QtCore.QPoint) -> None:
"""Show context menu for download queue."""
item = self.main_window.tr_queue_download.itemAt(point)
if not item:
return
menu = QtWidgets.QMenu()
status = item.text(0)
if status == QueueDownloadStatus.Waiting:
menu.addAction("🗑️ Remove from Queue", lambda: self.on_queue_download_remove_item(item))
if menu.isEmpty():
return
menu.exec(self.main_window.tr_queue_download.mapToGlobal(point))
def on_queue_download_remove_item(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Remove a specific item from the download queue."""
index = self.main_window.tr_queue_download.indexOfTopLevelItem(item)
if index >= 0:
self.main_window.tr_queue_download.takeTopLevelItem(index)
logger_gui.info("Removed item from download queue")
def on_queue_download_clear_all(self) -> None:
"""Clear all items from the download queue."""
self.on_clear_queue_download(
f"({QueueDownloadStatus.Waiting}|{QueueDownloadStatus.Finished}|{QueueDownloadStatus.Failed})"
)
def on_queue_download_clear_finished(self) -> None:
"""Clear finished items from the download queue."""
self.on_clear_queue_download(f"[{QueueDownloadStatus.Finished}]")
def on_clear_queue_download(self, regex: str) -> None:
"""Clear items from the download queue matching the given regex.
Args:
regex (str): Regular expression to match items.
"""
items: list[QtWidgets.QTreeWidgetItem | None] = self.main_window.tr_queue_download.findItems(
regex, QtCore.Qt.MatchFlag.MatchRegularExpression, column=0
)
for item in items:
self.main_window.tr_queue_download.takeTopLevelItem(
self.main_window.tr_queue_download.indexOfTopLevelItem(item)
)
def on_queue_download_remove(self) -> None:
"""Remove selected items from the download queue."""
items: list[QtWidgets.QTreeWidgetItem | None] = self.main_window.tr_queue_download.selectedItems()
if len(items) == 0:
logger_gui.error("Please select an item from the queue first.")
else:
for item in items:
status: str = item.text(0)
if status != QueueDownloadStatus.Downloading:
self.main_window.tr_queue_download.takeTopLevelItem(
self.main_window.tr_queue_download.indexOfTopLevelItem(item)
)
else:
logger_gui.info("Cannot remove a currently downloading item from queue.")
def on_pb_queue_download_toggle(self) -> None:
"""Toggle download status (pause / resume) accordingly.
:return: None
"""
handling_app: HandlingApp = HandlingApp()
if handling_app.event_run.is_set():
self.pb_queue_download_pause()
else:
self.pb_queue_download_run()
def pb_queue_download_run(self) -> None:
"""Start the download queue and update the button state."""
handling_app: HandlingApp = HandlingApp()
handling_app.event_run.set()
icon = QtGui.QIcon(QtGui.QIcon.fromTheme(QtGui.QIcon.ThemeIcon.MediaPlaybackPause))
self.main_window.pb_queue_download_toggle.setIcon(icon)
self.main_window.pb_queue_download_toggle.setStyleSheet("background-color: #e0a800; color: #212529")
def pb_queue_download_pause(self) -> None:
"""Pause the download queue and update the button state."""
handling_app: HandlingApp = HandlingApp()
handling_app.event_run.clear()
icon = QtGui.QIcon(QtGui.QIcon.fromTheme(QtGui.QIcon.ThemeIcon.MediaPlaybackStart))
self.main_window.pb_queue_download_toggle.setIcon(icon)
self.main_window.pb_queue_download_toggle.setStyleSheet("background-color: #218838; color: #fff")
def queue_download_media(self, queue_dl_item: QueueDownloadItem) -> None:
"""Add a media item to the download queue."""
child = QtWidgets.QTreeWidgetItem()
child.setText(0, queue_dl_item.status)
set_queue_download_media(child, queue_dl_item.obj)
child.setText(2, queue_dl_item.name)
child.setText(3, queue_dl_item.type_media)
child.setText(4, str(queue_dl_item.quality_audio))
child.setText(5, str(queue_dl_item.quality_video))
self.main_window.tr_queue_download.addTopLevelItem(child)
def watcher_queue_download(self) -> None:
"""Monitor the download queue and process items as they become available."""
handling_app: HandlingApp = HandlingApp()
while not handling_app.event_abort.is_set():
items = self.main_window.tr_queue_download.findItems(
QueueDownloadStatus.Waiting, QtCore.Qt.MatchFlag.MatchExactly, column=0
)
if items:
item = items[0]
media = get_queue_download_media(item)
quality_audio = get_queue_download_quality_audio(item)
quality_video = get_queue_download_quality_video(item)
try:
self.main_window.s_queue_download_item_downloading.emit(item)
result = self.on_queue_download(media, quality_audio=quality_audio, quality_video=quality_video)
if result == QueueDownloadStatus.Finished:
self.main_window.s_queue_download_item_finished.emit(item)
elif result == QueueDownloadStatus.Skipped:
self.main_window.s_queue_download_item_skipped.emit(item)
except Exception as e:
logger_gui.error(e)
self.main_window.s_queue_download_item_failed.emit(item)
else:
time.sleep(2)
def on_queue_download_item_downloading(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Update the status of a queue download item to 'Downloading'."""
self.queue_download_item_status(item, QueueDownloadStatus.Downloading)
def on_queue_download_item_finished(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Update the status of a queue download item to 'Finished'."""
self.queue_download_item_status(item, QueueDownloadStatus.Finished)
def on_queue_download_item_failed(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Update the status of a queue download item to 'Failed'."""
self.queue_download_item_status(item, QueueDownloadStatus.Failed)
def on_queue_download_item_skipped(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Update the status of a queue download item to 'Skipped'."""
self.queue_download_item_status(item, QueueDownloadStatus.Skipped)
def queue_download_item_status(self, item: QtWidgets.QTreeWidgetItem, status: str) -> None:
"""Set the status text of a queue download item."""
item.setText(0, status)
def on_queue_download(
self,
media: Track | Album | Playlist | Video | Mix | Artist,
quality_audio: Quality | None = None,
quality_video: QualityVideo | None = None,
) -> QueueDownloadStatus:
"""Download the specified media item(s) and return the result status."""
items_media = items_results_all(self.main_window.tidal.session, media) if isinstance(media, Artist) else [media]
download_delay = bool(isinstance(media, Track | Video) and self.settings.data.download_delay)
result = QueueDownloadStatus.Failed
for item_media in items_media:
result = self.download(
item_media,
self.main_window.dl,
delay_track=download_delay,
quality_audio=quality_audio,
quality_video=quality_video,
)
return result
def download(
self,
media: Track | Album | Playlist | Video | Mix,
dl: Download,
delay_track: bool = False,
quality_audio: Quality | None = None,
quality_video: QualityVideo | None = None,
) -> QueueDownloadStatus:
"""Download a media item and return the result status."""
self.main_window.s_pb_reset.emit()
self.main_window.s_statusbar_message.emit(StatusbarMessage(message="Download started..."))
file_template = get_format_template(media, self.settings)
result_dl, path_file = False, None
if isinstance(media, Track | Video):
result_dl, path_file = dl.item(
media=media,
file_template=file_template,
download_delay=delay_track,
quality_audio=quality_audio,
quality_video=quality_video,
)
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_audio=quality_audio,
quality_video=quality_video,
)
result_dl, path_file = True, "dummy"
self.main_window.s_statusbar_message.emit(StatusbarMessage(message="Download finished.", timeout=2000))
if result_dl and path_file:
return QueueDownloadStatus.Finished
if not result_dl and path_file:
return QueueDownloadStatus.Skipped
return QueueDownloadStatus.Failed
def on_queue_download_item_clicked(self, item: QtWidgets.QTreeWidgetItem) -> None:
"""Handle the event when a queue download item is clicked."""
media = get_queue_download_media(item)
self.main_window.info_tab_widget.update_on_selection(media)
self.main_window.thread_it(self.main_window.cover_manager.load_cover, media)
def media_to_queue_download_model(
self, media: Artist | Track | Video | Album | Playlist | Mix
) -> QueueDownloadItem | None:
"""Convert a media object to a QueueDownloadItem for the download queue."""
if hasattr(media, "available") and media.available is False:
return None
explicit = " 🅴" if isinstance(media, Track | Video | Album) and media.explicit else ""
name = ""
if isinstance(media, Track | Video):
name = f"{name_builder_artist(media)} - {name_builder_title(media)}{explicit}"
elif isinstance(media, Playlist | Artist):
name = media.name
elif isinstance(media, Album):
name = f"{name_builder_artist(media)} - {media.name}{explicit}"
elif isinstance(media, Mix):
name = media.title
quality_audio = self.settings.data.quality_audio
if isinstance(media, Track | Album):
quality_highest = quality_audio_highest(media)
if (
self.settings.data.quality_audio == quality_highest
or self.settings.data.quality_audio == Quality.hi_res_lossless
):
quality_audio = quality_highest
if name:
return QueueDownloadItem(
name=name,
quality_audio=quality_audio,
quality_video=self.settings.data.quality_video,
type_media=type(media).__name__,
status=QueueDownloadStatus.Waiting,
obj=media,
)
return None
|