diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 58475e5..2fcb24e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -78,13 +78,13 @@ jobs:
- name: Prepare artifacts
run: |
mkdir -p dist
- Copy-Item -Path "build\bin\SpotiFLAC.exe" -Destination "dist\SpotiFLAC-${{ steps.version.outputs.version }}.exe"
+ Copy-Item -Path "build\bin\SpotiFLAC.exe" -Destination "dist\SpotiFLAC.exe"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: windows-portable
- path: dist/SpotiFLAC-${{ steps.version.outputs.version }}.exe
+ path: dist/SpotiFLAC.exe
retention-days: 7
build-macos:
@@ -159,16 +159,16 @@ jobs:
--icon "SpotiFLAC.app" 175 120 \
--hide-extension "SpotiFLAC.app" \
--app-drop-link 425 120 \
- "dist/SpotiFLAC-${{ steps.version.outputs.version }}.dmg" \
+ "dist/SpotiFLAC.dmg" \
"build/bin/SpotiFLAC.app" || \
# Fallback to hdiutil if create-dmg fails
- hdiutil create -volname SpotiFLAC -srcfolder build/bin/SpotiFLAC.app -ov -format UDZO dist/SpotiFLAC-${{ steps.version.outputs.version }}.dmg
+ hdiutil create -volname SpotiFLAC -srcfolder build/bin/SpotiFLAC.app -ov -format UDZO dist/SpotiFLAC.dmg
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: macos-portable
- path: dist/SpotiFLAC-${{ steps.version.outputs.version }}.dmg
+ path: dist/SpotiFLAC.dmg
retention-days: 7
build-linux:
@@ -291,13 +291,13 @@ jobs:
# Create AppImage
mkdir -p dist
- ARCH=x86_64 ./appimagetool --no-appstream AppDir dist/SpotiFLAC-${{ steps.version.outputs.version }}.AppImage
+ ARCH=x86_64 ./appimagetool --no-appstream AppDir dist/SpotiFLAC.AppImage
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: linux-portable
- path: dist/SpotiFLAC-${{ steps.version.outputs.version }}.AppImage
+ path: dist/SpotiFLAC.AppImage
retention-days: 7
create-release:
@@ -336,9 +336,9 @@ jobs:
## Downloads
- - `SpotiFLAC-${{ steps.version.outputs.version }}.exe` - Windows
- - `SpotiFLAC-${{ steps.version.outputs.version }}.dmg` - macOS
- - `SpotiFLAC-${{ steps.version.outputs.version }}.AppImage` - Linux
+ - `SpotiFLAC.exe` - Windows
+ - `SpotiFLAC.dmg` - macOS
+ - `SpotiFLAC.AppImage` - Linux
Linux Requirements
@@ -362,8 +362,8 @@ jobs:
After installing the dependency, make the AppImage executable:
```bash
- chmod +x SpotiFLAC-${{ steps.version.outputs.version }}.AppImage
- ./SpotiFLAC-${{ steps.version.outputs.version }}.AppImage
+ chmod +x SpotiFLAC.AppImage
+ ./SpotiFLAC.AppImage
```
diff --git a/app.go b/app.go
index fbb3e5c..c443a82 100644
--- a/app.go
+++ b/app.go
@@ -50,6 +50,7 @@ type DownloadRequest struct {
TrackNumber bool `json:"track_number,omitempty"`
Position int `json:"position,omitempty"` // Position in playlist/album (1-based)
UseAlbumTrackNumber bool `json:"use_album_track_number,omitempty"` // Use album track number instead of playlist position
+ SpotifyID string `json:"spotify_id,omitempty"` // Spotify track ID for Amazon Music
}
// DownloadResponse represents the response structure for download operations
@@ -138,7 +139,16 @@ func (a *App) DownloadTrack(req DownloadRequest) (DownloadResponse, error) {
backend.SetDownloading(true)
defer backend.SetDownloading(false)
- if req.Service == "tidal" {
+ if req.Service == "amazon" {
+ if req.SpotifyID == "" {
+ return DownloadResponse{
+ Success: false,
+ Error: "Spotify ID is required for Amazon Music",
+ }, fmt.Errorf("Spotify ID is required for Amazon Music")
+ }
+ downloader := backend.NewAmazonDownloader()
+ filename, err = downloader.DownloadBySpotifyID(req.SpotifyID, req.OutputDir, req.FilenameFormat, req.TrackNumber, req.Position, req.TrackName, req.ArtistName, req.AlbumName, req.UseAlbumTrackNumber)
+ } else if req.Service == "tidal" {
searchQuery := req.Query
if searchQuery == "" {
searchQuery = req.ISRC
diff --git a/backend/amazon.go b/backend/amazon.go
new file mode 100644
index 0000000..4ce2699
--- /dev/null
+++ b/backend/amazon.go
@@ -0,0 +1,414 @@
+package backend
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "io"
+ "math/rand"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+)
+
+type AmazonDownloader struct {
+ client *http.Client
+ regions []string
+ lastAPICallTime time.Time
+ apiCallCount int
+ apiCallResetTime time.Time
+}
+
+type SongLinkResponse struct {
+ LinksByPlatform map[string]struct {
+ URL string `json:"url"`
+ } `json:"linksByPlatform"`
+}
+
+type DoubleDoubleSubmitResponse struct {
+ Success bool `json:"success"`
+ ID string `json:"id"`
+}
+
+type DoubleDoubleStatusResponse struct {
+ Status string `json:"status"`
+ FriendlyStatus string `json:"friendlyStatus"`
+ URL string `json:"url"`
+ Current struct {
+ Name string `json:"name"`
+ Artist string `json:"artist"`
+ } `json:"current"`
+}
+
+func NewAmazonDownloader() *AmazonDownloader {
+ return &AmazonDownloader{
+ client: &http.Client{
+ Timeout: 120 * time.Second,
+ },
+ regions: []string{"us", "eu"},
+ apiCallResetTime: time.Now(),
+ }
+}
+
+func (a *AmazonDownloader) getRandomUserAgent() string {
+ return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_%d_%d) AppleWebKit/%d.%d (KHTML, like Gecko) Chrome/%d.0.%d.%d Safari/%d.%d",
+ rand.Intn(4)+11, rand.Intn(5)+4,
+ rand.Intn(7)+530, rand.Intn(7)+30,
+ rand.Intn(25)+80, rand.Intn(1500)+3000, rand.Intn(65)+60,
+ rand.Intn(7)+530, rand.Intn(6)+30)
+}
+
+func (a *AmazonDownloader) GetAmazonURLFromSpotify(spotifyTrackID string) (string, error) {
+ // Rate limiting: max 10 requests per minute (song.link API limit)
+ // Reset counter every minute
+ now := time.Now()
+ if now.Sub(a.apiCallResetTime) >= time.Minute {
+ a.apiCallCount = 0
+ a.apiCallResetTime = now
+ }
+
+ // If we've hit the limit, wait until the next minute
+ if a.apiCallCount >= 9 { // Use 9 to be safe (limit is 10)
+ waitTime := time.Minute - now.Sub(a.apiCallResetTime)
+ if waitTime > 0 {
+ fmt.Printf("Rate limit reached, waiting %v...\n", waitTime.Round(time.Second))
+ time.Sleep(waitTime)
+ a.apiCallCount = 0
+ a.apiCallResetTime = time.Now()
+ }
+ }
+
+ // Add delay between requests (6 seconds = 10 requests per minute)
+ if !a.lastAPICallTime.IsZero() {
+ timeSinceLastCall := now.Sub(a.lastAPICallTime)
+ minDelay := 7 * time.Second // 7 seconds to be safe
+ if timeSinceLastCall < minDelay {
+ waitTime := minDelay - timeSinceLastCall
+ fmt.Printf("Rate limiting: waiting %v...\n", waitTime.Round(time.Second))
+ time.Sleep(waitTime)
+ }
+ }
+
+ // Decode base64 API URL
+ spotifyBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9vcGVuLnNwb3RpZnkuY29tL3RyYWNrLw==")
+ spotifyURL := fmt.Sprintf("%s%s", string(spotifyBase), spotifyTrackID)
+
+ apiBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9hcGkuc29uZy5saW5rL3YxLWFscGhhLjEvbGlua3M/dXJsPQ==")
+ apiURL := fmt.Sprintf("%s%s", string(apiBase), url.QueryEscape(spotifyURL))
+
+ req, err := http.NewRequest("GET", apiURL, nil)
+ if err != nil {
+ return "", fmt.Errorf("failed to create request: %w", err)
+ }
+
+ req.Header.Set("User-Agent", a.getRandomUserAgent())
+
+ fmt.Println("Getting Amazon URL...")
+
+ // Retry logic for rate limit errors
+ maxRetries := 3
+ var resp *http.Response
+ for i := 0; i < maxRetries; i++ {
+ resp, err = a.client.Do(req)
+ if err != nil {
+ return "", fmt.Errorf("failed to get Amazon URL: %w", err)
+ }
+
+ // Update rate limit tracking
+ a.lastAPICallTime = time.Now()
+ a.apiCallCount++
+
+ if resp.StatusCode == 429 { // Too Many Requests
+ resp.Body.Close()
+ if i < maxRetries-1 {
+ waitTime := 15 * time.Second
+ fmt.Printf("Rate limited by API, waiting %v before retry...\n", waitTime)
+ time.Sleep(waitTime)
+ continue
+ }
+ return "", fmt.Errorf("API rate limit exceeded after %d retries", maxRetries)
+ }
+
+ if resp.StatusCode != 200 {
+ resp.Body.Close()
+ return "", fmt.Errorf("API returned status %d", resp.StatusCode)
+ }
+
+ break
+ }
+ defer resp.Body.Close()
+
+ var songLinkResp SongLinkResponse
+ if err := json.NewDecoder(resp.Body).Decode(&songLinkResp); err != nil {
+ return "", fmt.Errorf("failed to decode response: %w", err)
+ }
+
+ amazonLink, ok := songLinkResp.LinksByPlatform["amazonMusic"]
+ if !ok || amazonLink.URL == "" {
+ return "", fmt.Errorf("amazon Music link not found")
+ }
+
+ amazonURL := amazonLink.URL
+
+ // Convert album URL to track URL if needed
+ if strings.Contains(amazonURL, "trackAsin=") {
+ parts := strings.Split(amazonURL, "trackAsin=")
+ if len(parts) > 1 {
+ trackAsin := strings.Split(parts[1], "&")[0]
+ musicBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9tdXNpYy5hbWF6b24uY29tL3RyYWNrcy8=")
+ amazonURL = fmt.Sprintf("%s%s?musicTerritory=US", string(musicBase), trackAsin)
+ }
+ }
+
+ fmt.Printf("Found Amazon URL: %s\n", amazonURL)
+ return amazonURL, nil
+}
+
+func (a *AmazonDownloader) DownloadFromService(amazonURL, outputDir string) (string, error) {
+ var lastError error
+
+ for _, region := range a.regions {
+ fmt.Printf("\nTrying region: %s...\n", region)
+ // Decode base64 service URL
+ serviceBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly8=")
+ serviceDomain, _ := base64.StdEncoding.DecodeString("LmRvdWJsZWRvdWJsZS50b3A=")
+ baseURL := fmt.Sprintf("%s%s%s", string(serviceBase), region, string(serviceDomain))
+
+ // Step 1: Submit download request
+ encodedURL := url.QueryEscape(amazonURL)
+ submitURL := fmt.Sprintf("%s/dl?url=%s", baseURL, encodedURL)
+
+ req, err := http.NewRequest("GET", submitURL, nil)
+ if err != nil {
+ lastError = fmt.Errorf("failed to create request: %w", err)
+ continue
+ }
+
+ req.Header.Set("User-Agent", a.getRandomUserAgent())
+
+ fmt.Println("Submitting download request...")
+ resp, err := a.client.Do(req)
+ if err != nil {
+ lastError = fmt.Errorf("failed to submit request: %w", err)
+ continue
+ }
+
+ if resp.StatusCode != 200 {
+ resp.Body.Close()
+ lastError = fmt.Errorf("submit failed with status %d", resp.StatusCode)
+ continue
+ }
+
+ var submitResp DoubleDoubleSubmitResponse
+ if err := json.NewDecoder(resp.Body).Decode(&submitResp); err != nil {
+ resp.Body.Close()
+ lastError = fmt.Errorf("failed to decode submit response: %w", err)
+ continue
+ }
+ resp.Body.Close()
+
+ if !submitResp.Success || submitResp.ID == "" {
+ lastError = fmt.Errorf("submit request failed")
+ continue
+ }
+
+ downloadID := submitResp.ID
+ fmt.Printf("Download ID: %s\n", downloadID)
+
+ // Step 2: Poll for completion
+ statusURL := fmt.Sprintf("%s/dl/%s", baseURL, downloadID)
+ fmt.Println("Waiting for download to complete...")
+
+ maxWait := 300 * time.Second
+ elapsed := time.Duration(0)
+ pollInterval := 3 * time.Second
+
+ for elapsed < maxWait {
+ time.Sleep(pollInterval)
+ elapsed += pollInterval
+
+ statusReq, err := http.NewRequest("GET", statusURL, nil)
+ if err != nil {
+ continue
+ }
+
+ statusReq.Header.Set("User-Agent", a.getRandomUserAgent())
+
+ statusResp, err := a.client.Do(statusReq)
+ if err != nil {
+ fmt.Printf("\rStatus check failed, retrying...")
+ continue
+ }
+
+ if statusResp.StatusCode != 200 {
+ statusResp.Body.Close()
+ fmt.Printf("\rStatus check failed (status %d), retrying...", statusResp.StatusCode)
+ continue
+ }
+
+ var status DoubleDoubleStatusResponse
+ if err := json.NewDecoder(statusResp.Body).Decode(&status); err != nil {
+ statusResp.Body.Close()
+ fmt.Printf("\rInvalid JSON response, retrying...")
+ continue
+ }
+ statusResp.Body.Close()
+
+ if status.Status == "done" {
+ fmt.Println("\nDownload ready!")
+
+ // Build download URL
+ fileURL := status.URL
+ if strings.HasPrefix(fileURL, "./") {
+ fileURL = fmt.Sprintf("%s/%s", baseURL, fileURL[2:])
+ } else if strings.HasPrefix(fileURL, "/") {
+ fileURL = fmt.Sprintf("%s%s", baseURL, fileURL)
+ }
+
+ trackName := status.Current.Name
+ artist := status.Current.Artist
+
+ fmt.Printf("Downloading: %s - %s\n", artist, trackName)
+
+ // Download file
+ downloadReq, err := http.NewRequest("GET", fileURL, nil)
+ if err != nil {
+ lastError = fmt.Errorf("failed to create download request: %w", err)
+ break
+ }
+
+ downloadReq.Header.Set("User-Agent", a.getRandomUserAgent())
+
+ fileResp, err := a.client.Do(downloadReq)
+ if err != nil {
+ lastError = fmt.Errorf("failed to download file: %w", err)
+ break
+ }
+ defer fileResp.Body.Close()
+
+ if fileResp.StatusCode != 200 {
+ lastError = fmt.Errorf("download failed with status %d", fileResp.StatusCode)
+ break
+ }
+
+ // Generate filename
+ fileName := fmt.Sprintf("%s - %s.flac", artist, trackName)
+ for _, char := range `<>:"/\|?*` {
+ fileName = strings.ReplaceAll(fileName, string(char), "")
+ }
+ fileName = strings.TrimSpace(fileName)
+
+ filePath := filepath.Join(outputDir, fileName)
+
+ // Save file
+ out, err := os.Create(filePath)
+ if err != nil {
+ lastError = fmt.Errorf("failed to create file: %w", err)
+ break
+ }
+ defer out.Close()
+
+ fmt.Println("Downloading...")
+ // Use progress writer to track download
+ pw := NewProgressWriter(out)
+ _, err = io.Copy(pw, fileResp.Body)
+ if err != nil {
+ out.Close()
+ return "", fmt.Errorf("failed to write file: %w", err)
+ }
+
+ // Print final size
+ fmt.Printf("\rDownloaded: %.2f MB (Complete)\n", float64(pw.GetTotal())/(1024*1024))
+ fmt.Println("Download complete!")
+ return filePath, nil
+
+ } else if status.Status == "error" {
+ errorMsg := status.FriendlyStatus
+ if errorMsg == "" {
+ errorMsg = "Unknown error"
+ }
+ lastError = fmt.Errorf("processing failed: %s", errorMsg)
+ break
+ } else {
+ // Still processing
+ friendlyStatus := status.FriendlyStatus
+ if friendlyStatus == "" {
+ friendlyStatus = status.Status
+ }
+ fmt.Printf("\r%s...", friendlyStatus)
+ }
+ }
+
+ if elapsed >= maxWait {
+ lastError = fmt.Errorf("download timeout")
+ fmt.Printf("\nError with %s region: %v\n", region, lastError)
+ continue
+ }
+
+ if lastError != nil {
+ fmt.Printf("\nError with %s region: %v\n", region, lastError)
+ }
+ }
+
+ return "", fmt.Errorf("all regions failed. Last error: %v", lastError)
+}
+
+func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName string, useAlbumTrackNumber bool) (string, error) {
+ // Create output directory if needed
+ if outputDir != "." {
+ if err := os.MkdirAll(outputDir, 0755); err != nil {
+ return "", fmt.Errorf("failed to create output directory: %w", err)
+ }
+ }
+
+ // Get Amazon URL from Spotify track ID
+ amazonURL, err := a.GetAmazonURLFromSpotify(spotifyTrackID)
+ if err != nil {
+ return "", err
+ }
+
+ // Download from service
+ filePath, err := a.DownloadFromService(amazonURL, outputDir)
+ if err != nil {
+ return "", err
+ }
+
+ // File already has embedded metadata, just rename if needed
+ if spotifyTrackName != "" && spotifyArtistName != "" {
+ safeArtist := sanitizeFilename(spotifyArtistName)
+ safeTitle := sanitizeFilename(spotifyTrackName)
+
+ // Build filename based on format settings
+ var newFilename string
+ switch filenameFormat {
+ case "artist-title":
+ newFilename = fmt.Sprintf("%s - %s", safeArtist, safeTitle)
+ case "title":
+ newFilename = safeTitle
+ default: // "title-artist"
+ newFilename = fmt.Sprintf("%s - %s", safeTitle, safeArtist)
+ }
+
+ // Add track number prefix if enabled
+ if includeTrackNumber && position > 0 {
+ newFilename = fmt.Sprintf("%02d. %s", position, newFilename)
+ }
+
+ newFilename = newFilename + ".flac"
+ newFilePath := filepath.Join(outputDir, newFilename)
+
+ // Rename file
+ if err := os.Rename(filePath, newFilePath); err != nil {
+ fmt.Printf("Warning: Failed to rename file: %v\n", err)
+ } else {
+ filePath = newFilePath
+ fmt.Printf("Renamed to: %s\n", newFilename)
+ }
+ }
+
+ fmt.Println("Done")
+ return filePath, nil
+}
diff --git a/backend/spotify_metadata.go b/backend/spotify_metadata.go
index 9873791..50ae6ff 100644
--- a/backend/spotify_metadata.go
+++ b/backend/spotify_metadata.go
@@ -66,6 +66,7 @@ type TrackMetadata struct {
TrackNumber int `json:"track_number"`
ExternalURL string `json:"external_urls"`
ISRC string `json:"isrc"`
+ SpotifyID string `json:"spotify_id,omitempty"`
}
// AlbumTrackMetadata holds per-track info for album / playlist formatting.
@@ -80,6 +81,7 @@ type AlbumTrackMetadata struct {
ExternalURL string `json:"external_urls"`
ISRC string `json:"isrc"`
AlbumType string `json:"album_type,omitempty"`
+ SpotifyID string `json:"spotify_id,omitempty"`
}
type TrackResponse struct {
@@ -487,6 +489,7 @@ func (c *SpotifyMetadataClient) formatPlaylistData(raw *playlistRaw) PlaylistRes
TrackNumber: item.Track.TrackNumber,
ExternalURL: item.Track.ExternalURL.Spotify,
ISRC: item.Track.ExternalID.ISRC,
+ SpotifyID: item.Track.ID,
})
}
@@ -523,6 +526,7 @@ func (c *SpotifyMetadataClient) formatAlbumData(ctx context.Context, raw *albumR
TrackNumber: item.TrackNumber,
ExternalURL: item.ExternalURL.Spotify,
ISRC: isrc,
+ SpotifyID: item.ID,
})
}
@@ -588,6 +592,7 @@ func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context,
TrackNumber: tr.TrackNumber,
ExternalURL: tr.ExternalURL.Spotify,
ISRC: isrc,
+ SpotifyID: tr.ID,
})
}
}
@@ -628,6 +633,7 @@ func formatTrackData(raw *trackFull) TrackResponse {
TrackNumber: raw.TrackNumber,
ExternalURL: raw.ExternalURL.Spotify,
ISRC: raw.ExternalID.ISRC,
+ SpotifyID: raw.ID,
},
}
}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 3b78a7b..8f7c4b6 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -40,7 +40,7 @@ function App() {
const [hasUpdate, setHasUpdate] = useState(false);
const ITEMS_PER_PAGE = 50;
- const CURRENT_VERSION = "5.9";
+ const CURRENT_VERSION = "6.0";
const download = useDownload();
const metadata = useMetadata();
@@ -78,7 +78,7 @@ function App() {
const checkForUpdates = async () => {
try {
const response = await fetch(
- "https://raw.githubusercontent.com/afkarxyz/SpotiFLAC/refs/heads/main/version.json"
+ "https://cdn.jsdelivr.net/gh/afkarxyz/SpotiFLAC@refs/heads/main/version.json"
);
const data = await response.json();
const latestVersion = data.version;
diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx
index 1365340..a8164cf 100644
--- a/frontend/src/components/Header.tsx
+++ b/frontend/src/components/Header.tsx
@@ -49,7 +49,7 @@ export function Header({ version, hasUpdate }: HeaderProps) {
- Get Spotify tracks in true FLAC from Tidal, Deezer & Qobuz — no account required.
+ Get Spotify tracks in true FLAC from Tidal, Deezer, Qobuz & Amazon Music — no account required.
diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx
index 9296d8a..375eb20 100644
--- a/frontend/src/components/Settings.tsx
+++ b/frontend/src/components/Settings.tsx
@@ -46,6 +46,13 @@ const QobuzIcon = () => (
);
+const AmazonIcon = () => (
+
+
+
+
+);
+
export function Settings() {
const [open, setOpen] = useState(false);
const [savedSettings, setSavedSettings] = useState
(getSettings());
@@ -156,7 +163,7 @@ export function Settings() {
setTempSettings((prev) => ({ ...prev, downloadPath: value }));
};
- const handleDownloaderChange = (value: "auto" | "deezer" | "tidal" | "qobuz") => {
+ const handleDownloaderChange = (value: "auto" | "deezer" | "tidal" | "qobuz" | "amazon") => {
setTempSettings((prev) => ({ ...prev, downloader: value }));
};
@@ -246,6 +253,12 @@ export function Settings() {
Qobuz
+
+
+
+ Amazon Music
+
+
diff --git a/frontend/src/components/TrackInfo.tsx b/frontend/src/components/TrackInfo.tsx
index f7f9095..8002772 100644
--- a/frontend/src/components/TrackInfo.tsx
+++ b/frontend/src/components/TrackInfo.tsx
@@ -9,7 +9,7 @@ interface TrackInfoProps {
isDownloading: boolean;
downloadingTrack: string | null;
isDownloaded: boolean;
- onDownload: (isrc: string, name: string, artists: string) => void;
+ onDownload: (isrc: string, name: string, artists: string, albumName?: string, spotifyId?: string) => void;
onOpenFolder: () => void;
}
@@ -50,7 +50,7 @@ export function TrackInfo({
{track.isrc && (
onDownload(track.isrc, track.name, track.artists)}
+ onClick={() => onDownload(track.isrc, track.name, track.artists, track.album_name, track.spotify_id)}
disabled={isDownloading || downloadingTrack === track.isrc}
>
{downloadingTrack === track.isrc ? (
diff --git a/frontend/src/hooks/useDownload.ts b/frontend/src/hooks/useDownload.ts
index c9afb1d..fc5d941 100644
--- a/frontend/src/hooks/useDownload.ts
+++ b/frontend/src/hooks/useDownload.ts
@@ -25,7 +25,8 @@ export function useDownload() {
albumName?: string,
playlistName?: string,
isArtistDiscography?: boolean,
- position?: number
+ position?: number,
+ spotifyId?: string
) => {
let service = settings.downloader;
@@ -108,7 +109,7 @@ export function useDownload() {
return await downloadTrack({
isrc,
- service: service as "deezer" | "tidal" | "qobuz",
+ service: service as "deezer" | "tidal" | "qobuz" | "amazon",
query,
track_name: trackName,
artist_name: artistName,
@@ -118,6 +119,7 @@ export function useDownload() {
track_number: settings.trackNumber,
position,
use_album_track_number: useAlbumTrackNumber,
+ spotify_id: spotifyId,
});
};
@@ -125,7 +127,8 @@ export function useDownload() {
isrc: string,
trackName?: string,
artistName?: string,
- albumName?: string
+ albumName?: string,
+ spotifyId?: string
) => {
if (!isrc) {
toast.error("No ISRC found for this track");
@@ -145,7 +148,8 @@ export function useDownload() {
albumName,
undefined,
false,
- undefined // Don't pass position for single track
+ undefined, // Don't pass position for single track
+ spotifyId
);
if (response.success) {
@@ -212,7 +216,8 @@ export function useDownload() {
track?.album_name,
playlistName,
isArtistDiscography,
- i + 1 // Sequential position based on selection order
+ i + 1, // Sequential position based on selection order
+ track?.spotify_id
);
if (response.success) {
@@ -284,7 +289,8 @@ export function useDownload() {
track.album_name,
playlistName,
isArtistDiscography,
- i + 1
+ i + 1,
+ track.spotify_id
);
if (response.success) {
diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts
index 16dc68c..341f063 100644
--- a/frontend/src/lib/settings.ts
+++ b/frontend/src/lib/settings.ts
@@ -2,7 +2,7 @@ import { GetDefaults } from "../../wailsjs/go/main/App";
export interface Settings {
downloadPath: string;
- downloader: "auto" | "deezer" | "tidal" | "qobuz";
+ downloader: "auto" | "deezer" | "tidal" | "qobuz" | "amazon";
theme: string;
themeMode: "auto" | "light" | "dark";
filenameFormat: "title-artist" | "artist-title" | "title";
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index 54c2eab..087ac5a 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -9,6 +9,7 @@ export interface TrackMetadata {
external_urls: string;
isrc: string;
album_type?: string;
+ spotify_id?: string;
}
export interface TrackResponse {
@@ -97,7 +98,7 @@ export type SpotifyMetadataResponse =
export interface DownloadRequest {
isrc: string;
- service: "deezer" | "tidal" | "qobuz";
+ service: "deezer" | "tidal" | "qobuz" | "amazon";
query?: string;
track_name?: string;
artist_name?: string;
@@ -110,6 +111,7 @@ export interface DownloadRequest {
track_number?: boolean;
position?: number;
use_album_track_number?: boolean;
+ spotify_id?: string;
}
export interface DownloadResponse {
diff --git a/wails.json b/wails.json
index 1f281f6..cd6816f 100644
--- a/wails.json
+++ b/wails.json
@@ -11,7 +11,7 @@
},
"info": {
"productName": "SpotiFLAC",
- "productVersion": "5.9"
+ "productVersion": "6.0"
},
"wailsjsdir": "./frontend",
"assetdir": "./frontend/dist",