This commit is contained in:
afkarxyz
2025-05-11 18:31:28 +07:00
parent 60d20cbebe
commit 9b6b43c0a4
2 changed files with 92 additions and 103 deletions
+59 -82
View File
@@ -6,6 +6,7 @@ import requests
import re
from packaging import version
import json
import asyncio
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit,
@@ -17,7 +18,7 @@ from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl, QTimer, QTime, QSettings
from PyQt6.QtGui import QIcon, QTextCursor, QDesktopServices, QPixmap, QBrush
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from getMetadata import get_filtered_data, parse_uri, SpotifyInvalidUrlException
from getMetadata import get_filtered_data, parse_uri, SpotifyInvalidUrlException, get_raw_spotify_data
from getTracks import TrackDownloader
import SquidWTF
@@ -88,17 +89,15 @@ class DownloadWorker(QThread):
try:
downloader = TrackDownloader(self.use_fallback, self.timeout)
def progress_update(current, total):
def progress_update_lucida(current, total, current_overall_progress):
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))
current_overall_progress)
else:
self.progress.emit(f"Processing metadata...", 0)
downloader.set_progress_callback(progress_update)
self.progress.emit(f"Processing metadata...", current_overall_progress)
total_tracks = len(self.tracks)
@@ -110,12 +109,15 @@ class DownloadWorker(QThread):
if self.is_stopped:
return
current_overall_progress = int((i / total_tracks) * 100)
next_overall_progress = int(((i + 1) / total_tracks) * 100)
self.progress.emit(f"Starting download ({i+1}/{total_tracks}): {track.title} - {track.artists}",
int((i) / total_tracks * 100))
current_overall_progress)
try:
track_id = track.id
self.progress.emit(f"Getting track info for ID: {track_id} from {self.service}", 0)
self.progress.emit(f"Getting track info for ID: {track_id} from {self.service}", current_overall_progress)
if self.is_playlist and self.use_album_subfolders:
album_folder = re.sub(r'[<>:"/\\|?*]', '_', track.album)
@@ -125,83 +127,68 @@ class DownloadWorker(QThread):
track_outpath = self.outpath
if self.service == "qobuz":
self.progress.emit(f"Getting track metadata for: {track.title} - {track.artists}", 0)
self.progress.emit(f"Getting track metadata for: {track.title} - {track.artists}", current_overall_progress)
isrc = None
try:
from getMetadata import get_raw_spotify_data, parse_uri
track_url = track.external_urls
self.progress.emit(f"Fetching Spotify metadata for ISRC...", 0)
self.progress.emit(f"Fetching Spotify metadata for ISRC...", current_overall_progress)
raw_data = get_raw_spotify_data(track_url)
if raw_data and "external_ids" in raw_data and "isrc" in raw_data["external_ids"]:
isrc = raw_data["external_ids"]["isrc"]
self.progress.emit(f"Found ISRC from Spotify: {isrc}", 0)
self.progress.emit(f"Found ISRC from Spotify: {isrc}", current_overall_progress)
except Exception as e:
self.progress.emit(f"Could not get ISRC from Spotify raw data: {str(e)}", 0)
self.progress.emit(f"Could not get ISRC from Spotify raw data: {str(e)}", current_overall_progress)
if not isrc:
self.progress.emit(f"No ISRC found, searching by title and artist", 0)
self.progress.emit(f"No ISRC found, searching by title and artist", current_overall_progress)
search_query = f"{track.title} {track.artists}"
try:
self.progress.emit(f"Searching Qobuz for: {search_query}", 0)
self.progress.emit(f"Searching Qobuz for: {search_query}", current_overall_progress)
qobuz_track_info = SquidWTF.search_track(track.title, track.artists, strict_match=True, region=self.qobuz_region)
if qobuz_track_info:
qobuz_track_id = qobuz_track_info["id"]
self.progress.emit(f"Found track on Qobuz by title search: {qobuz_track_info['title']} - {qobuz_track_info['performer']['name']}", 0)
self.progress.emit(f"Found track on Qobuz by title search: {qobuz_track_info['title']} - {qobuz_track_info['performer']['name']}", current_overall_progress)
found_artist = qobuz_track_info['performer']['name'].lower()
expected_artist = track.artists.lower()
if expected_artist not in found_artist and found_artist not in expected_artist:
self.progress.emit(f"Warning: Artist mismatch! Expected: {track.artists}, Found: {qobuz_track_info['performer']['name']}", 0)
self.progress.emit(f"Warning: Artist mismatch! Expected: {track.artists}, Found: {qobuz_track_info['performer']['name']}", current_overall_progress)
raise Exception(f"Artist mismatch: Expected '{track.artists}', found '{qobuz_track_info['performer']['name']}'")
else:
raise Exception(f"Could not find track on Qobuz: {track.title} - {track.artists}")
except Exception as e:
self.progress.emit(f"Search by title failed: {str(e)}", 0)
self.progress.emit(f"Search by title failed: {str(e)}", current_overall_progress)
raise Exception(f"Could not find track on Qobuz: {track.title} - {track.artists}")
else:
self.progress.emit(f"Searching Qobuz with ISRC: {isrc}", 0)
self.progress.emit(f"Searching Qobuz with ISRC: {isrc}", current_overall_progress)
qobuz_track_info = SquidWTF.get_track_info(isrc, region=self.qobuz_region)
qobuz_track_id = qobuz_track_info["id"]
self.progress.emit(f"Found track on Qobuz: {qobuz_track_info['title']} - {qobuz_track_info['performer']['name']}", 0)
self.progress.emit(f"Found track on Qobuz: {qobuz_track_info['title']} - {qobuz_track_info['performer']['name']}", current_overall_progress)
found_artist = qobuz_track_info['performer']['name'].lower()
expected_artist = track.artists.lower()
if expected_artist not in found_artist and found_artist not in expected_artist:
self.progress.emit(f"Warning: Artist mismatch! Expected: {track.artists}, Found: {qobuz_track_info['performer']['name']}", 0)
self.progress.emit(f"Warning: Artist mismatch! Expected: {track.artists}, Found: {qobuz_track_info['performer']['name']}", current_overall_progress)
download_url = SquidWTF.get_download_url(qobuz_track_id, region=self.qobuz_region)
os.makedirs(track_outpath, exist_ok=True)
temp_filename = os.path.join(track_outpath, f"temp_{qobuz_track_id}.flac")
self.progress.emit(f"Downloading from Qobuz...", current_overall_progress)
self.progress.emit(f"Downloading from Qobuz...", 0)
def progress_callback(current, total):
def progress_callback_qobuz(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))
current_overall_progress)
try:
SquidWTF.download_file(download_url, temp_filename, progress_callback)
SquidWTF.download_file(download_url, temp_filename, progress_callback_qobuz)
if not os.path.exists(temp_filename) or os.path.getsize(temp_filename) == 0:
raise Exception(f"Downloaded file is empty or does not exist: {temp_filename}")
self.progress.emit(f"Embedding metadata...", 0)
self.progress.emit(f"Embedding metadata...", current_overall_progress)
SquidWTF.embed_metadata(temp_filename, qobuz_track_info)
if (self.is_album or (self.is_playlist and self.use_album_subfolders)) and self.use_track_numbers:
@@ -217,25 +204,28 @@ class DownloadWorker(QThread):
os.rename(temp_filename, new_filepath)
downloaded_file = new_filepath
self.progress.emit(f"File renamed to: {new_filename}", 0)
self.progress.emit(f"File renamed to: {new_filename}", current_overall_progress)
except Exception as e:
self.progress.emit(f"Error during download or processing: {str(e)}", 0)
self.progress.emit(f"Error during download or processing: {str(e)}", current_overall_progress)
if os.path.exists(temp_filename):
try:
os.remove(temp_filename)
self.progress.emit(f"Removed incomplete download file", 0)
self.progress.emit(f"Removed incomplete download file", current_overall_progress)
except:
pass
raise Exception(f"Failed to download or process file: {str(e)}")
else:
import asyncio
metadata = asyncio.run(downloader.get_track_info(track_id, self.service))
self.progress.emit(f"Track info received, starting download process", 0)
self.progress.emit(f"Track info received, starting download process", current_overall_progress)
is_paused_callback = lambda: self.is_paused
is_stopped_callback = lambda: self.is_stopped
downloader.set_progress_callback(
lambda current, total: progress_update_lucida(current, total, current_overall_progress)
)
downloaded_file = downloader.download(
metadata,
track_outpath,
@@ -255,14 +245,14 @@ class DownloadWorker(QThread):
if os.path.exists(new_filepath):
os.remove(new_filepath)
os.rename(downloaded_file, new_filepath)
self.progress.emit(f"File renamed to: {new_filename}", 0)
self.progress.emit(f"File renamed to: {new_filename}", current_overall_progress)
self.progress.emit(f"Successfully downloaded: {track.title} - {track.artists}",
int((i + 1) / total_tracks * 100))
next_overall_progress)
except Exception as e:
self.failed_tracks.append((track.title, track.artists, str(e)))
self.progress.emit(f"Failed to download: {track.title} - {track.artists}\nError: {str(e)}",
int((i + 1) / total_tracks * 100))
next_overall_progress)
continue
if not self.is_stopped:
@@ -344,17 +334,14 @@ class ServiceStatusChecker(QThread):
services_status['tidal'] = current_services.get('tidal', 0) > 0
services_status['deezer'] = current_services.get('deezer', 0) > 0
print("Lucida services status check successful")
except json.JSONDecodeError as e:
print(f"Lucida API returned invalid JSON: {e}")
except Exception as e:
print(f"Error processing Lucida API response: {str(e)}")
else:
print(f"Lucida API returned status code: {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"Error connecting to Lucida API: {str(e)}")
except Exception as e:
print(f"Unexpected error checking Lucida services: {str(e)}")
except json.JSONDecodeError:
pass
except Exception:
pass
except requests.exceptions.RequestException:
pass
except Exception:
pass
eu_online = False
us_online = False
@@ -362,16 +349,14 @@ class ServiceStatusChecker(QThread):
try:
eu_response = requests.get("https://eu.qobuz.squid.wtf", timeout=5)
eu_online = eu_response.status_code in [200, 304]
print(f"SquidWTF (Qobuz EU) status check: {eu_response.status_code} - {'Online' if eu_online else 'Offline'}")
except Exception as e:
print(f"Error checking EU Qobuz region: {str(e)}")
except Exception:
pass
try:
us_response = requests.get("https://us.qobuz.squid.wtf", timeout=5)
us_online = us_response.status_code in [200, 304]
print(f"SquidWTF (Qobuz US) status check: {us_response.status_code} - {'Online' if us_online else 'Offline'}")
except Exception as e:
print(f"Error checking US Qobuz region: {str(e)}")
except Exception:
pass
services_status['qobuz'] = eu_online or us_online
@@ -520,12 +505,9 @@ class QobuzRegionComboBox(QComboBox):
try:
response = requests.get(region['url'], timeout=5)
regions_status[region['id']] = response.status_code in [200, 304]
print(f"SquidWTF ({region['name']}) status check: {response.status_code} - {'Online' if regions_status[region['id']] else 'Offline'}")
except requests.exceptions.RequestException as e:
print(f"Error connecting to SquidWTF API ({region['name']}): {str(e)}")
except requests.exceptions.RequestException:
regions_status[region['id']] = False
except Exception as e:
print(f"Unexpected error checking SquidWTF ({region['name']}): {str(e)}")
except Exception:
regions_status[region['id']] = False
self.regions_status = regions_status
@@ -601,8 +583,8 @@ class SpotiFLACGUI(QWidget):
if result == QDialog.DialogCode.Accepted:
QDesktopServices.openUrl(QUrl("https://github.com/afkarxyz/SpotiFLAC/releases"))
except Exception as e:
print(f"Error checking for updates: {e}")
except Exception:
pass
@staticmethod
def format_duration(ms):
@@ -1076,6 +1058,7 @@ class SpotiFLACGUI(QWidget):
def save_qobuz_region_setting(self):
region = self.qobuz_region_dropdown.currentData()
self.qobuz_region = region
self.settings.setValue('qobuz_region', region)
self.settings.sync()
self.log_output.append(f"Qobuz region setting saved: {self.qobuz_region_dropdown.currentText()}")
@@ -1355,7 +1338,7 @@ class SpotiFLACGUI(QWidget):
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"
qobuz_region_val = self.qobuz_region_dropdown.currentData() if service == "qobuz" else self.qobuz_region
self.worker = DownloadWorker(
tracks_to_download,
@@ -1370,7 +1353,7 @@ class SpotiFLACGUI(QWidget):
self.use_fallback,
service,
self.timeout_value,
qobuz_region=qobuz_region
qobuz_region=qobuz_region_val
)
self.worker.finished.connect(self.on_download_finished)
self.worker.progress.connect(self.update_progress)
@@ -1391,17 +1374,12 @@ class SpotiFLACGUI(QWidget):
def update_progress(self, message, percentage):
if "Download progress:" in message or "Processing metadata..." in message:
current_text = self.log_output.toPlainText()
if current_text:
lines = current_text.split('\n')
if "Download progress:" in lines[-1] or "Processing metadata..." in lines[-1]:
if lines and ("Download progress:" in lines[-1] or "Processing metadata..." in lines[-1]):
lines[-1] = message
new_text = '\n'.join(lines)
self.log_output.setPlainText(new_text)
self.log_output.moveCursor(QTextCursor.MoveOperation.End)
else:
self.log_output.append(message)
@@ -1410,8 +1388,7 @@ class SpotiFLACGUI(QWidget):
else:
self.log_output.append(message)
if percentage > 0:
self.progress_bar.setValue(percentage)
self.progress_bar.setValue(percentage)
def stop_download(self):
if hasattr(self, 'worker'):
+33 -21
View File
@@ -4,8 +4,20 @@ from datetime import datetime
import sys
import os
def _safe_print(*args, **kwargs):
if sys.stdout:
print(*args, **kwargs)
def _safe_stdout_write(data_to_write):
if sys.stdout:
sys.stdout.write(data_to_write)
def _safe_flush():
if sys.stdout:
sys.stdout.flush()
def get_track_info(isrc, region="us"):
print(f"Search: {isrc}")
_safe_print(f"Search: {isrc}")
base_url = f"https://{region}.qobuz.squid.wtf"
url = f"{base_url}/api/get-music?q={isrc}&offset=0"
response = requests.get(url)
@@ -16,7 +28,7 @@ def get_track_info(isrc, region="us"):
tracks = data["data"]["tracks"]["items"]
if not tracks:
print(f"Not Found: {isrc}")
_safe_print(f"Not Found: {isrc}")
raise Exception(f"No tracks found for ISRC: {isrc}")
track = None
@@ -26,14 +38,14 @@ def get_track_info(isrc, region="us"):
break
if not track:
print(f"Not Found: {isrc}")
_safe_print(f"Not Found: {isrc}")
raise Exception(f"No track with matching ISRC: {isrc}")
print(f"Found: {track['title']} - {track['performer']['name']}")
_safe_print(f"Found: {track['title']} - {track['performer']['name']}")
return track
def search_track(title, artist, strict_match=False, region="us"):
print(f"Search by title/artist: {title} - {artist}")
_safe_print(f"Search by title/artist: {title} - {artist}")
search_query = f"{title} {artist}".replace("feat.", "").replace("ft.", "")
@@ -47,7 +59,7 @@ def search_track(title, artist, strict_match=False, region="us"):
tracks = data["data"]["tracks"]["items"]
if not tracks:
print(f"Not Found: {title} - {artist}")
_safe_print(f"Not Found: {title} - {artist}")
raise Exception(f"No tracks found for: {title} - {artist}")
best_match = None
@@ -60,7 +72,7 @@ def search_track(title, artist, strict_match=False, region="us"):
if title_lower == item_title and (artist_lower in item_artist or item_artist in artist_lower):
best_match = item
print(f"Found exact title match with artist: {item['title']} - {item['performer']['name']}")
_safe_print(f"Found exact title match with artist: {item['title']} - {item['performer']['name']}")
break
if not best_match and not strict_match:
@@ -70,24 +82,24 @@ def search_track(title, artist, strict_match=False, region="us"):
if title_lower in item_title and (artist_lower in item_artist or item_artist in artist_lower):
best_match = item
print(f"Found partial match: {item['title']} - {item['performer']['name']}")
_safe_print(f"Found partial match: {item['title']} - {item['performer']['name']}")
break
if strict_match and best_match:
item_artist = best_match["performer"]["name"].lower()
if artist_lower not in item_artist and item_artist not in artist_lower:
print(f"Artist mismatch in strict mode: Expected '{artist}', found '{best_match['performer']['name']}'")
_safe_print(f"Artist mismatch in strict mode: Expected '{artist}', found '{best_match['performer']['name']}'")
best_match = None
if not best_match and not strict_match and tracks:
best_match = tracks[0]
print(f"No good match, using first result: {best_match['title']} - {best_match['performer']['name']}")
_safe_print(f"No good match, using first result: {best_match['title']} - {best_match['performer']['name']}")
if not best_match:
print(f"Not Found: {title} - {artist}")
_safe_print(f"Not Found: {title} - {artist}")
raise Exception(f"No suitable track found for: {title} - {artist}")
print(f"Found by title search: {best_match['title']} - {best_match['performer']['name']}")
_safe_print(f"Found by title search: {best_match['title']} - {best_match['performer']['name']}")
return best_match
def get_download_url(track_id, region="us"):
@@ -106,7 +118,7 @@ def download_file(url, filename, progress_callback=None):
if directory and not os.path.exists(directory):
try:
os.makedirs(directory, exist_ok=True)
print(f"Created directory: {directory}")
_safe_print(f"Created directory: {directory}")
except Exception as e:
raise Exception(f"Failed to create directory {directory}: {str(e)}")
@@ -135,11 +147,11 @@ def download_file(url, filename, progress_callback=None):
progress_callback(downloaded, total_size)
elif total_size > 0:
progress = (downloaded / total_size) * 100
sys.stdout.write(f"\rProgress Download: {progress:.1f}%")
sys.stdout.flush()
_safe_stdout_write(f"\rProgress Download: {progress:.1f}%")
_safe_flush()
if total_size > 0:
sys.stdout.write("\n")
if total_size > 0 and not progress_callback:
_safe_stdout_write("\n")
if not os.path.exists(filename) or os.path.getsize(filename) == 0:
raise Exception(f"Download failed: File {filename} is empty or does not exist")
@@ -149,7 +161,7 @@ def download_file(url, filename, progress_callback=None):
if os.path.exists(filename):
try:
os.remove(filename)
print(f"Removed incomplete file: {filename}")
_safe_print(f"Removed incomplete file: {filename}")
except:
pass
raise Exception(f"Download failed: {str(e)}")
@@ -159,7 +171,7 @@ def embed_metadata(filename, track_info):
raise Exception(f"Cannot embed metadata: File {filename} does not exist")
try:
print("Embedding Tags...")
_safe_print("Embedding Tags...")
audio = FLAC(filename)
audio.clear()
@@ -190,7 +202,7 @@ def embed_metadata(filename, track_info):
audio.add_picture(picture)
except Exception as e:
print(f"Warning: Could not add cover image: {str(e)}")
_safe_print(f"Warning: Could not add cover image: {str(e)}")
audio.save()
except Exception as e:
@@ -206,7 +218,7 @@ def download_cover_image(url):
def main():
try:
isrc = "USUM72409273"
isrc = "USQX92500261"
region = "us"
track_info = get_track_info(isrc, region)