Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ceb727adb9 | |||
| bbea8ca493 | |||
| f567dd19bf |
@@ -15,12 +15,14 @@ Sometimes, the **download speed** from Lucida can be fast or slow; it varies unp
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> When **Fallback** is enabled, it will use the backup server `Lucida.su`
|
||||
|
||||
+36
-7
@@ -66,13 +66,14 @@ class DownloadWorker(QThread):
|
||||
def progress_update(current, total):
|
||||
if total > 0:
|
||||
percent = (current / total) * 100
|
||||
self.progress.emit(f"Download progress: {percent:.2f}% ({current}/{total})",
|
||||
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))
|
||||
else:
|
||||
self.progress.emit(f"Processing metadata...", 0)
|
||||
|
||||
downloader.set_progress_callback(progress_update)
|
||||
downloader.set_filename_format(self.filename_format)
|
||||
|
||||
total_tracks = len(self.tracks)
|
||||
|
||||
@@ -102,7 +103,16 @@ class DownloadWorker(QThread):
|
||||
metadata = asyncio.run(downloader.get_track_info(track_id, self.service))
|
||||
|
||||
self.progress.emit(f"Track info received, starting download process", 0)
|
||||
downloaded_file = downloader.download(metadata, track_outpath)
|
||||
|
||||
is_paused_callback = lambda: self.is_paused
|
||||
is_stopped_callback = lambda: self.is_stopped
|
||||
|
||||
downloaded_file = downloader.download(
|
||||
metadata,
|
||||
track_outpath,
|
||||
is_paused_callback=is_paused_callback,
|
||||
is_stopped_callback=is_stopped_callback
|
||||
)
|
||||
|
||||
if (self.is_album or (self.is_playlist and self.use_album_subfolders)) and self.use_track_numbers:
|
||||
new_filename = f"{track.track_number:02d} - {self.get_formatted_filename(track)}"
|
||||
@@ -300,7 +310,7 @@ class ServiceComboBox(QComboBox):
|
||||
class SpotiFLACGUI(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.current_version = "2.2"
|
||||
self.current_version = "2.3"
|
||||
self.tracks = []
|
||||
self.reset_state()
|
||||
|
||||
@@ -719,7 +729,7 @@ class SpotiFLACGUI(QWidget):
|
||||
spacer = QSpacerItem(20, 6, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
about_layout.addItem(spacer)
|
||||
|
||||
footer_label = QLabel("v2.2 | March 2025")
|
||||
footer_label = QLabel("v2.3 | March 2025")
|
||||
footer_label.setStyleSheet("font-size: 12px; color: palette(text); margin-top: 10px;")
|
||||
about_layout.addWidget(footer_label, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
@@ -1044,8 +1054,27 @@ class SpotiFLACGUI(QWidget):
|
||||
self.tab_widget.setCurrentWidget(self.process_tab)
|
||||
|
||||
def update_progress(self, message, percentage):
|
||||
self.log_output.append(message)
|
||||
self.log_output.moveCursor(QTextCursor.MoveOperation.End)
|
||||
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]:
|
||||
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)
|
||||
else:
|
||||
self.log_output.append(message)
|
||||
else:
|
||||
self.log_output.append(message)
|
||||
|
||||
if percentage > 0:
|
||||
self.progress_bar.setValue(percentage)
|
||||
|
||||
|
||||
+35
-30
@@ -12,22 +12,14 @@ class TrackDownloader:
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
}
|
||||
self.progress_callback = None
|
||||
self.filename_format = 'title_artist'
|
||||
self.use_fallback = use_fallback
|
||||
self.base_domain = "lucida.su" if use_fallback else "lucida.to"
|
||||
|
||||
def set_progress_callback(self, callback):
|
||||
self.progress_callback = callback
|
||||
|
||||
def set_filename_format(self, format_type):
|
||||
self.filename_format = format_type
|
||||
|
||||
def generate_filename(self, metadata):
|
||||
if self.filename_format == 'artist_title':
|
||||
filename = f"{metadata['artists']} - {metadata['title']}.flac"
|
||||
else:
|
||||
filename = f"{metadata['title']} - {metadata['artists']}.flac"
|
||||
return self.sanitize_filename(filename)
|
||||
def generate_filename(self, track_id, service):
|
||||
return f"{track_id}_{service}.flac"
|
||||
|
||||
async def get_track_info(self, track_id, service="amazon", use_fallback=None):
|
||||
if use_fallback is None:
|
||||
@@ -42,6 +34,8 @@ class TrackDownloader:
|
||||
if "error" in result:
|
||||
raise Exception(f"Failed to get track info: {result['error']}")
|
||||
|
||||
result["track_id"] = track_id
|
||||
|
||||
return result
|
||||
|
||||
def convert_spotify_link(self, spotify_url, target_service="amazon", domain_type="to"):
|
||||
@@ -73,7 +67,7 @@ class TrackDownloader:
|
||||
}
|
||||
|
||||
session = requests.Session()
|
||||
session.verify = False
|
||||
session.verify = True
|
||||
|
||||
response = session.get(
|
||||
base_url,
|
||||
@@ -129,9 +123,7 @@ class TrackDownloader:
|
||||
"token": {
|
||||
"primary": None,
|
||||
"expiry": None
|
||||
},
|
||||
"title": "Title",
|
||||
"artists": "Artist"
|
||||
}
|
||||
}
|
||||
|
||||
if token:
|
||||
@@ -149,27 +141,18 @@ class TrackDownloader:
|
||||
except Exception as error:
|
||||
return {"error": str(error)}
|
||||
|
||||
def sanitize_filename(self, filename):
|
||||
invalid_chars = '<>:"/\\|?*'
|
||||
for char in invalid_chars:
|
||||
filename = filename.replace(char, '')
|
||||
|
||||
filename = ' '.join(filename.split())
|
||||
filename = filename.replace(' ,', ',')
|
||||
filename = filename.replace(',', ', ')
|
||||
while ' ' in filename:
|
||||
filename = filename.replace(' ', ' ')
|
||||
filename = filename.rsplit('.', 1)
|
||||
filename[0] = filename[0].strip()
|
||||
return '.'.join(filename)
|
||||
|
||||
def download(self, metadata, output_dir):
|
||||
def download(self, metadata, output_dir, is_paused_callback=None, is_stopped_callback=None):
|
||||
track_url = metadata['url']
|
||||
primary_token = metadata['token']['primary']
|
||||
expiry = metadata['token']['expiry']
|
||||
track_id = metadata['track_id']
|
||||
service = metadata['service']
|
||||
|
||||
print(f"Starting download for: {track_url}")
|
||||
|
||||
if is_stopped_callback and is_stopped_callback():
|
||||
raise Exception("Download stopped by user")
|
||||
|
||||
initial_request = {
|
||||
"account": {"id": "auto", "type": "country"},
|
||||
"compat": "false",
|
||||
@@ -201,12 +184,20 @@ class TrackDownloader:
|
||||
handoff = initial_response["handoff"]
|
||||
server = initial_response["server"]
|
||||
|
||||
file_name = self.generate_filename(metadata)
|
||||
file_name = self.generate_filename(track_id, service)
|
||||
|
||||
completion_url = f"https://{server}.{self.base_domain}/api/fetch/request/{handoff}"
|
||||
|
||||
print("Waiting for track processing to complete")
|
||||
while True:
|
||||
if is_stopped_callback and is_stopped_callback():
|
||||
raise Exception("Download stopped by user")
|
||||
|
||||
while is_paused_callback and is_paused_callback():
|
||||
time.sleep(0.1)
|
||||
if is_stopped_callback and is_stopped_callback():
|
||||
raise Exception("Download stopped by user")
|
||||
|
||||
completion_response = self.client.get(completion_url, headers=self.headers).json()
|
||||
|
||||
status = completion_response["status"]
|
||||
@@ -250,6 +241,20 @@ class TrackDownloader:
|
||||
last_update_time = start_time
|
||||
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
if is_stopped_callback and is_stopped_callback():
|
||||
file.close()
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
raise Exception("Download stopped by user")
|
||||
|
||||
while is_paused_callback and is_paused_callback():
|
||||
time.sleep(0.1)
|
||||
if is_stopped_callback and is_stopped_callback():
|
||||
file.close()
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
raise Exception("Download stopped by user")
|
||||
|
||||
if chunk:
|
||||
file.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"version": "2.1"
|
||||
"version": "2.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user