Compare commits

...

5 Commits

Author SHA1 Message Date
afkarxyz 1e7a48d263 v2.6 2025-05-06 10:38:01 +07:00
afkarxyz 0a83a0dd6e Update README.md 2025-05-06 10:35:17 +07:00
afkarxyz da429d9410 v2.5 2025-04-25 06:33:32 +07:00
afkarxyz 63211c726b v2.5 2025-04-25 06:30:14 +07:00
afkarxyz 055cb6991a Update v2.4 2025-04-08 13:10:12 +07:00
4 changed files with 67 additions and 32 deletions
+9 -7
View File
@@ -1,12 +1,12 @@
[![GitHub All Releases](https://img.shields.io/github/downloads/afkarxyz/SpotiFLAC/total?style=for-the-badge)](https://github.com/afkarxyz/SpotiFLAC/releases) [![GitHub All Releases](https://img.shields.io/github/downloads/afkarxyz/SpotiFLAC/total?style=for-the-badge)](https://github.com/afkarxyz/SpotiFLAC/releases)
![spotiflac](https://github.com/user-attachments/assets/a233a276-14a4-4f4c-b267-f182dd3912a0) ![SpotiFLAC](https://github.com/user-attachments/assets/b4c4f403-edbd-4a71-b74b-c7d433d47d06)
<div align="center"> <div align="center">
<b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Tidal, Amazon Music and Deezer with the help of Lucida. <b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Tidal, Amazon Music and Deezer with the help of Lucida.
</div> </div>
### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v2.3/SpotiFLAC.exe) ### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v2.5/SpotiFLAC.exe)
# #
@@ -15,15 +15,17 @@ Sometimes, the **download speed** from Lucida can be fast or slow; it varies unp
## Screenshots ## Screenshots
![image](https://github.com/user-attachments/assets/611b8b52-6615-44fe-b6f8-905c07801c47) ![image](https://github.com/user-attachments/assets/70a5dceb-3374-4255-8f6a-4afb5ee534b0)
![image](https://github.com/user-attachments/assets/81e65977-11f0-4162-96f3-90730dd87e74) ![image](https://github.com/user-attachments/assets/9f0d6aa5-456b-4a90-b48a-7e0c22819ebd)
![image](https://github.com/user-attachments/assets/4dd37c0a-30e3-479a-9b3d-57fd360d87b3) ![image](https://github.com/user-attachments/assets/be9a79b5-260e-4948-9f72-10c735210ab7)
![image](https://github.com/user-attachments/assets/66f1ae70-e049-4b4c-ba81-1df5054d0e7d) ![image](https://github.com/user-attachments/assets/c4403934-9003-447e-a27b-fc74cab23454)
![image](https://github.com/user-attachments/assets/04954db9-e94a-4f9d-8eac-46d7ff7a4c33) ![image](https://github.com/user-attachments/assets/1feec621-f8bf-4b2a-ae73-afcb1fb1deba)
![image](https://github.com/user-attachments/assets/66cc3398-547d-4568-8d49-a05ad4997370)
> When **Fallback** is enabled, it will use the backup server `Lucida.su` > When **Fallback** is enabled, it will use the backup server `Lucida.su`
+53 -21
View File
@@ -55,7 +55,7 @@ class DownloadWorker(QThread):
def __init__(self, tracks, outpath, is_single_track=False, is_album=False, is_playlist=False, def __init__(self, tracks, outpath, is_single_track=False, is_album=False, is_playlist=False,
album_or_playlist_name='', filename_format='title_artist', use_track_numbers=True, album_or_playlist_name='', filename_format='title_artist', use_track_numbers=True,
use_album_subfolders=False, use_fallback=False, service="amazon"): use_album_subfolders=False, use_fallback=False, service="amazon", timeout=30):
super().__init__() super().__init__()
self.tracks = tracks self.tracks = tracks
self.outpath = outpath self.outpath = outpath
@@ -68,6 +68,7 @@ class DownloadWorker(QThread):
self.use_album_subfolders = use_album_subfolders self.use_album_subfolders = use_album_subfolders
self.use_fallback = use_fallback self.use_fallback = use_fallback
self.service = service self.service = service
self.timeout = timeout
self.is_paused = False self.is_paused = False
self.is_stopped = False self.is_stopped = False
self.failed_tracks = [] self.failed_tracks = []
@@ -81,7 +82,7 @@ class DownloadWorker(QThread):
def run(self): def run(self):
try: try:
downloader = TrackDownloader(self.use_fallback) downloader = TrackDownloader(self.use_fallback, self.timeout)
def progress_update(current, total): def progress_update(current, total):
if total > 0: if total > 0:
@@ -330,7 +331,7 @@ class ServiceComboBox(QComboBox):
class SpotiFLACGUI(QWidget): class SpotiFLACGUI(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.current_version = "2.4" self.current_version = "2.6"
self.tracks = [] self.tracks = []
self.reset_state() self.reset_state()
@@ -343,6 +344,7 @@ class SpotiFLACGUI(QWidget):
self.use_album_subfolders = self.settings.value('use_album_subfolders', False, type=bool) self.use_album_subfolders = self.settings.value('use_album_subfolders', False, type=bool)
self.use_fallback = self.settings.value('use_fallback', False, type=bool) self.use_fallback = self.settings.value('use_fallback', False, type=bool)
self.service = self.settings.value('service', 'amazon') self.service = self.settings.value('service', 'amazon')
self.timeout_value = self.settings.value('timeout_value', 30, type=int)
self.check_for_updates = self.settings.value('check_for_updates', True, type=bool) self.check_for_updates = self.settings.value('check_for_updates', True, type=bool)
self.elapsed_time = QTime(0, 0, 0) self.elapsed_time = QTime(0, 0, 0)
@@ -586,7 +588,7 @@ class SpotiFLACGUI(QWidget):
output_layout.setSpacing(5) output_layout.setSpacing(5)
output_label = QLabel('Output Directory') output_label = QLabel('Output Directory')
output_label.setStyleSheet("font-weight: bold; color: palette(text);") output_label.setStyleSheet("font-weight: bold;")
output_layout.addWidget(output_label) output_layout.addWidget(output_label)
output_dir_layout = QHBoxLayout() output_dir_layout = QHBoxLayout()
@@ -609,21 +611,18 @@ class SpotiFLACGUI(QWidget):
file_layout.setSpacing(5) file_layout.setSpacing(5)
file_label = QLabel('File Settings') file_label = QLabel('File Settings')
file_label.setStyleSheet("font-weight: bold; color: palette(text);") file_label.setStyleSheet("font-weight: bold;")
file_layout.addWidget(file_label) file_layout.addWidget(file_label)
format_layout = QHBoxLayout() format_layout = QHBoxLayout()
format_label = QLabel('Filename Format:') format_label = QLabel('Filename Format:')
format_label.setStyleSheet("color: palette(text);")
self.format_group = QButtonGroup(self) self.format_group = QButtonGroup(self)
self.title_artist_radio = QRadioButton('Title - Artist') self.title_artist_radio = QRadioButton('Title - Artist')
self.title_artist_radio.setStyleSheet("color: palette(text);")
self.title_artist_radio.setCursor(Qt.CursorShape.PointingHandCursor) self.title_artist_radio.setCursor(Qt.CursorShape.PointingHandCursor)
self.title_artist_radio.toggled.connect(self.save_filename_format) self.title_artist_radio.toggled.connect(self.save_filename_format)
self.artist_title_radio = QRadioButton('Artist - Title') self.artist_title_radio = QRadioButton('Artist - Title')
self.artist_title_radio.setStyleSheet("color: palette(text);")
self.artist_title_radio.setCursor(Qt.CursorShape.PointingHandCursor) self.artist_title_radio.setCursor(Qt.CursorShape.PointingHandCursor)
self.artist_title_radio.toggled.connect(self.save_filename_format) self.artist_title_radio.toggled.connect(self.save_filename_format)
@@ -644,14 +643,12 @@ class SpotiFLACGUI(QWidget):
checkbox_layout = QHBoxLayout() checkbox_layout = QHBoxLayout()
self.track_number_checkbox = QCheckBox('Add Track Numbers to Album Files') self.track_number_checkbox = QCheckBox('Add Track Numbers to Album Files')
self.track_number_checkbox.setStyleSheet("color: palette(text);")
self.track_number_checkbox.setCursor(Qt.CursorShape.PointingHandCursor) self.track_number_checkbox.setCursor(Qt.CursorShape.PointingHandCursor)
self.track_number_checkbox.setChecked(self.use_track_numbers) self.track_number_checkbox.setChecked(self.use_track_numbers)
self.track_number_checkbox.toggled.connect(self.save_track_numbering) self.track_number_checkbox.toggled.connect(self.save_track_numbering)
checkbox_layout.addWidget(self.track_number_checkbox) checkbox_layout.addWidget(self.track_number_checkbox)
self.album_subfolder_checkbox = QCheckBox('Create Album Subfolders for Playlist Downloads') self.album_subfolder_checkbox = QCheckBox('Create Album Subfolders for Playlist Downloads')
self.album_subfolder_checkbox.setStyleSheet("color: palette(text);")
self.album_subfolder_checkbox.setCursor(Qt.CursorShape.PointingHandCursor) self.album_subfolder_checkbox.setCursor(Qt.CursorShape.PointingHandCursor)
self.album_subfolder_checkbox.setChecked(self.use_album_subfolders) self.album_subfolder_checkbox.setChecked(self.use_album_subfolders)
self.album_subfolder_checkbox.toggled.connect(self.save_album_subfolder_setting) self.album_subfolder_checkbox.toggled.connect(self.save_album_subfolder_setting)
@@ -666,14 +663,13 @@ class SpotiFLACGUI(QWidget):
auth_layout = QVBoxLayout(auth_group) auth_layout = QVBoxLayout(auth_group)
auth_layout.setSpacing(5) auth_layout.setSpacing(5)
auth_label = QLabel('Lucida') auth_label = QLabel('Lucida Settings')
auth_label.setStyleSheet("font-weight: bold; color: palette(text);") auth_label.setStyleSheet("font-weight: bold;")
auth_layout.addWidget(auth_label) auth_layout.addWidget(auth_label)
service_fallback_layout = QHBoxLayout() service_fallback_layout = QHBoxLayout()
service_label = QLabel('Service:') service_label = QLabel('Service:')
service_label.setStyleSheet("color: palette(text);")
self.service_dropdown = ServiceComboBox() self.service_dropdown = ServiceComboBox()
self.service_dropdown.currentIndexChanged.connect(self.save_service_setting) self.service_dropdown.currentIndexChanged.connect(self.save_service_setting)
@@ -684,12 +680,21 @@ class SpotiFLACGUI(QWidget):
service_fallback_layout.addSpacing(20) service_fallback_layout.addSpacing(20)
self.fallback_checkbox = QCheckBox('Fallback') self.fallback_checkbox = QCheckBox('Fallback')
self.fallback_checkbox.setStyleSheet("color: palette(text);")
self.fallback_checkbox.setCursor(Qt.CursorShape.PointingHandCursor) self.fallback_checkbox.setCursor(Qt.CursorShape.PointingHandCursor)
self.fallback_checkbox.setChecked(self.use_fallback) self.fallback_checkbox.setChecked(self.use_fallback)
self.fallback_checkbox.toggled.connect(self.save_fallback_setting) self.fallback_checkbox.toggled.connect(self.save_fallback_setting)
service_fallback_layout.addWidget(self.fallback_checkbox) service_fallback_layout.addWidget(self.fallback_checkbox)
service_fallback_layout.addSpacing(20)
timeout_label = QLabel('Timeout:')
self.timeout_input = QLineEdit()
self.timeout_input.setText(str(self.timeout_value))
self.timeout_input.setFixedWidth(60)
self.timeout_input.textChanged.connect(self.save_timeout_setting)
service_fallback_layout.addWidget(timeout_label)
service_fallback_layout.addWidget(self.timeout_input)
service_fallback_layout.addStretch() service_fallback_layout.addStretch()
auth_layout.addLayout(service_fallback_layout) auth_layout.addLayout(service_fallback_layout)
@@ -749,8 +754,8 @@ class SpotiFLACGUI(QWidget):
spacer = QSpacerItem(20, 6, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) spacer = QSpacerItem(20, 6, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
about_layout.addItem(spacer) about_layout.addItem(spacer)
footer_label = QLabel("v2.4 | April 2025") footer_label = QLabel("v2.6 | May 2025")
footer_label.setStyleSheet("font-size: 12px; color: palette(text); margin-top: 10px;") footer_label.setStyleSheet("font-size: 12px; margin-top: 10px;")
about_layout.addWidget(footer_label, alignment=Qt.AlignmentFlag.AlignCenter) about_layout.addWidget(footer_label, alignment=Qt.AlignmentFlag.AlignCenter)
about_tab.setLayout(about_layout) about_tab.setLayout(about_layout)
@@ -781,6 +786,21 @@ class SpotiFLACGUI(QWidget):
self.settings.sync() self.settings.sync()
self.log_output.append("Fallback setting saved successfully!") self.log_output.append("Fallback setting saved successfully!")
def save_timeout_setting(self):
try:
timeout = int(self.timeout_input.text())
if timeout > 0:
self.timeout_value = timeout
self.settings.setValue('timeout_value', self.timeout_value)
self.settings.sync()
self.log_output.append(f"Timeout setting saved: {self.timeout_value} seconds")
else:
self.timeout_input.setText(str(self.timeout_value))
self.log_output.append("Timeout must be a positive number")
except ValueError:
self.timeout_input.setText(str(self.timeout_value))
self.log_output.append("Timeout must be a valid number")
def save_service_setting(self): def save_service_setting(self):
service = self.service_dropdown.currentData() service = self.service_dropdown.currentData()
self.service = service self.service = service
@@ -950,10 +970,21 @@ class SpotiFLACGUI(QWidget):
self.followers_label.hide() self.followers_label.hide()
if metadata.get('releaseDate'): if metadata.get('releaseDate'):
release_date = datetime.strptime(metadata['releaseDate'], "%Y-%m-%d") try:
formatted_date = release_date.strftime("%d-%m-%Y") release_date = metadata['releaseDate']
self.release_date_label.setText(f"<b>Released</b> {formatted_date}") if len(release_date) == 4:
self.release_date_label.show() date_obj = datetime.strptime(release_date, "%Y")
elif len(release_date) == 7:
date_obj = datetime.strptime(release_date, "%Y-%m")
else:
date_obj = datetime.strptime(release_date, "%Y-%m-%d")
formatted_date = date_obj.strftime("%d-%m-%Y")
self.release_date_label.setText(f"<b>Released</b> {formatted_date}")
self.release_date_label.show()
except ValueError:
self.release_date_label.setText(f"<b>Released</b> {metadata['releaseDate']}")
self.release_date_label.show()
else: else:
self.release_date_label.hide() self.release_date_label.hide()
@@ -1064,7 +1095,8 @@ class SpotiFLACGUI(QWidget):
self.use_track_numbers, self.use_track_numbers,
self.use_album_subfolders, self.use_album_subfolders,
self.use_fallback, self.use_fallback,
service service,
self.timeout_value
) )
self.worker.finished.connect(self.on_download_finished) self.worker.finished.connect(self.on_download_finished)
self.worker.progress.connect(self.update_progress) self.worker.progress.connect(self.update_progress)
+4 -3
View File
@@ -6,13 +6,14 @@ import re
import base64 import base64
class TrackDownloader: class TrackDownloader:
def __init__(self, use_fallback=False): def __init__(self, use_fallback=False, timeout=30):
self.client = requests.Session() self.client = requests.Session()
self.headers = { self.headers = {
'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' '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.progress_callback = None
self.use_fallback = use_fallback self.use_fallback = use_fallback
self.timeout = timeout
self.base_domain = "lucida.su" if use_fallback else "lucida.to" self.base_domain = "lucida.su" if use_fallback else "lucida.to"
def set_progress_callback(self, callback): def set_progress_callback(self, callback):
@@ -73,7 +74,7 @@ class TrackDownloader:
base_url, base_url,
params=request_params, params=request_params,
headers=headers, headers=headers,
timeout=30 timeout=self.timeout
) )
html_content = response.text html_content = response.text
@@ -294,7 +295,7 @@ async def main():
output_dir = "." output_dir = "."
track_id = "2plbrEY59IikOBgBGLjaoe" track_id = "2plbrEY59IikOBgBGLjaoe"
service = "amazon" service = "tidal"
def progress_update(current, total): def progress_update(current, total):
if total > 0: if total > 0:
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "2.3" "version": "2.5"
} }