Compare commits

...

3 Commits

Author SHA1 Message Date
afkarxyz 783350fe88 v3.9 2025-07-21 17:28:44 +07:00
afkarxyz 0057d43f46 v3.8 2025-07-14 13:20:14 +07:00
afkarxyz 9928968ffb v3.7 2025-07-13 05:30:59 +07:00
4 changed files with 26 additions and 42 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Qobuz & Tidal. <b>SpotiFLAC</b> allows you to download Spotify tracks in true FLAC format through services like Qobuz & Tidal.
</div> </div>
### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v3.6/SpotiFLAC.exe) ### [Download](https://github.com/afkarxyz/SpotiFLAC/releases/download/v3.8/SpotiFLAC.exe)
## Screenshots ## Screenshots
+20 -10
View File
@@ -76,6 +76,8 @@ class DownloadWorker(QThread):
def get_formatted_filename(self, track): def get_formatted_filename(self, track):
if self.filename_format == "artist_title": if self.filename_format == "artist_title":
filename = f"{track.artists} - {track.title}.flac" filename = f"{track.artists} - {track.title}.flac"
elif self.filename_format == "title_only":
filename = f"{track.title}.flac"
else: else:
filename = f"{track.title} - {track.artists}.flac" filename = f"{track.title} - {track.artists}.flac"
return re.sub(r'[<>:"/\\|?*]', '_', filename) return re.sub(r'[<>:"/\\|?*]', '_', filename)
@@ -503,7 +505,7 @@ class QobuzRegionComboBox(QComboBox):
class SpotiFLACGUI(QWidget): class SpotiFLACGUI(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.current_version = "3.7" self.current_version = "3.9"
self.tracks = [] self.tracks = []
self.reset_state() self.reset_state()
@@ -544,12 +546,11 @@ class SpotiFLACGUI(QWidget):
if dialog.disable_check.isChecked(): if dialog.disable_check.isChecked():
self.settings.setValue('check_for_updates', False) self.settings.setValue('check_for_updates', False)
self.check_for_updates = False self.check_for_updates = False
if result == QDialog.DialogCode.Accepted: if result == QDialog.DialogCode.Accepted:
QDesktopServices.openUrl(QUrl("https://github.com/afkarxyz/SpotiFLAC/releases")) QDesktopServices.openUrl(QUrl("https://github.com/afkarxyz/SpotiFLAC/releases"))
except Exception as e: except Exception as e:
print(f"Error checking for updates: {e}") pass
@staticmethod @staticmethod
def format_duration(ms): def format_duration(ms):
@@ -788,7 +789,6 @@ class SpotiFLACGUI(QWidget):
format_layout = QHBoxLayout() format_layout = QHBoxLayout()
format_label = QLabel('Filename Format:') format_label = QLabel('Filename Format:')
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.setCursor(Qt.CursorShape.PointingHandCursor) self.title_artist_radio.setCursor(Qt.CursorShape.PointingHandCursor)
@@ -798,17 +798,25 @@ class SpotiFLACGUI(QWidget):
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)
self.title_only_radio = QRadioButton('Title')
self.title_only_radio.setCursor(Qt.CursorShape.PointingHandCursor)
self.title_only_radio.toggled.connect(self.save_filename_format)
if hasattr(self, 'filename_format') and self.filename_format == "artist_title": if hasattr(self, 'filename_format') and self.filename_format == "artist_title":
self.artist_title_radio.setChecked(True) self.artist_title_radio.setChecked(True)
elif hasattr(self, 'filename_format') and self.filename_format == "title_only":
self.title_only_radio.setChecked(True)
else: else:
self.title_artist_radio.setChecked(True) self.title_artist_radio.setChecked(True)
self.format_group.addButton(self.title_artist_radio) self.format_group.addButton(self.title_artist_radio)
self.format_group.addButton(self.artist_title_radio) self.format_group.addButton(self.artist_title_radio)
self.format_group.addButton(self.title_only_radio)
format_layout.addWidget(format_label) format_layout.addWidget(format_label)
format_layout.addWidget(self.title_artist_radio) format_layout.addWidget(self.title_artist_radio)
format_layout.addWidget(self.artist_title_radio) format_layout.addWidget(self.artist_title_radio)
format_layout.addWidget(self.title_only_radio)
format_layout.addStretch() format_layout.addStretch()
file_layout.addLayout(format_layout) file_layout.addLayout(format_layout)
@@ -931,7 +939,7 @@ 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("v3.7 | July 2025") footer_label = QLabel("v3.9 | July 2025")
footer_label.setStyleSheet("font-size: 12px; 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)
@@ -982,7 +990,12 @@ class SpotiFLACGUI(QWidget):
self.settings.sync() self.settings.sync()
def save_filename_format(self): def save_filename_format(self):
self.filename_format = "artist_title" if self.artist_title_radio.isChecked() else "title_artist" if self.artist_title_radio.isChecked():
self.filename_format = "artist_title"
elif self.title_only_radio.isChecked():
self.filename_format = "title_only"
else:
self.filename_format = "title_artist"
self.settings.setValue('filename_format', self.filename_format) self.settings.setValue('filename_format', self.filename_format)
self.settings.sync() self.settings.sync()
@@ -1387,7 +1400,6 @@ class SpotiFLACGUI(QWidget):
for i, track in enumerate(self.tracks, 1): for i, track in enumerate(self.tracks, 1):
if self.is_playlist: if self.is_playlist:
track.track_number = i track.track_number = i
duration = self.format_duration(track.duration_ms) duration = self.format_duration(track.duration_ms)
display_text = f"{i}. {track.title} - {track.artists}{duration}" display_text = f"{i}. {track.title} - {track.artists}{duration}"
list_item = self.track_list.item(i - 1) list_item = self.track_list.item(i - 1)
@@ -1412,13 +1424,11 @@ class SpotiFLACGUI(QWidget):
if __name__ == '__main__': if __name__ == '__main__':
try: try:
if sys.platform == "win32": if sys.platform == "win32":
import os
os.system("chcp 65001 > nul")
import io import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
except Exception as e: except Exception as e:
print(f"Warning: Could not set UTF-8 encoding: {e}") pass
app = QApplication(sys.argv) app = QApplication(sys.argv)
ex = SpotiFLACGUI() ex = SpotiFLACGUI()
+2 -28
View File
@@ -2,7 +2,6 @@ import asyncio
import json import json
import os import os
import re import re
import tempfile
import time import time
import httpx import httpx
from mutagen.flac import FLAC, Picture from mutagen.flac import FLAC, Picture
@@ -24,22 +23,11 @@ class TidalDownloader:
self.progress_callback = ProgressCallback() self.progress_callback = ProgressCallback()
self.client_id = "zU4XHVVkc2tDPo4t" self.client_id = "zU4XHVVkc2tDPo4t"
self.client_secret = "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=" self.client_secret = "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4="
self.temp_dir = tempfile.gettempdir()
self.token_path = os.path.join(self.temp_dir, "tidal_token.json")
self.access_token = None
self._load_token()
def set_progress_callback(self, callback): def set_progress_callback(self, callback):
self.progress_callback = callback self.progress_callback = callback
def _load_token(self):
if os.path.exists(self.token_path):
try:
with open(self.token_path, "r") as tok:
token = json.loads(tok.read())
self.access_token = token.get("access_token")
except:
pass
def sanitize_filename(self, filename): def sanitize_filename(self, filename):
if not filename: if not filename:
@@ -48,9 +36,6 @@ class TidalDownloader:
return re.sub(r'\s+', ' ', sanitized).strip() or "Unnamed Track" return re.sub(r'\s+', ' ', sanitized).strip() or "Unnamed Track"
async def get_access_token(self): async def get_access_token(self):
if self.access_token:
return self.access_token
refresh_url = "https://auth.tidal.com/v1/oauth2/token" refresh_url = "https://auth.tidal.com/v1/oauth2/token"
payload = { payload = {
@@ -68,18 +53,7 @@ class TidalDownloader:
if response.status_code == 200: if response.status_code == 200:
token_data = response.json() token_data = response.json()
new_token = token_data.get("access_token") return token_data.get("access_token")
try:
with open(self.token_path, "w") as f:
json.dump({
"access_token": new_token
}, f)
except:
pass
self.access_token = new_token
return new_token
else: else:
return None return None
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"version": "3.6" "version": "3.8"
} }