From bdc7717ef389c0472d57120d2bc78541f32be6e0 Mon Sep 17 00:00:00 2001 From: afkarxyz Date: Sat, 26 Jul 2025 09:37:45 +0700 Subject: [PATCH] v4.2 --- SpotiFLAC.py | 457 +++++++++++++++++++++++++++++++++++++-------------- deezer.png | Bin 0 -> 6504 bytes deezerDL.py | 45 ++--- eu.svg | 28 ++++ icon.ico | Bin 0 -> 172823 bytes icon.svg | 48 ++++++ qobuz.png | Bin 0 -> 42467 bytes qobuzDL.py | 56 ++----- tidal.png | Bin 0 -> 6252 bytes tidalDL.py | 245 ++++++++++++--------------- us.svg | 9 + 11 files changed, 575 insertions(+), 313 deletions(-) create mode 100644 deezer.png create mode 100644 eu.svg create mode 100644 icon.ico create mode 100644 icon.svg create mode 100644 qobuz.png create mode 100644 tidal.png create mode 100644 us.svg diff --git a/SpotiFLAC.py b/SpotiFLAC.py index a2a291f..9ffd3e6 100644 --- a/SpotiFLAC.py +++ b/SpotiFLAC.py @@ -2,15 +2,17 @@ import sys import os from dataclasses import dataclass from datetime import datetime +from pathlib import Path import requests import re import asyncio from packaging import version +import qdarktheme from PyQt6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel, QFileDialog, QListWidget, QTextEdit, QTabWidget, QButtonGroup, QRadioButton, - QAbstractItemView, QSpacerItem, QSizePolicy, QProgressBar, QCheckBox, QDialog, + QAbstractItemView, QProgressBar, QCheckBox, QDialog, QDialogButtonBox, QComboBox, QStyledItemDelegate ) from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl, QTimer, QTime, QSettings, QSize @@ -82,7 +84,7 @@ class DownloadWorker(QThread): filename = f"{track.title}.flac" else: filename = f"{track.title} - {track.artists}.flac" - return re.sub(r'[<>:"/\\|?*]', '_', filename) + return re.sub(r'[<>:"/\\|?*]', lambda m: "'" if m.group() == '"' else '_', filename) def run(self): try: @@ -94,16 +96,12 @@ class DownloadWorker(QThread): downloader = DeezerDownloader() else: downloader = TidalDownloader() - def progress_update(current, total): if total > 0: percent = (current / total) * 100 - current_mb = current / (1024 * 1024) - total_mb = total / (1024 * 1024) - self.progress.emit(f"Download progress: {percent:.2f}% ({current_mb:.2f}MB/{total_mb:.2f}MB)", - int(percent)) + self.progress.emit("", int(percent)) else: - self.progress.emit(f"Processing metadata...", 0) + self.progress.emit("Processing metadata...", 0) downloader.set_progress_callback(progress_update) @@ -133,7 +131,7 @@ class DownloadWorker(QThread): else: new_filename = self.get_formatted_filename(track) - new_filename = re.sub(r'[<>:"/\\|?*]', '_', new_filename) + new_filename = re.sub(r'[<>:"/\\|?*]', lambda m: "'" if m.group() == '"' else '_', new_filename) new_filepath = os.path.join(track_outpath, new_filename) if os.path.exists(new_filepath) and os.path.getsize(new_filepath) > 0: @@ -169,23 +167,14 @@ class DownloadWorker(QThread): is_paused_callback = lambda: self.is_paused is_stopped_callback = lambda: self.is_stopped - try: - loop = asyncio.get_event_loop() - if loop.is_closed(): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - download_result_details = loop.run_until_complete(downloader.download( + download_result_details = downloader.download( query=f"{track.title} {track.artists}", isrc=track.isrc, output_dir=track_outpath, quality="LOSSLESS", is_paused_callback=is_paused_callback, is_stopped_callback=is_stopped_callback - )) + ) if isinstance(download_result_details, str) and os.path.exists(download_result_details): downloaded_file = download_result_details @@ -208,16 +197,7 @@ class DownloadWorker(QThread): self.progress.emit(f"Downloading from Deezer with ISRC: {track.isrc}", 0) - try: - loop = asyncio.get_event_loop() - if loop.is_closed(): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - success = loop.run_until_complete(downloader.download_by_isrc(track.isrc, track_outpath)) + success = asyncio.run(downloader.download_by_isrc(track.isrc, track_outpath)) if success: safe_title = "".join(c for c in track.title if c.isalnum() or c in (' ', '-', '_')).rstrip() @@ -311,26 +291,20 @@ class DownloadWorker(QThread): class UpdateDialog(QDialog): def __init__(self, current_version, new_version, parent=None): super().__init__(parent) - self.setWindowTitle("Update Available") + self.setWindowTitle("Update Now") self.setFixedWidth(400) self.setModal(True) layout = QVBoxLayout() - message = QLabel(f"A new version of SpotiFLAC is available!\n\n" - f"Current version: v{current_version}\n" - f"New version: v{new_version}") + message = QLabel(f"SpotiFLAC v{new_version} Available!") message.setWordWrap(True) layout.addWidget(message) - self.disable_check = QCheckBox("Turn off update checking") - self.disable_check.setCursor(Qt.CursorShape.PointingHandCursor) - layout.addWidget(self.disable_check) - button_box = QDialogButtonBox() - self.update_button = QPushButton("Update") + self.update_button = QPushButton("Check") self.update_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.cancel_button = QPushButton("Cancel") + self.cancel_button = QPushButton("Later") self.cancel_button.setCursor(Qt.CursorShape.PointingHandCursor) button_box.addButton(self.update_button, QDialogButtonBox.ButtonRole.AcceptRole) @@ -342,8 +316,6 @@ class UpdateDialog(QDialog): self.update_button.clicked.connect(self.accept) self.cancel_button.clicked.connect(self.reject) - - class TidalStatusChecker(QThread): status_updated = pyqtSignal(bool) @@ -398,7 +370,7 @@ class StatusIndicatorDelegate(QStyledItemDelegate): circle_size = 6 circle_y = option.rect.center().y() - circle_size // 2 - circle_x = option.rect.right() - circle_size - 10 + circle_x = option.rect.right() - circle_size - 5 painter.save() painter.setPen(Qt.PenStyle.NoPen) @@ -422,7 +394,7 @@ class ServiceComboBox(QComboBox): self.tidal_status_timer = QTimer(self) self.tidal_status_timer.timeout.connect(self.refresh_tidal_status) - self.tidal_status_timer.start(6000) + self.tidal_status_timer.start(60000) self.deezer_status_checker = DeezerStatusChecker() self.deezer_status_checker.status_updated.connect(self.update_deezer_service_status) @@ -431,7 +403,7 @@ class ServiceComboBox(QComboBox): self.deezer_status_timer = QTimer(self) self.deezer_status_timer.timeout.connect(self.refresh_deezer_status) - self.deezer_status_timer.start(6000) + self.deezer_status_timer.start(60000) def setup_items(self): current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -458,10 +430,10 @@ class ServiceComboBox(QComboBox): pixmap.fill(Qt.GlobalColor.transparent) pixmap.save(path) - def update_tidal_service_status(self, is_online): + def update_service_status(self, service_id, is_online): for i in range(self.count()): - service_id = self.itemData(i, Qt.ItemDataRole.UserRole + 1) - if service_id == 'tidal': + current_service_id = self.itemData(i, Qt.ItemDataRole.UserRole + 1) + if current_service_id == service_id: service_data = self.itemData(i, Qt.ItemDataRole.UserRole) if isinstance(service_data, dict): service_data['online'] = is_online @@ -469,24 +441,27 @@ class ServiceComboBox(QComboBox): break self.update() + def update_tidal_service_status(self, is_online): + self.update_service_status('tidal', is_online) + def refresh_tidal_status(self): + if hasattr(self, 'tidal_status_checker') and self.tidal_status_checker.isRunning(): + self.tidal_status_checker.quit() + self.tidal_status_checker.wait() + self.tidal_status_checker = TidalStatusChecker() self.tidal_status_checker.status_updated.connect(self.update_tidal_service_status) self.tidal_status_checker.error.connect(lambda e: print(f"Tidal status check error: {e}")) self.tidal_status_checker.start() def update_deezer_service_status(self, is_online): - for i in range(self.count()): - service_id = self.itemData(i, Qt.ItemDataRole.UserRole + 1) - if service_id == 'deezer': - service_data = self.itemData(i, Qt.ItemDataRole.UserRole) - if isinstance(service_data, dict): - service_data['online'] = is_online - self.setItemData(i, service_data, Qt.ItemDataRole.UserRole) - break - self.update() + self.update_service_status('deezer', is_online) def refresh_deezer_status(self): + if hasattr(self, 'deezer_status_checker') and self.deezer_status_checker.isRunning(): + self.deezer_status_checker.quit() + self.deezer_status_checker.wait() + self.deezer_status_checker = DeezerStatusChecker() self.deezer_status_checker.status_updated.connect(self.update_deezer_service_status) self.deezer_status_checker.error.connect(lambda e: print(f"Deezer status check error: {e}")) @@ -525,7 +500,7 @@ class QobuzRegionComboBox(QComboBox): self.status_timer = QTimer(self) self.status_timer.timeout.connect(self.check_status) - self.status_timer.start(10000) + self.status_timer.start(60000) def setup_items(self): current_dir = os.path.dirname(os.path.abspath(__file__)) @@ -566,6 +541,12 @@ class QobuzRegionComboBox(QComboBox): self.update() def check_status(self): + for region_id, checker in self.status_checkers.items(): + if checker.isRunning(): + checker.quit() + checker.wait() + self.status_checkers.clear() + for region in self.regions: region_id = region['id'] checker = QobuzStatusChecker(region_id) @@ -583,13 +564,13 @@ class QobuzRegionComboBox(QComboBox): class SpotiFLACGUI(QWidget): def __init__(self): super().__init__() - self.current_version = "4.1" + self.current_version = "4.2" self.tracks = [] self.all_tracks = [] self.reset_state() self.settings = QSettings('SpotiFLAC', 'Settings') - self.last_output_path = self.settings.value('output_path', os.path.expanduser("~\\Music")) + self.last_output_path = self.settings.value('output_path', str(Path.home() / "Music")) self.last_url = self.settings.value('spotify_url', '') self.filename_format = self.settings.value('filename_format', 'title_artist') @@ -598,6 +579,7 @@ class SpotiFLACGUI(QWidget): self.service = self.settings.value('service', 'tidal') self.qobuz_region = self.settings.value('qobuz_region', 'us') self.check_for_updates = self.settings.value('check_for_updates', True, type=bool) + self.current_theme_color = self.settings.value('theme_color', '#2196F3') self.elapsed_time = QTime(0, 0, 0) self.timer = QTimer(self) @@ -611,6 +593,13 @@ class SpotiFLACGUI(QWidget): if self.check_for_updates: QTimer.singleShot(0, self.check_updates) + def set_combobox_value(self, combobox, target_value): + for i in range(combobox.count()): + if combobox.itemData(i, Qt.ItemDataRole.UserRole + 1) == target_value: + combobox.setCurrentIndex(i) + return True + return False + def check_updates(self): try: response = requests.get("https://raw.githubusercontent.com/afkarxyz/SpotiFLAC/refs/heads/main/version.json") @@ -622,9 +611,6 @@ class SpotiFLACGUI(QWidget): dialog = UpdateDialog(self.current_version, new_version, self) result = dialog.exec() - if dialog.disable_check.isChecked(): - self.settings.setValue('check_for_updates', False) - self.check_for_updates = False if result == QDialog.DialogCode.Accepted: QDesktopServices.openUrl(QUrl("https://github.com/afkarxyz/SpotiFLAC/releases")) @@ -682,12 +668,13 @@ class SpotiFLACGUI(QWidget): spotify_label.setFixedWidth(100) self.spotify_url = QLineEdit() - self.spotify_url.setPlaceholderText("Please enter the Spotify URL") + self.spotify_url.setPlaceholderText("Enter Spotify URL") self.spotify_url.setClearButtonEnabled(True) self.spotify_url.setText(self.last_url) self.spotify_url.textChanged.connect(self.save_url) self.fetch_btn = QPushButton('Fetch') + self.fetch_btn.setFixedWidth(80) self.fetch_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.fetch_btn.clicked.connect(self.fetch_tracks) @@ -730,6 +717,7 @@ class SpotiFLACGUI(QWidget): self.setup_dashboard_tab() self.setup_process_tab() self.setup_settings_tab() + self.setup_theme_tab() self.setup_about_tab() def setup_dashboard_tab(self): @@ -745,6 +733,7 @@ class SpotiFLACGUI(QWidget): self.setup_track_buttons() dashboard_layout.addLayout(self.btn_layout) + dashboard_layout.addWidget(self.single_track_container) dashboard_tab.setLayout(dashboard_layout) self.tab_widget.addTab(dashboard_tab, "Dashboard") @@ -814,7 +803,7 @@ class SpotiFLACGUI(QWidget): search_layout.addLayout(search_input_layout) self.search_widget.setLayout(search_layout) - self.search_widget.hide() + self.search_widget.hide() def setup_track_buttons(self): self.btn_layout = QHBoxLayout() @@ -824,7 +813,7 @@ class SpotiFLACGUI(QWidget): self.clear_btn = QPushButton('Clear') for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: - btn.setFixedWidth(150) + btn.setMinimumWidth(120) btn.setCursor(Qt.CursorShape.PointingHandCursor) self.download_selected_btn.clicked.connect(self.download_selected) @@ -834,8 +823,29 @@ class SpotiFLACGUI(QWidget): self.btn_layout.addStretch() for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: - self.btn_layout.addWidget(btn) + self.btn_layout.addWidget(btn, 1) self.btn_layout.addStretch() + + self.single_track_container = QWidget() + single_track_layout = QHBoxLayout(self.single_track_container) + single_track_layout.setContentsMargins(0, 0, 0, 0) + + self.single_download_btn = QPushButton('Download') + self.single_clear_btn = QPushButton('Clear') + + for btn in [self.single_download_btn, self.single_clear_btn]: + btn.setFixedWidth(120) + btn.setCursor(Qt.CursorShape.PointingHandCursor) + + self.single_download_btn.clicked.connect(self.download_all) + self.single_clear_btn.clicked.connect(self.clear_tracks) + + single_track_layout.addStretch() + single_track_layout.addWidget(self.single_download_btn) + single_track_layout.addWidget(self.single_clear_btn) + single_track_layout.addStretch() + + self.single_track_container.hide() def setup_process_tab(self): self.process_tab = QWidget() @@ -862,13 +872,19 @@ class SpotiFLACGUI(QWidget): self.stop_btn = QPushButton('Stop') self.pause_resume_btn = QPushButton('Pause') + self.stop_btn.setFixedWidth(120) + self.pause_resume_btn.setFixedWidth(120) + self.stop_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.pause_resume_btn.setCursor(Qt.CursorShape.PointingHandCursor) self.stop_btn.clicked.connect(self.stop_download) self.pause_resume_btn.clicked.connect(self.toggle_pause_resume) + + control_layout.addStretch() control_layout.addWidget(self.stop_btn) control_layout.addWidget(self.pause_resume_btn) + control_layout.addStretch() process_layout.addLayout(control_layout) @@ -901,6 +917,7 @@ class SpotiFLACGUI(QWidget): self.output_dir.textChanged.connect(self.save_settings) self.output_browse = QPushButton('Browse') + self.output_browse.setFixedWidth(80) self.output_browse.setCursor(Qt.CursorShape.PointingHandCursor) self.output_browse.clicked.connect(self.browse_output) @@ -1008,15 +1025,8 @@ class SpotiFLACGUI(QWidget): settings_layout.addStretch() settings_tab.setLayout(settings_layout) self.tab_widget.addTab(settings_tab, "Settings") - for i in range(self.service_dropdown.count()): - if self.service_dropdown.itemData(i, Qt.ItemDataRole.UserRole + 1) == self.service: - self.service_dropdown.setCurrentIndex(i) - break - - for i in range(self.qobuz_region_dropdown.count()): - if self.qobuz_region_dropdown.itemData(i, Qt.ItemDataRole.UserRole + 1) == self.qobuz_region: - self.qobuz_region_dropdown.setCurrentIndex(i) - break + self.set_combobox_value(self.service_dropdown, self.service) + self.set_combobox_value(self.qobuz_region_dropdown, self.qobuz_region) @@ -1026,18 +1036,189 @@ class SpotiFLACGUI(QWidget): lambda region_id, is_online: self.service_dropdown.update_qobuz_status(region_id, is_online) ) + def setup_theme_tab(self): + theme_tab = QWidget() + theme_layout = QVBoxLayout() + theme_layout.setSpacing(8) + theme_layout.setContentsMargins(15, 15, 15, 15) + + grid_layout = QVBoxLayout() + + self.color_buttons = {} + + first_row_palettes = [ + ("Red", [ + ("#FFCDD2", "100"), ("#EF9A9A", "200"), ("#E57373", "300"), ("#EF5350", "400"), ("#F44336", "500"), ("#E53935", "600"), ("#D32F2F", "700"), ("#C62828", "800"), ("#B71C1C", "900"), ("#FF8A80", "A100"), ("#FF5252", "A200"), ("#FF1744", "A400"), ("#D50000", "A700") + ]), + ("Pink", [ + ("#F8BBD0", "100"), ("#F48FB1", "200"), ("#F06292", "300"), ("#EC407A", "400"), ("#E91E63", "500"), ("#D81B60", "600"), ("#C2185B", "700"), ("#AD1457", "800"), ("#880E4F", "900"), ("#FF80AB", "A100"), ("#FF4081", "A200"), ("#F50057", "A400"), ("#C51162", "A700") + ]), + ("Purple", [ + ("#E1BEE7", "100"), ("#CE93D8", "200"), ("#BA68C8", "300"), ("#AB47BC", "400"), ("#9C27B0", "500"), ("#8E24AA", "600"), ("#7B1FA2", "700"), ("#6A1B9A", "800"), ("#4A148C", "900"), ("#EA80FC", "A100"), ("#E040FB", "A200"), ("#D500F9", "A400"), ("#AA00FF", "A700") + ]) + ] + + second_row_palettes = [ + ("Deep Purple", [ + ("#D1C4E9", "100"), ("#B39DDB", "200"), ("#9575CD", "300"), ("#7E57C2", "400"), ("#673AB7", "500"), ("#5E35B1", "600"), ("#512DA8", "700"), ("#4527A0", "800"), ("#311B92", "900"), ("#B388FF", "A100"), ("#7C4DFF", "A200"), ("#651FFF", "A400"), ("#6200EA", "A700") + ]), + ("Indigo", [ + ("#C5CAE9", "100"), ("#9FA8DA", "200"), ("#7986CB", "300"), ("#5C6BC0", "400"), ("#3F51B5", "500"), ("#3949AB", "600"), ("#303F9F", "700"), ("#283593", "800"), ("#1A237E", "900"), ("#8C9EFF", "A100"), ("#536DFE", "A200"), ("#3D5AFE", "A400"), ("#304FFE", "A700") + ]), + ("Blue", [ + ("#BBDEFB", "100"), ("#90CAF9", "200"), ("#64B5F6", "300"), ("#42A5F5", "400"), ("#2196F3", "500"), ("#1E88E5", "600"), ("#1976D2", "700"), ("#1565C0", "800"), ("#0D47A1", "900"), ("#82B1FF", "A100"), ("#448AFF", "A200"), ("#2979FF", "A400"), ("#2962FF", "A700") + ]) + ] + + third_row_palettes = [ + ("Light Blue", [ + ("#B3E5FC", "100"), ("#81D4FA", "200"), ("#4FC3F7", "300"), ("#29B6F6", "400"), ("#03A9F4", "500"), ("#039BE5", "600"), ("#0288D1", "700"), ("#0277BD", "800"), ("#01579B", "900"), ("#80D8FF", "A100"), ("#40C4FF", "A200"), ("#00B0FF", "A400"), ("#0091EA", "A700") + ]), + ("Cyan", [ + ("#B2EBF2", "100"), ("#80DEEA", "200"), ("#4DD0E1", "300"), ("#26C6DA", "400"), ("#00BCD4", "500"), ("#00ACC1", "600"), ("#0097A7", "700"), ("#00838F", "800"), ("#006064", "900"), ("#84FFFF", "A100"), ("#18FFFF", "A200"), ("#00E5FF", "A400"), ("#00B8D4", "A700") + ]), + ("Teal", [ + ("#B2DFDB", "100"), ("#80CBC4", "200"), ("#4DB6AC", "300"), ("#26A69A", "400"), ("#009688", "500"), ("#00897B", "600"), ("#00796B", "700"), ("#00695C", "800"), ("#004D40", "900"), ("#A7FFEB", "A100"), ("#64FFDA", "A200"), ("#1DE9B6", "A400"), ("#00BFA5", "A700") + ]) + ] + + fourth_row_palettes = [ + ("Green", [ + ("#C8E6C9", "100"), ("#A5D6A7", "200"), ("#81C784", "300"), ("#66BB6A", "400"), ("#4CAF50", "500"), ("#43A047", "600"), ("#388E3C", "700"), ("#2E7D32", "800"), ("#1B5E20", "900"), ("#B9F6CA", "A100"), ("#69F0AE", "A200"), ("#00E676", "A400"), ("#00C853", "A700") + ]), + ("Light Green", [ + ("#DCEDC8", "100"), ("#C5E1A5", "200"), ("#AED581", "300"), ("#9CCC65", "400"), ("#8BC34A", "500"), ("#7CB342", "600"), ("#689F38", "700"), ("#558B2F", "800"), ("#33691E", "900"), ("#CCFF90", "A100"), ("#B2FF59", "A200"), ("#76FF03", "A400"), ("#64DD17", "A700") + ]), + ("Lime", [ + ("#F0F4C3", "100"), ("#E6EE9C", "200"), ("#DCE775", "300"), ("#D4E157", "400"), ("#CDDC39", "500"), ("#C0CA33", "600"), ("#AFB42B", "700"), ("#9E9D24", "800"), ("#827717", "900"), ("#F4FF81", "A100"), ("#EEFF41", "A200"), ("#C6FF00", "A400"), ("#AEEA00", "A700") + ]) + ] + + fifth_row_palettes = [ + ("Yellow", [ + ("#FFF9C4", "100"), ("#FFF59D", "200"), ("#FFF176", "300"), ("#FFEE58", "400"), ("#FFEB3B", "500"), ("#FDD835", "600"), ("#FBC02D", "700"), ("#F9A825", "800"), ("#F57F17", "900"), ("#FFFF8D", "A100"), ("#FFFF00", "A200"), ("#FFEA00", "A400"), ("#FFD600", "A700") + ]), + ("Amber", [ + ("#FFECB3", "100"), ("#FFE082", "200"), ("#FFD54F", "300"), ("#FFCA28", "400"), ("#FFC107", "500"), ("#FFB300", "600"), ("#FFA000", "700"), ("#FF8F00", "800"), ("#FF6F00", "900"), ("#FFE57F", "A100"), ("#FFD740", "A200"), ("#FFC400", "A400"), ("#FFAB00", "A700") + ]), + ("Orange", [ + ("#FFE0B2", "100"), ("#FFCC80", "200"), ("#FFB74D", "300"), ("#FFA726", "400"), ("#FF9800", "500"), ("#FB8C00", "600"), ("#F57C00", "700"), ("#EF6C00", "800"), ("#E65100", "900"), ("#FFD180", "A100"), ("#FFAB40", "A200"), ("#FF9100", "A400"), ("#FF6D00", "A700") + ]) + ] + + for row_palettes in [first_row_palettes, second_row_palettes, third_row_palettes, fourth_row_palettes, fifth_row_palettes]: + row_layout = QHBoxLayout() + row_layout.setSpacing(15) + + for palette_name, colors in row_palettes: + column_layout = QVBoxLayout() + column_layout.setSpacing(3) + + palette_label = QLabel(palette_name) + palette_label.setStyleSheet("margin-bottom: 2px;") + palette_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + column_layout.addWidget(palette_label) + + color_buttons_layout = QHBoxLayout() + color_buttons_layout.setSpacing(3) + + for color_hex, color_name in colors: + color_btn = QPushButton() + color_btn.setFixedSize(18, 18) + + is_current = color_hex == self.current_theme_color + border_style = "2px solid #fff" if is_current else "none" + + color_btn.setStyleSheet(f""" + QPushButton {{ + background-color: {color_hex}; + border: {border_style}; + border-radius: 9px; + }} + QPushButton:hover {{ + border: 2px solid #fff; + }} + QPushButton:pressed {{ + border: 2px solid #fff; + }} + """) + color_btn.setCursor(Qt.CursorShape.PointingHandCursor) + color_btn.setToolTip(f"{palette_name} {color_name}\n{color_hex}") + color_btn.clicked.connect(lambda checked, color=color_hex, btn=color_btn: self.change_theme_color(color, btn)) + + self.color_buttons[color_hex] = color_btn + + color_buttons_layout.addWidget(color_btn) + + column_layout.addLayout(color_buttons_layout) + row_layout.addLayout(column_layout) + + grid_layout.addLayout(row_layout) + + theme_layout.addLayout(grid_layout) + theme_layout.addStretch() + + theme_tab.setLayout(theme_layout) + self.tab_widget.addTab(theme_tab, "Theme") + + def change_theme_color(self, color, clicked_btn=None): + if hasattr(self, 'color_buttons'): + for color_hex, btn in self.color_buttons.items(): + if color_hex == self.current_theme_color: + btn.setStyleSheet(f""" + QPushButton {{ + background-color: {color_hex}; + border: none; + border-radius: 9px; + }} + QPushButton:hover {{ + border: 2px solid #fff; + }} + QPushButton:pressed {{ + border: 2px solid #fff; + }} + """) + break + + self.current_theme_color = color + self.settings.setValue('theme_color', color) + self.settings.sync() + + if clicked_btn: + clicked_btn.setStyleSheet(f""" + QPushButton {{ + background-color: {color}; + border: 2px solid #fff; + border-radius: 9px; + }} + QPushButton:hover {{ + border: 2px solid #fff; + }} + QPushButton:pressed {{ + border: 2px solid #fff; + }} + """) + + qdarktheme.setup_theme( + custom_colors={ + "[dark]": { + "primary": color, + } + } + ) + def setup_about_tab(self): about_tab = QWidget() about_layout = QVBoxLayout() about_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) - about_layout.setSpacing(3) + about_layout.setSpacing(15) sections = [ - ("Check for Updates", "https://github.com/afkarxyz/SpotiFLAC/releases"), - ("Report an Issue", "https://github.com/afkarxyz/SpotiFLAC/issues") + ("Check for Updates", "Check", "https://github.com/afkarxyz/SpotiFLAC/releases"), + ("Report an Issue", "Report", "https://github.com/afkarxyz/SpotiFLAC/issues") ] - for title, url in sections: + for title, button_text, url in sections: section_widget = QWidget() section_layout = QVBoxLayout(section_widget) section_layout.setSpacing(10) @@ -1048,39 +1229,21 @@ class SpotiFLACGUI(QWidget): label.setAlignment(Qt.AlignmentFlag.AlignCenter) section_layout.addWidget(label) - button = QPushButton("Click Here!") - button.setFixedWidth(150) - button.setStyleSheet(""" - QPushButton { - background-color: palette(button); - color: palette(button-text); - border: 1px solid palette(mid); - padding: 6px; - border-radius: 15px; - } - QPushButton:hover { - background-color: palette(light); - } - QPushButton:pressed { - background-color: palette(midlight); - } - """) + button = QPushButton(button_text) + button.setFixedSize(120, 25) button.setCursor(Qt.CursorShape.PointingHandCursor) button.clicked.connect(lambda _, url=url: QDesktopServices.openUrl(QUrl(url if url.startswith(('http://', 'https://')) else f'https://{url}'))) section_layout.addWidget(button, alignment=Qt.AlignmentFlag.AlignCenter) about_layout.addWidget(section_widget) - - if sections.index((title, url)) < len(sections) - 1: - spacer = QSpacerItem(20, 6, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - about_layout.addItem(spacer) - footer_label = QLabel("v4.1 | July 2025") - footer_label.setStyleSheet("font-size: 12px; margin-top: 10px;") + footer_label = QLabel(f"v{self.current_version} | July 2025") + footer_label.setStyleSheet("font-size: 12px; margin-top: 20px;") about_layout.addWidget(footer_label, alignment=Qt.AlignmentFlag.AlignCenter) about_tab.setLayout(about_layout) - self.tab_widget.addTab(about_tab, "About") + self.tab_widget.addTab(about_tab, "About") + def on_service_changed(self, index): service = self.service_dropdown.currentData() self.service = service @@ -1281,8 +1444,7 @@ class SpotiFLACGUI(QWidget): 'artists': playlist_data["playlist_info"]["owner"]["display_name"], 'cover': playlist_data["playlist_info"]["owner"]["images"], 'followers': playlist_data["playlist_info"]["followers"]["total"], - 'total_tracks': playlist_data["playlist_info"]["tracks"]["total"] - } + 'total_tracks': playlist_data["playlist_info"]["tracks"]["total"] } self.update_display_after_fetch(metadata) def update_display_after_fetch(self, metadata): @@ -1364,21 +1526,30 @@ class SpotiFLACGUI(QWidget): def update_button_states(self): if self.is_single_track: - self.download_selected_btn.hide() - self.remove_btn.hide() - self.download_all_btn.setText('Download') - self.clear_btn.setText('Clear') + for btn in [self.download_selected_btn, self.download_all_btn, self.remove_btn, self.clear_btn]: + btn.hide() + + self.single_track_container.show() + + self.single_download_btn.setEnabled(True) + self.single_clear_btn.setEnabled(True) + else: + self.single_track_container.hide() + self.download_selected_btn.show() + self.download_all_btn.show() self.remove_btn.show() + self.clear_btn.show() + self.download_all_btn.setText('Download All') self.clear_btn.setText('Clear') - - self.download_all_btn.show() - self.clear_btn.show() - - self.download_selected_btn.setEnabled(True) - self.download_all_btn.setEnabled(True) + + self.download_all_btn.setMinimumWidth(120) + self.clear_btn.setMinimumWidth(120) + + self.download_selected_btn.setEnabled(True) + self.download_all_btn.setEnabled(True) def hide_track_buttons(self): buttons = [ @@ -1389,6 +1560,9 @@ class SpotiFLACGUI(QWidget): ] for btn in buttons: btn.hide() + + if hasattr(self, 'single_track_container'): + self.single_track_container.hide() def download_selected(self): if self.is_single_track: @@ -1426,8 +1600,7 @@ class SpotiFLACGUI(QWidget): try: self.start_download_worker(tracks_to_download, outpath) except Exception as e: - self.log_output.append(f"Error: An error occurred while starting the download: {str(e)}") - + self.log_output.append(f"Error: An error occurred while starting the download: {str(e)}") def start_download_worker(self, tracks_to_download, outpath): service = self.service_dropdown.currentData() qobuz_region = self.qobuz_region_dropdown.currentData() if service == "qobuz" else "us" @@ -1454,6 +1627,12 @@ class SpotiFLACGUI(QWidget): def update_ui_for_download_start(self): self.download_selected_btn.setEnabled(False) self.download_all_btn.setEnabled(False) + + if hasattr(self, 'single_download_btn'): + self.single_download_btn.setEnabled(False) + if hasattr(self, 'single_clear_btn'): + self.single_clear_btn.setEnabled(False) + self.stop_btn.show() self.pause_resume_btn.show() self.progress_bar.show() @@ -1482,7 +1661,6 @@ class SpotiFLACGUI(QWidget): self.log_output.append(message) else: self.log_output.append(message) - if percentage > 0 and not "Download progress:" in message: self.progress_bar.setValue(percentage) @@ -1502,6 +1680,11 @@ class SpotiFLACGUI(QWidget): self.download_selected_btn.setEnabled(True) self.download_all_btn.setEnabled(True) + if hasattr(self, 'single_download_btn'): + self.single_download_btn.setEnabled(True) + if hasattr(self, 'single_clear_btn'): + self.single_clear_btn.setEnabled(True) + if success: self.log_output.append(f"\nStatus: {message}") if failed_tracks: @@ -1558,6 +1741,31 @@ class SpotiFLACGUI(QWidget): self.timer.stop() self.time_label.hide() + def closeEvent(self, event): + if hasattr(self, 'timer'): + self.timer.stop() + + if hasattr(self, 'service_dropdown'): + for attr_name in ['tidal_status_checker', 'deezer_status_checker']: + if hasattr(self.service_dropdown, attr_name): + checker = getattr(self.service_dropdown, attr_name) + if checker.isRunning(): + checker.quit() + checker.wait() + + if hasattr(self, 'qobuz_region_dropdown'): + for checker in self.qobuz_region_dropdown.status_checkers.values(): + if checker.isRunning(): + checker.quit() + checker.wait() + + if hasattr(self, 'worker') and self.worker and self.worker.isRunning(): + self.worker.stop() + self.worker.quit() + self.worker.wait() + + event.accept() + if __name__ == '__main__': try: if sys.platform == "win32": @@ -1568,6 +1776,17 @@ if __name__ == '__main__': pass app = QApplication(sys.argv) + + settings = QSettings('SpotiFLAC', 'Settings') + theme_color = settings.value('theme_color', '#2196F3') + + qdarktheme.setup_theme( + custom_colors={ + "[dark]": { + "primary": theme_color, + } + } + ) ex = SpotiFLACGUI() ex.show() sys.exit(app.exec()) \ No newline at end of file diff --git a/deezer.png b/deezer.png new file mode 100644 index 0000000000000000000000000000000000000000..db9cdd1c94bd95713362734551d223e0e0cfa50f GIT binary patch literal 6504 zcmd^EXFD9w)@2Nah+y;>20=s(f?#CyUZPEiGNO0UqBBGnHG1!Th~6_w5WSb^L>Ija zdEMvz7x#WRXP>g4b@nNHeOM<}0d1;LYd-R_I!h5L2wRhqk47Rg| zoFo={m~ICPi-t)-T0+a+@Q;Ck6P-|;sz8N-ovm;{JIW=1k%7*7Xh2;khF)koe-JD$ zf+_&N6G1)yNO$^th+weDY-BL-H@MLO+0$bm$u!MXKDuwMgFsM_!FAS)#Ko(R8$zb0Eba$_y4?e*;2QMrN^c+3lW&s zrw$~IIB7zfckiq`6~ZrUWH!CPo@ZJA3dD(vI@j&4iyY27K145(j%A1tf^!eQrM|*8QdxzQkZuDtAHIs|gTwv_m6WD(49H{ld}h_lV{QgIQv2 zBZ9&o&L)arm~duEM3vz{$z#CGW?ajyj&{|f5HRYY0b|dNSWodJS#qY~^2d~Uxcu^Q zs3mA|ggz;^SWdifAYBXsAJbBkxEDatEe=D4UT#h$hIqTj&&$4NSg+f-{}FM2CoU9< z!f;DpLP983?Y=4G(+t?w!K~CB38m_&a zDc$(;o|4KrYn;6h;%_A_*`&+av=OglM4atjX~6!xkADHVvX(ql`jW}vgfITRD9ci2 z&$)z)d}PMEeU)dK|Cg&>oM!%8oBULh?4X8geP`%J%Gi6p7fyPnL0=L&QaPe96@|Ld zPC=<7Q-h8}+Yk2|IPFsPDlcV$<8pD{snXq$Vdt(*?d*NA&Zm^I3O*5T-kj<`rM@45 z2YexH(iY1@lUCr_hbN>@dI!b2+WD!Rab9P_|J3;i%@_4lhtAVv2*R5CsFIte=}}}L zXe@$K2AO7(ZM8NDD%JE2FfsLawL+1K}8{;dYQwiVTP{;?Ss z{%Aj7+kPe?-0q|9W$Y28=<#xDNaN`k$}Z&hhsC;g@2EmPAG<q7P0YTwH&FaR*;0_k zW`^KlT+pmkEwpUhX?UU?eDMBo$mdSO%5zQqyX(tS%c6#XW^FV6tg>K5zPt!V-lWKL z0zYT9e?5y7ufyjzFve*kLK$b7MX#&nlU7ql>U>93^;+*2JU$E<@rR8Z?tRjmka}+r zl{Yjk`{Fy5)XGD~Y^VsYJG@$bbN7~kQ+xtn_OWkl)M-M2E0@|v1>^(l^ zctqh&O=o|B-9cL$Q9xIn1`w4oY+>HX=5m!#D4O9>2{^JR8}Z7Kf9@1&lPYLzlliLc zTh&PK&eCV`X3oBX^uP${(Y)6UJNwO;_(&O9c&3%gtt2+xkiF2b?{Ia6-bDEQ&6S4k zki#p>luNla-J+gpI!GMeoU>c;Lq2&$=!kC3b%MvzEcu$ zKy;DxHPg!J1MQ9BhIVtaJ+5z3Otti+iQB1;W8dt;D_IY_QI58({<~S}MX0;;bIx|; zBDm$oeBv+V7|wgAZetlc2bcmtDDdBH=aS@zB@RlQO{FqDw@U;VMia~8CieDBnG{C9 zwghvzZ$`%%`jtJoxUm1Qq3MGaONW{tHZWMBIW>tFkZIN+Vt?>da&8O-cPxFWqA~zPK5mn0bW1K&(c7B5v&d<=yS=#E7Zd-G1WxNgE zCdrGb5p2Kc-uSY4E(#!2G`_=fsko&nYwf$Mkbym%v$Fl@8N|zIeuFNBC``u32Cj}R z4OPlbeL0Sv$0erTrSU$A;A6O))L_Xv7n||cUfJihi5YRSq8wuVMm4Kn!vSH2DxrC% z>F{1Z#NjQic}zp;Fw@6yQu{SbD&D+M@NhTQ%gXa#Jz0~)3rc;7Q)>N6BB3AT1^eA^ zHk}MNgiU*@h3j#h&z5X?Hzl2R?d(_{sBEUjO0hk~`jk_>^Q~zY`|e?V4dPUjkJFed zTsZZ|H&TaTVUcGPe`9G;b|V0xVGhC08uHhHl0FCp;=!~ z_MI*UQ7FBj@}BRG*< z7RH6IOsPad$mXs5Y<;QuOD&%$r^c{KA*btPaP~?6kg-ZE^x$`SYqr%pSkBC{q;j?PnC$)5Tu%QxFkM`+gsv%Fs;G8EZK;UYkDU#}uQ zmbSP#mP$n!U|PqAU`xBEs5xx7I@qbFnz5x!b?#u}6M@QuC!Se(hw2r}I0^V2g7)pH z2aBx_VpUH{Dw1C|jb0={nHL{KCuQ>YMxWzL{YU+nQJe;s-72j-Vw!8nk&iI|3kA|xr-9<*w_aJ>LTM|Y-@z3Qx_Fy0$MFEK;3on~e@mJe=w>W}=-$3)pt~)q#esb*-H^3F(d@!eZBPqoP z+-&786@5Z?u-|wZ>clLQooO8C{B%4a_&J{UPZIZ?uzIuEVps_d%A{B+#YY5IAg$42 z&r-!wQkKRIbxbi0e_1MmvTLVS)%a@=#aIEEJt_Wf-|UZR(TcCWEd|fIeO1~acHfy* zv9jhwC_E+jV+$`5;O>hP1Pt-pEt^Dr6&w9YI$K?4j~nC6y*Qx*kxv2uhDI7LURP`M z6J*e#zQAE+M)MX>S3*K8niJVTyV<3RO_g}lf@R@U8up=M z+Y_}LZT4T4N*Nj;GZut~#R_3-qW{VCWfIh}I=@e$iVMI~De#`Iv?OIj;Q;e~#j08F zxrl&+f0~|gi(9`!1k{EO*o+Q}d}E%wBUSgPXwyg44Id4B|C;PRKU6i^VmH^$vp2DY zJt{nD(Pp^~-2`UMWsslX>E%E7373CnA=%>JP2lBh^~6jr49>Un(R*|x4KrBz`U)#h zWE~FGo=A@HT>@tAZkZ_k>&$rfb)7LL*CK`O+O?#dU{DdT5`fnqMT#<|-@5hBdWEt> zZHfD54>yv?@7Ps7Inf0t#{)6=Pb*$*p@x(;xmFh4 ziES^&4^wDu04eO)Fg$mWLNAfd)AxCb|AjaOHp1rUGW!U zyCM}S&z+_7Lm}=8D<$HZ($RMD+2lTxsl>J$M_#@l$1NR>o4s`Hs`;u^rW?%KxrKKMVv??0>8m*i^U&@v4!rCjUlSTleaW| z^22-&tcs{lo1TMz5f$#`X_fud_o64gT2+d7*_*=Q_v9@9&^jV>fZ=d(L7=zK`)%e+ zCbTu>r^DcXtdP-CHN}e!jgZFF1#{clCPU$Nt&qB92EMigq%tc=APcWPJt$({CxYeZ z1>Zn=;6ZZD*r9aMdF4|kCl0WVNa$Q-x!&+Mg|qA(<1}Jnfee?i+}2T*{GpVqGf9KG zA*@U&YekZM=egM9{R!*_)p1S(0n{zD7u+pCF)mj+jtiiBTW zWE^XO!EC>@waK`R;7%e&E^Xyi6>0e8N!7mwZ zK(}^{aVEoN;iyHug>>!n^EK9H%*MSBGlwG#S5^rb80Ml}`#K`FkYl)}Cq`kQ@nA0L zi8Y6Ux8jwUVxM?xj!VDG) zFVlru78sM_=EFso%$weVXZ;x|V(WZ|-He!ro28!4jJz(ou~2dOT~=G?Dbpj=vH}3` zJ6q{W7%a-%pUL@DT`4n^Z^*sNZa#<=X^wpDZ*n#xPl2Vu>QInAZtkHVR;e)uTIz_t z`D^21awb|e>T?v#e(6@~s?Z%z8*xmkWv4)ApxAG>5pldtVC!GA@%Dp%%f|#ppYMa) zZ|<1BP)X(JzU}$zpCdj8J#tmuL37e`e$X0DDdja3vFn;9Y6J=9$X95G9t~l!U60ZzViEPdC{4bwiiS0ry z#P=cBYl09rmKU64!UNgx6+F29>C7hXcKGPNY%Q^zSt~j{h{|rKKf?SAqkDbaP^fdBTu2m9On^Cp3Y*L%kypu z$EMiz#Nhjk+wB$aQk9;l`sms&mxE-Z2c0E(!{Z9vreEYY=2Ae>R{f_{+qqG(89UMB zn^wVlx-g4~`7@%eB{vyd6qjfcBi|j*Vvb)?Qv_Lt*AfR?YpiF)v}@4hRCdSTGBuZ@ zTf3X>fTjk28Uzr9pDHHC(TeT1ugj08@y)CTdgQ;|32vLLDvMV-AM)poZnh$E$LxG+ zT;7joGS9tgny#%R|8v{HK#gE-`;*G%F2FdRO56$rm7Dg-g8ifo5V50n+$f+DnGpuFs zQ9vhBkG%4x`?u0PpZ)&KgXrG1_Z{z|Fk9zkXcX=Iq?j zk5wA}!Jsy9)09|9W^uWqtC!|p>-006iZ}Jw?zDh9P#ZbCQQB~%97aFlR@PK|H3VSZ zSk||9l5qXB3Ir2v(&A~uh0AaNpj8yPRRCfhJ3-M>v#QX9!g@uVy%x}y>y|cSy{^i~ zfYWa$+&d-#(;07Gj!#b4$b0Hy3;*-ni%^pJ&%z_s^gQu`ak%bjRhcKL)}a%5|2LrTO)=Q$Q}~k4LDfTXi`cK&Tfx=({F-( zMCzP3tW$kcBJOmJb}%B$K(3BrXt7D$<@%2<+I{7kMF-?mA}jRYzl{4`hl>s74>fEiI7COlQz*AZisF@zik;y z&W?4?c9jkG3E)veImjQQ6Z-p6>%9)c{-3cHvtN2D2!R}`^kidv9>hh{tcVWxLIXY?!h-D`i2uMNrsc>CCr~a6i7+$d zC^XoK9-^@#EJaxd0MSwSHVij?`e|QbpID#aC-qC#zykeVl^2a+O%Qawd5u(Z?M*`V+bxUrYpGd-?cY z^7DSD@a@fdrlJaoa6$?wri;G{$6^_4P_u*(P{aqub}APeXGmoCboXl`ICEFY)*^h^ zHTz(IKM{A~*$0V`J|h+^Pk@R)hCn&sD=4ycq82Iw%P8S55g$|4e~`l)uOcpeeU08s z&df0h4Oyum6vy(`Q<_N*$sfx~VF9D??i?YN;(b2S(tdguxjRWezK_lAJZQIQL@nKe zj4q6~yA1|`7_k74u*1y#;Y1IR)WvyfQ0QkY5GU|iDmLKd!5r`v3p{ literal 0 HcmV?d00001 diff --git a/deezerDL.py b/deezerDL.py index 305016d..4d0d22b 100644 --- a/deezerDL.py +++ b/deezerDL.py @@ -170,27 +170,22 @@ class DeezerDownloader: print("Downloading FLAC file...") try: - response = self.session.get(flac_url, stream=True) + response = self.session.get(flac_url) response.raise_for_status() - total_size = int(response.headers.get('content-length', 0)) - print(f"File size: {total_size} bytes ({total_size / (1024*1024):.2f} MB)") - safe_title = "".join(c for c in metadata.get('title', 'Unknown') if c.isalnum() or c in (' ', '-', '_')).rstrip() safe_artist = "".join(c for c in metadata.get('artists', 'Unknown') if c.isalnum() or c in (' ', '-', '_')).rstrip() filename = f"{safe_artist} - {safe_title}.flac" file_path = os.path.join(output_dir, filename) - downloaded = 0 with open(file_path, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - downloaded += len(chunk) - if self.progress_callback and total_size > 0: - current_mb = downloaded / (1024 * 1024) - total_mb = total_size / (1024 * 1024) - percent = (downloaded / total_size) * 100 - self.progress_callback(downloaded, total_size) + f.write(response.content) + + downloaded = len(response.content) + print(f"File size: {downloaded} bytes ({downloaded / (1024*1024):.2f} MB)") + + if self.progress_callback: + self.progress_callback(downloaded, downloaded) print(f"Downloaded: {file_path}") @@ -214,19 +209,29 @@ class DeezerDownloader: return False async def main(): - if len(sys.argv) != 2: - print("Usage: python deezerDL.py ") - print("Example: python deezerDL.py USUM72409273") - return - - isrc = sys.argv[1] + print("=== DeezerDL - Deezer Downloader ===") downloader = DeezerDownloader() - success = await downloader.download_by_isrc(isrc) + isrc = "USAT22409172" + output_dir = "." + + success = await downloader.download_by_isrc(isrc, output_dir) if success: print("Download completed successfully!") else: print("Download failed!") if __name__ == "__main__": + try: + import sys + if sys.platform == "win32": + import os + os.system("chcp 65001 > nul") + try: + sys.stdout.reconfigure(encoding='utf-8') + except: + pass + except: + pass + asyncio.run(main()) \ No newline at end of file diff --git a/eu.svg b/eu.svg new file mode 100644 index 0000000..b0874c1 --- /dev/null +++ b/eu.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8686c1648ed35babc7dedd6c8dada32adee2e183 GIT binary patch literal 172823 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
    9Y2IE^?qrk*&*asY5seKT!}V~13Cbl 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 D+yqex literal 0 HcmV?d00001 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..455ce78 --- /dev/null +++ b/icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qobuz.png b/qobuz.png new file mode 100644 index 0000000000000000000000000000000000000000..a66fe4b7492951e8909c396f770966b8d277b167 GIT binary patch literal 42467 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�yj1d_#O(=srGKYfa9ZExrF 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

    Di0Rt*S}H^Fx)2JiQDuKx-%PPMTq+c`d@(9l6k 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 l;%((m-aL7mtNVP7-rbINW6i(l#}xcW= 1: - if total_size > 0: - progress_percent = (downloaded_size / total_size) * 100 - elapsed_time = current_time - start_time - speed = downloaded_size / (1024 * 1024 * elapsed_time) if elapsed_time > 0 else 0 - print(f"{progress_percent:.2f}% - {speed:.2f} MB/s") - else: - print(f"{downloaded_size / (1024 * 1024):.2f} MB") - - last_update_time = current_time - - if self.progress_callback: - self.progress_callback(downloaded_size, total_size) + while is_paused_callback and is_paused_callback(): + time.sleep(0.1) + if is_stopped_callback and is_stopped_callback(): + raise Exception("Download stopped") + + with open(temp_filename, 'wb') as f: + f.write(response.content) + + downloaded_size = len(response.content) + total_size = downloaded_size + + if self.progress_callback: + self.progress_callback(downloaded_size, total_size) os.rename(temp_filename, output_filename) print("Download complete") diff --git a/tidal.png b/tidal.png new file mode 100644 index 0000000000000000000000000000000000000000..8d37f2b6de5fa8ab12d0d31181b8493b1964c08b GIT binary patch literal 6252 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 A0RR91 literal 0 HcmV?d00001 diff --git a/tidalDL.py b/tidalDL.py index b90aa1e..0094001 100644 --- a/tidalDL.py +++ b/tidalDL.py @@ -3,7 +3,7 @@ import json import os import re import time -import httpx +import requests from mutagen.flac import FLAC, Picture from mutagen.id3 import PictureType @@ -35,7 +35,7 @@ class TidalDownloader: sanitized = re.sub(r'[\\/*?:"<>|]', "", str(filename)) return re.sub(r'\s+', ' ', sanitized).strip() or "Unnamed Track" - async def get_access_token(self): + def get_access_token(self): refresh_url = "https://auth.tidal.com/v1/oauth2/token" payload = { @@ -43,68 +43,67 @@ class TidalDownloader: "grant_type": "client_credentials", } - async with httpx.AsyncClient(http2=True) as client: - try: - response = await client.post( - url=refresh_url, - data=payload, - auth=(self.client_id, self.client_secret), - ) - - if response.status_code == 200: - token_data = response.json() - return token_data.get("access_token") - else: - return None - - except: - return None - - async def search_tracks(self, query): try: - tidal_token = await self.get_access_token() + response = requests.post( + url=refresh_url, + data=payload, + auth=(self.client_id, self.client_secret), + timeout=self.timeout + ) + + if response.status_code == 200: + token_data = response.json() + return token_data.get("access_token") + else: + return None + + except: + return None + + def search_tracks(self, query): + try: + tidal_token = self.get_access_token() if not tidal_token: raise Exception("Failed to get access token") search_url = f"https://api.tidal.com/v1/search/tracks?query={query}&limit=25&offset=0&countryCode=US" header = {"authorization": f"Bearer {tidal_token}"} - async with httpx.AsyncClient(http2=True) as client: - search_data = await client.get(url=search_url, headers=header) - response_data = search_data.json() - - filtered_items = [{ - "id": item.get("id"), - "title": item.get("title"), - "url": item.get("url"), - "isrc": item.get("isrc"), - "audioQuality": item.get("audioQuality"), - "mediaMetadata": item.get("mediaMetadata"), - "album": item.get("album", {}), - "artists": item.get("artists", []), - "artist": item.get("artist", {}), - "trackNumber": item.get("trackNumber"), - "volumeNumber": item.get("volumeNumber"), - "duration": item.get("duration"), - "copyright": item.get("copyright"), - "explicit": item.get("explicit") - } for item in response_data.get("items", [])] - - return { - "limit": response_data.get("limit"), - "offset": response_data.get("offset"), - "totalNumberOfItems": response_data.get("totalNumberOfItems"), - "items": filtered_items - } + search_data = requests.get(url=search_url, headers=header, timeout=self.timeout) + response_data = search_data.json() + + filtered_items = [{ + "id": item.get("id"), + "title": item.get("title"), + "url": item.get("url"), + "isrc": item.get("isrc"), + "audioQuality": item.get("audioQuality"), + "mediaMetadata": item.get("mediaMetadata"), + "album": item.get("album", {}), + "artists": item.get("artists", []), + "artist": item.get("artist", {}), + "trackNumber": item.get("trackNumber"), + "volumeNumber": item.get("volumeNumber"), + "duration": item.get("duration"), + "copyright": item.get("copyright"), + "explicit": item.get("explicit") + } for item in response_data.get("items", [])] + + return { + "limit": response_data.get("limit"), + "offset": response_data.get("offset"), + "totalNumberOfItems": response_data.get("totalNumberOfItems"), + "items": filtered_items + } except Exception as e: raise Exception(f"Search error: {str(e)}") - async def get_track_info(self, query, isrc=None): + def get_track_info(self, query, isrc=None): print(f"Fetching: {query}" + (f" (ISRC: {isrc})" if isrc else "")) try: - result = await self.search_tracks(query) + result = self.search_tracks(query) if not result or not result.get("items"): raise Exception(f"No tracks found for query: {query}") @@ -143,99 +142,73 @@ class TidalDownloader: except Exception as e: raise Exception(f"Error getting track info: {str(e)}") - async def get_download_url(self, track_id, quality="LOSSLESS"): + def get_download_url(self, track_id, quality="LOSSLESS"): print("Fetching URL...") download_api_url = f"https://hifi.401658.xyz/track/?id={track_id}&quality={quality}" - async with httpx.AsyncClient(http2=True, timeout=self.timeout) as client: - try: - response = await client.get(download_api_url) + try: + response = requests.get(download_api_url, timeout=self.timeout) + + if response.status_code == 200: + data = response.json() - if response.status_code == 200: - data = response.json() - - for item in data: - if "OriginalTrackUrl" in item: - print("URL found") - return { - "download_url": item["OriginalTrackUrl"], - "track_info": data[0] if data else {} - } - - raise Exception("Download URL not found in response") - else: - raise Exception(f"API returned status code: {response.status_code}") - - except Exception as e: - raise Exception(f"Error getting download URL: {str(e)}") + for item in data: + if "OriginalTrackUrl" in item: + print("URL found") + return { + "download_url": item["OriginalTrackUrl"], + "track_info": data[0] if data else {} + } + + raise Exception("Download URL not found in response") + else: + raise Exception(f"API returned status code: {response.status_code}") + + except Exception as e: + raise Exception(f"Error getting download URL: {str(e)}") - async def download_album_art(self, album_id, size="1280x1280"): + def download_album_art(self, album_id, size="1280x1280"): try: art_url = f"https://resources.tidal.com/images/{album_id.replace('-', '/')}/{size}.jpg" - async with httpx.AsyncClient(http2=True, timeout=self.timeout) as client: - response = await client.get(art_url) + response = requests.get(art_url, timeout=self.timeout) + + if response.status_code == 200: + return response.content + else: + print(f"Failed to download album art: HTTP {response.status_code}") + return None - if response.status_code == 200: - return response.content - else: - print(f"Failed to download album art: HTTP {response.status_code}") - return None - except Exception as e: print(f"Error downloading album art: {str(e)}") return None - async def download_file(self, url, filepath, is_paused_callback=None, is_stopped_callback=None): + def download_file(self, url, filepath, is_paused_callback=None, is_stopped_callback=None): temp_filepath = filepath + ".part" retry_count = 0 while retry_count <= self.max_retries: try: - async with httpx.AsyncClient(http2=True, timeout=60.0) as client: - async with client.stream('GET', url) as response: - if response.status_code != 200: - raise Exception(f"HTTP {response.status_code}") - - total_size = int(response.headers.get('content-length', 0)) - downloaded_size = 0 - start_time = time.time() - last_update_time = start_time - - with open(temp_filepath, 'wb') as f: - async for chunk in response.aiter_bytes(chunk_size=self.download_chunk_size): - if is_stopped_callback and is_stopped_callback(): - f.close() - if os.path.exists(temp_filepath): - os.remove(temp_filepath) - raise Exception("Download stopped") - - while is_paused_callback and is_paused_callback(): - await asyncio.sleep(0.1) - if is_stopped_callback and is_stopped_callback(): - f.close() - if os.path.exists(temp_filepath): - os.remove(temp_filepath) - raise Exception("Download stopped") - - f.write(chunk) - downloaded_size += len(chunk) - - current_time = time.time() - if current_time - last_update_time >= 1: - if total_size > 0: - progress_percent = (downloaded_size / total_size) * 100 - elapsed_time = current_time - start_time - speed = downloaded_size / (1024 * 1024 * elapsed_time) if elapsed_time > 0 else 0 - print(f"{progress_percent:.2f}% - {speed:.2f} MB/s") - else: - print(f"{downloaded_size / (1024 * 1024):.2f} MB") - - last_update_time = current_time - - if self.progress_callback: - self.progress_callback(downloaded_size, total_size) - + response = requests.get(url, timeout=60.0) + if response.status_code != 200: + raise Exception(f"HTTP {response.status_code}") + + if is_stopped_callback and is_stopped_callback(): + raise Exception("Download stopped") + + while is_paused_callback and is_paused_callback(): + time.sleep(0.1) + if is_stopped_callback and is_stopped_callback(): + raise Exception("Download stopped") + + with open(temp_filepath, 'wb') as f: + f.write(response.content) + + downloaded_size = len(response.content) + + if self.progress_callback: + self.progress_callback(downloaded_size, downloaded_size) + os.rename(temp_filepath, filepath) print("Download complete") return {"success": True, "size": downloaded_size} @@ -252,9 +225,9 @@ class TidalDownloader: print(f"Download error (attempt {retry_count}/{self.max_retries}): {str(e)}") print(f"Retrying in {retry_count * 2} seconds...") - await asyncio.sleep(retry_count * 2) + time.sleep(retry_count * 2) - async def embed_metadata(self, filepath, track_info, search_info=None): + def embed_metadata(self, filepath, track_info, search_info=None): try: print("Embedding metadata...") audio = FLAC(filepath) @@ -325,7 +298,7 @@ class TidalDownloader: audio["COMMENT"] = f"Tidal {track_info['audioQuality']}" if album_info.get("cover"): - album_art = await self.download_album_art(album_info["cover"]) + album_art = self.download_album_art(album_info["cover"]) if album_art: picture = Picture() picture.data = album_art @@ -343,14 +316,14 @@ class TidalDownloader: print(f"Error embedding metadata: {str(e)}") return False - async def download(self, query, isrc=None, output_dir=".", quality="LOSSLESS", is_paused_callback=None, is_stopped_callback=None): + def download(self, query, isrc=None, output_dir=".", quality="LOSSLESS", is_paused_callback=None, is_stopped_callback=None): if output_dir != ".": try: os.makedirs(output_dir, exist_ok=True) except OSError as e: raise Exception(f"Directory error: {e}") - track_info = await self.get_track_info(query, isrc) + track_info = self.get_track_info(query, isrc) track_id = track_info.get("id") if not track_id: @@ -376,12 +349,12 @@ class TidalDownloader: print(f"File already exists: {output_filename} ({file_size / (1024 * 1024):.2f} MB)") return output_filename - download_info = await self.get_download_url(track_id, quality) + download_info = self.get_download_url(track_id, quality) download_url = download_info["download_url"] download_track_info = download_info["track_info"] print(f"Downloading to: {output_filename}") - await self.download_file( + self.download_file( download_url, output_filename, is_paused_callback=is_paused_callback, @@ -390,7 +363,7 @@ class TidalDownloader: print("Adding metadata...") try: - await self.embed_metadata(output_filename, download_track_info, track_info) + self.embed_metadata(output_filename, download_track_info, track_info) print("Metadata saved") except Exception as e: print(f"Tagging failed: {e}") @@ -398,7 +371,7 @@ class TidalDownloader: print("Done") return output_filename -async def main(): +def main(): print("=== TidalDL - Tidal Downloader ===") downloader = TidalDownloader(timeout=30, max_retries=3) @@ -407,7 +380,7 @@ async def main(): output_dir = "." try: - downloaded_file = await downloader.download(query, isrc, output_dir) + downloaded_file = downloader.download(query, isrc, output_dir) print(f"Success: File saved as {downloaded_file}") except Exception as e: print(f"Error: {str(e)}") @@ -425,4 +398,4 @@ if __name__ == "__main__": except: pass - asyncio.run(main()) \ No newline at end of file + main() \ No newline at end of file diff --git a/us.svg b/us.svg new file mode 100644 index 0000000..9cfd0c9 --- /dev/null +++ b/us.svg @@ -0,0 +1,9 @@ + + + + + + + + +