v5.7-beta3
This commit is contained in:
+37
-17
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -57,7 +58,9 @@ func NewDeezerDownloader() *DeezerDownloader {
|
||||
}
|
||||
|
||||
func (d *DeezerDownloader) GetTrackByISRC(isrc string) (*DeezerTrack, error) {
|
||||
url := fmt.Sprintf("https://api.deezer.com/2.0/track/isrc:%s", isrc)
|
||||
// Decode base64 API URL
|
||||
apiBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9hcGkuZGVlemVyLmNvbS8yLjAvdHJhY2svaXNyYzo=")
|
||||
url := fmt.Sprintf("%s%s", string(apiBase), isrc)
|
||||
|
||||
resp, err := d.client.Get(url)
|
||||
if err != nil {
|
||||
@@ -82,7 +85,9 @@ func (d *DeezerDownloader) GetTrackByISRC(isrc string) (*DeezerTrack, error) {
|
||||
}
|
||||
|
||||
func (d *DeezerDownloader) GetDownloadURL(trackID int64) (string, error) {
|
||||
url := fmt.Sprintf("https://api.deezmate.com/dl/%d", trackID)
|
||||
// Decode base64 API URL
|
||||
apiBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9hcGkuZGVlem1hdGUuY29tL2RsLw==")
|
||||
url := fmt.Sprintf("%s%d", string(apiBase), trackID)
|
||||
|
||||
resp, err := d.client.Get(url)
|
||||
if err != nil {
|
||||
@@ -183,7 +188,7 @@ func buildFilename(title, artist string, trackNumber int, format string, include
|
||||
return filename + ".flac"
|
||||
}
|
||||
|
||||
func (d *DeezerDownloader) DownloadByISRC(isrc, outputDir, filenameFormat string, includeTrackNumber bool) error {
|
||||
func (d *DeezerDownloader) DownloadByISRC(isrc, outputDir, filenameFormat string, includeTrackNumber bool, spotifyTrackName, spotifyArtistName, spotifyAlbumName string) error {
|
||||
fmt.Printf("Fetching track info for ISRC: %s\n", isrc)
|
||||
|
||||
track, err := d.GetTrackByISRC(isrc)
|
||||
@@ -191,21 +196,36 @@ func (d *DeezerDownloader) DownloadByISRC(isrc, outputDir, filenameFormat string
|
||||
return err
|
||||
}
|
||||
|
||||
artists := track.Artist.Name
|
||||
if len(track.Contributors) > 0 {
|
||||
var mainArtists []string
|
||||
for _, contrib := range track.Contributors {
|
||||
if contrib.Role == "Main" {
|
||||
mainArtists = append(mainArtists, contrib.Name)
|
||||
// Use Spotify metadata if provided, otherwise fallback to Deezer metadata
|
||||
artists := spotifyArtistName
|
||||
trackTitle := spotifyTrackName
|
||||
albumTitle := spotifyAlbumName
|
||||
|
||||
if artists == "" {
|
||||
artists = track.Artist.Name
|
||||
if len(track.Contributors) > 0 {
|
||||
var mainArtists []string
|
||||
for _, contrib := range track.Contributors {
|
||||
if contrib.Role == "Main" {
|
||||
mainArtists = append(mainArtists, contrib.Name)
|
||||
}
|
||||
}
|
||||
if len(mainArtists) > 0 {
|
||||
artists = strings.Join(mainArtists, ", ")
|
||||
}
|
||||
}
|
||||
if len(mainArtists) > 0 {
|
||||
artists = strings.Join(mainArtists, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Found track: %s - %s\n", artists, track.Title)
|
||||
fmt.Printf("Album: %s\n", track.Album.Title)
|
||||
if trackTitle == "" {
|
||||
trackTitle = track.Title
|
||||
}
|
||||
|
||||
if albumTitle == "" {
|
||||
albumTitle = track.Album.Title
|
||||
}
|
||||
|
||||
fmt.Printf("Found track: %s - %s\n", artists, trackTitle)
|
||||
fmt.Printf("Album: %s\n", albumTitle)
|
||||
|
||||
downloadURL, err := d.GetDownloadURL(track.ID)
|
||||
if err != nil {
|
||||
@@ -213,7 +233,7 @@ func (d *DeezerDownloader) DownloadByISRC(isrc, outputDir, filenameFormat string
|
||||
}
|
||||
|
||||
safeArtist := sanitizeFilename(artists)
|
||||
safeTitle := sanitizeFilename(track.Title)
|
||||
safeTitle := sanitizeFilename(trackTitle)
|
||||
|
||||
// Build filename based on format settings
|
||||
filename := buildFilename(safeTitle, safeArtist, track.TrackPos, filenameFormat, includeTrackNumber)
|
||||
@@ -239,9 +259,9 @@ func (d *DeezerDownloader) DownloadByISRC(isrc, outputDir, filenameFormat string
|
||||
|
||||
fmt.Println("Embedding metadata and cover art...")
|
||||
metadata := Metadata{
|
||||
Title: track.Title,
|
||||
Title: trackTitle,
|
||||
Artist: artists,
|
||||
Album: track.Album.Title,
|
||||
Album: albumTitle,
|
||||
Date: track.ReleaseDate,
|
||||
TrackNumber: track.TrackPos,
|
||||
DiscNumber: track.DiskNumber,
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QobuzDownloader struct {
|
||||
client *http.Client
|
||||
appID string
|
||||
}
|
||||
|
||||
type QobuzSearchResponse struct {
|
||||
Query string `json:"query"`
|
||||
Tracks struct {
|
||||
Limit int `json:"limit"`
|
||||
Offset int `json:"offset"`
|
||||
Total int `json:"total"`
|
||||
Items []QobuzTrack `json:"items"`
|
||||
} `json:"tracks"`
|
||||
}
|
||||
|
||||
type QobuzTrack struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Version string `json:"version"`
|
||||
Duration int `json:"duration"`
|
||||
TrackNumber int `json:"track_number"`
|
||||
MediaNumber int `json:"media_number"`
|
||||
ISRC string `json:"isrc"`
|
||||
Copyright string `json:"copyright"`
|
||||
MaximumBitDepth int `json:"maximum_bit_depth"`
|
||||
MaximumSamplingRate float64 `json:"maximum_sampling_rate"`
|
||||
Hires bool `json:"hires"`
|
||||
HiresStreamable bool `json:"hires_streamable"`
|
||||
ReleaseDateOriginal string `json:"release_date_original"`
|
||||
Performer struct {
|
||||
Name string `json:"name"`
|
||||
ID int64 `json:"id"`
|
||||
} `json:"performer"`
|
||||
Album struct {
|
||||
Title string `json:"title"`
|
||||
ID string `json:"id"`
|
||||
Image struct {
|
||||
Small string `json:"small"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Large string `json:"large"`
|
||||
} `json:"image"`
|
||||
Artist struct {
|
||||
Name string `json:"name"`
|
||||
ID int64 `json:"id"`
|
||||
} `json:"artist"`
|
||||
Label struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"label"`
|
||||
} `json:"album"`
|
||||
}
|
||||
|
||||
type QobuzStreamResponse struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func NewQobuzDownloader() *QobuzDownloader {
|
||||
return &QobuzDownloader{
|
||||
client: &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
},
|
||||
appID: "798273057",
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) SearchByISRC(isrc string) (*QobuzTrack, error) {
|
||||
// Decode base64 API URL
|
||||
apiBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly93d3cucW9idXouY29tL2FwaS5qc29uLzAuMi90cmFjay9zZWFyY2g/cXVlcnk9")
|
||||
url := fmt.Sprintf("%s%s&limit=1&app_id=%s", string(apiBase), isrc, q.appID)
|
||||
|
||||
resp, err := q.client.Get(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search track: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var searchResp QobuzSearchResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
if len(searchResp.Tracks.Items) == 0 {
|
||||
return nil, fmt.Errorf("track not found for ISRC: %s", isrc)
|
||||
}
|
||||
|
||||
return &searchResp.Tracks.Items[0], nil
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) GetDownloadURL(trackID int64, quality string) (string, error) {
|
||||
// Map quality to Qobuz quality code
|
||||
// Qobuz uses: 5 (MP3 320), 6 (FLAC 16-bit), 7 (FLAC 24-bit), 27 (Hi-Res)
|
||||
qualityCode := "27" // Default to Hi-Res
|
||||
|
||||
fmt.Printf("Getting download URL for track ID: %d\n", trackID)
|
||||
|
||||
// Decode base64 API URLs
|
||||
primaryBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9kYWIueWVldC5zdS9hcGkvc3RyZWFtP3RyYWNrSWQ9")
|
||||
|
||||
// Try primary API first
|
||||
primaryURL := fmt.Sprintf("%s%d&quality=%s", string(primaryBase), trackID, qualityCode)
|
||||
|
||||
resp, err := q.client.Get(primaryURL)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf("Primary API response: %s\n", string(body))
|
||||
|
||||
var streamResp QobuzStreamResponse
|
||||
if err := json.Unmarshal(body, &streamResp); err == nil && streamResp.URL != "" {
|
||||
fmt.Printf("Got download URL from primary API\n")
|
||||
return streamResp.URL, nil
|
||||
}
|
||||
}
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
// Fallback to secondary API
|
||||
fmt.Println("Primary API failed, trying fallback...")
|
||||
fallbackBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9kYWJtdXNpYy54eXovYXBpL3N0cmVhbT90cmFja0lkPQ==")
|
||||
fallbackURL := fmt.Sprintf("%s%d&quality=%s", string(fallbackBase), trackID, qualityCode)
|
||||
|
||||
resp, err = q.client.Get(fallbackURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get download URL: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf("Fallback API error response: %s\n", string(body))
|
||||
return "", fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf("Fallback API response: %s\n", string(body))
|
||||
|
||||
var streamResp QobuzStreamResponse
|
||||
if err := json.Unmarshal(body, &streamResp); err != nil {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
if streamResp.URL == "" {
|
||||
return "", fmt.Errorf("no download URL available")
|
||||
}
|
||||
|
||||
fmt.Printf("Got download URL from fallback API\n")
|
||||
return streamResp.URL, nil
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) DownloadFile(url, filepath string) error {
|
||||
fmt.Println("Starting file download...")
|
||||
resp, err := q.client.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download file: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("download failed with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
fmt.Printf("Creating file: %s\n", filepath)
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file: %w", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
fmt.Println("Writing file content...")
|
||||
written, err := io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Downloaded %d bytes\n", written)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) DownloadCoverArt(coverURL, filepath string) error {
|
||||
if coverURL == "" {
|
||||
return fmt.Errorf("no cover URL provided")
|
||||
}
|
||||
|
||||
resp, err := q.client.Get(coverURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to download cover: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("cover download failed with status %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
out, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create cover file: %w", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return err
|
||||
}
|
||||
|
||||
func buildQobuzFilename(title, artist string, trackNumber int, format string, includeTrackNumber bool) string {
|
||||
var filename string
|
||||
|
||||
// Build base filename based on format
|
||||
switch format {
|
||||
case "artist-title":
|
||||
filename = fmt.Sprintf("%s - %s", artist, title)
|
||||
case "title":
|
||||
filename = title
|
||||
default: // "title-artist"
|
||||
filename = fmt.Sprintf("%s - %s", title, artist)
|
||||
}
|
||||
|
||||
// Add track number prefix if enabled
|
||||
if includeTrackNumber && trackNumber > 0 {
|
||||
filename = fmt.Sprintf("%02d. %s", trackNumber, filename)
|
||||
}
|
||||
|
||||
return filename + ".flac"
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) DownloadByISRC(isrc, outputDir, quality, filenameFormat string, includeTrackNumber bool, spotifyTrackName, spotifyArtistName, spotifyAlbumName string) error {
|
||||
fmt.Printf("Fetching track info for ISRC: %s\n", isrc)
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
if outputDir != "." {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
track, err := q.SearchByISRC(isrc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use Spotify metadata if provided, otherwise fallback to Qobuz metadata
|
||||
artists := spotifyArtistName
|
||||
trackTitle := spotifyTrackName
|
||||
albumTitle := spotifyAlbumName
|
||||
|
||||
if artists == "" {
|
||||
artists = track.Performer.Name
|
||||
if track.Album.Artist.Name != "" {
|
||||
artists = track.Album.Artist.Name
|
||||
}
|
||||
}
|
||||
|
||||
if trackTitle == "" {
|
||||
trackTitle = track.Title
|
||||
if track.Version != "" && track.Version != "null" {
|
||||
trackTitle = fmt.Sprintf("%s (%s)", track.Title, track.Version)
|
||||
}
|
||||
}
|
||||
|
||||
if albumTitle == "" {
|
||||
albumTitle = track.Album.Title
|
||||
}
|
||||
|
||||
fmt.Printf("Found track: %s - %s\n", artists, trackTitle)
|
||||
fmt.Printf("Album: %s\n", albumTitle)
|
||||
|
||||
qualityInfo := "Standard"
|
||||
if track.Hires {
|
||||
qualityInfo = fmt.Sprintf("Hi-Res (%d-bit / %.1f kHz)", track.MaximumBitDepth, track.MaximumSamplingRate)
|
||||
}
|
||||
fmt.Printf("Quality: %s\n", qualityInfo)
|
||||
|
||||
fmt.Println("Getting download URL...")
|
||||
downloadURL, err := q.GetDownloadURL(track.ID, quality)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get download URL: %w", err)
|
||||
}
|
||||
|
||||
if downloadURL == "" {
|
||||
return fmt.Errorf("received empty download URL")
|
||||
}
|
||||
|
||||
// Show partial URL for security
|
||||
urlPreview := downloadURL
|
||||
if len(downloadURL) > 60 {
|
||||
urlPreview = downloadURL[:60] + "..."
|
||||
}
|
||||
fmt.Printf("Download URL obtained: %s\n", urlPreview)
|
||||
|
||||
safeArtist := sanitizeFilename(artists)
|
||||
safeTitle := sanitizeFilename(trackTitle)
|
||||
|
||||
// Build filename based on format settings
|
||||
filename := buildQobuzFilename(safeTitle, safeArtist, track.TrackNumber, filenameFormat, includeTrackNumber)
|
||||
filepath := filepath.Join(outputDir, filename)
|
||||
|
||||
fmt.Printf("Downloading FLAC file to: %s\n", filepath)
|
||||
if err := q.DownloadFile(downloadURL, filepath); err != nil {
|
||||
return fmt.Errorf("failed to download file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Downloaded: %s\n", filepath)
|
||||
|
||||
coverPath := ""
|
||||
if track.Album.Image.Large != "" {
|
||||
coverPath = filepath + ".cover.jpg"
|
||||
fmt.Println("Downloading cover art...")
|
||||
if err := q.DownloadCoverArt(track.Album.Image.Large, coverPath); err != nil {
|
||||
fmt.Printf("Warning: Failed to download cover art: %v\n", err)
|
||||
} else {
|
||||
defer os.Remove(coverPath)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Embedding metadata and cover art...")
|
||||
|
||||
releaseYear := ""
|
||||
if len(track.ReleaseDateOriginal) >= 4 {
|
||||
releaseYear = track.ReleaseDateOriginal[:4]
|
||||
}
|
||||
|
||||
metadata := Metadata{
|
||||
Title: trackTitle,
|
||||
Artist: artists,
|
||||
Album: albumTitle,
|
||||
Date: releaseYear,
|
||||
TrackNumber: track.TrackNumber,
|
||||
DiscNumber: track.MediaNumber,
|
||||
ISRC: track.ISRC,
|
||||
}
|
||||
|
||||
if err := EmbedMetadata(filepath, metadata, coverPath); err != nil {
|
||||
return fmt.Errorf("failed to embed metadata: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Metadata embedded successfully!")
|
||||
return nil
|
||||
}
|
||||
+46
-25
@@ -81,7 +81,9 @@ func NewTidalDownloader(apiURL string) *TidalDownloader {
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) GetAvailableAPIs() ([]string, error) {
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/afkarxyz/SpotiFLAC/refs/heads/main/tidal.json")
|
||||
// Decode base64 API URL
|
||||
apiURL, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Fma2FyeHl6L1Nwb3RpRkxBQy9yZWZzL2hlYWRzL21haW4vdGlkYWwuanNvbg==")
|
||||
resp, err := http.Get(string(apiURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch API list: %w", err)
|
||||
}
|
||||
@@ -107,7 +109,9 @@ func (t *TidalDownloader) GetAvailableAPIs() ([]string, error) {
|
||||
func (t *TidalDownloader) GetAccessToken() (string, error) {
|
||||
data := fmt.Sprintf("client_id=%s&grant_type=client_credentials", t.clientID)
|
||||
|
||||
req, err := http.NewRequest("POST", "https://auth.tidal.com/v1/oauth2/token", strings.NewReader(data))
|
||||
// Decode base64 API URL
|
||||
authURL, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9hdXRoLnRpZGFsLmNvbS92MS9vYXV0aDIvdG9rZW4=")
|
||||
req, err := http.NewRequest("POST", string(authURL), strings.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -142,8 +146,9 @@ func (t *TidalDownloader) SearchTracks(query string) (*TidalSearchResponse, erro
|
||||
return nil, fmt.Errorf("failed to get access token: %w", err)
|
||||
}
|
||||
|
||||
// URL encode the query parameter
|
||||
searchURL := fmt.Sprintf("https://api.tidal.com/v1/search/tracks?query=%s&limit=25&offset=0&countryCode=US", url.QueryEscape(query))
|
||||
// Decode base64 API URL and encode the query parameter
|
||||
searchBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9hcGkudGlkYWwuY29tL3YxL3NlYXJjaC90cmFja3M/cXVlcnk9")
|
||||
searchURL := fmt.Sprintf("%s%s&limit=25&offset=0&countryCode=US", string(searchBase), url.QueryEscape(query))
|
||||
|
||||
req, err := http.NewRequest("GET", searchURL, nil)
|
||||
if err != nil {
|
||||
@@ -265,7 +270,9 @@ func (t *TidalDownloader) GetDownloadURL(trackID int64, quality string) (string,
|
||||
|
||||
func (t *TidalDownloader) DownloadAlbumArt(albumID string) ([]byte, error) {
|
||||
albumID = strings.ReplaceAll(albumID, "-", "/")
|
||||
artURL := fmt.Sprintf("https://resources.tidal.com/images/%s/1280x1280.jpg", albumID)
|
||||
// Decode base64 API URL
|
||||
imageBase, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly9yZXNvdXJjZXMudGlkYWwuY29tL2ltYWdlcy8=")
|
||||
artURL := fmt.Sprintf("%s%s/1280x1280.jpg", string(imageBase), albumID)
|
||||
|
||||
resp, err := t.client.Get(artURL)
|
||||
if err != nil {
|
||||
@@ -306,7 +313,7 @@ func (t *TidalDownloader) DownloadFile(url, filepath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) Download(query, isrc, outputDir, quality, filenameFormat string, includeTrackNumber bool) (string, error) {
|
||||
func (t *TidalDownloader) Download(query, isrc, outputDir, quality, filenameFormat string, includeTrackNumber bool, spotifyTrackName, spotifyArtistName, spotifyAlbumName string) (string, error) {
|
||||
if outputDir != "." {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("directory error: %w", err)
|
||||
@@ -322,26 +329,40 @@ func (t *TidalDownloader) Download(query, isrc, outputDir, quality, filenameForm
|
||||
return "", fmt.Errorf("no track ID found")
|
||||
}
|
||||
|
||||
var artists []string
|
||||
if len(trackInfo.Artists) > 0 {
|
||||
for _, artist := range trackInfo.Artists {
|
||||
if artist.Name != "" {
|
||||
artists = append(artists, artist.Name)
|
||||
}
|
||||
}
|
||||
} else if trackInfo.Artist.Name != "" {
|
||||
artists = append(artists, trackInfo.Artist.Name)
|
||||
}
|
||||
// Use Spotify metadata if provided, otherwise fallback to Tidal metadata
|
||||
artistName := spotifyArtistName
|
||||
trackTitle := spotifyTrackName
|
||||
albumTitle := spotifyAlbumName
|
||||
|
||||
artistName := "Unknown Artist"
|
||||
if len(artists) > 0 {
|
||||
artistName = strings.Join(artists, ", ")
|
||||
if artistName == "" {
|
||||
var artists []string
|
||||
if len(trackInfo.Artists) > 0 {
|
||||
for _, artist := range trackInfo.Artists {
|
||||
if artist.Name != "" {
|
||||
artists = append(artists, artist.Name)
|
||||
}
|
||||
}
|
||||
} else if trackInfo.Artist.Name != "" {
|
||||
artists = append(artists, trackInfo.Artist.Name)
|
||||
}
|
||||
|
||||
artistName = "Unknown Artist"
|
||||
if len(artists) > 0 {
|
||||
artistName = strings.Join(artists, ", ")
|
||||
}
|
||||
}
|
||||
artistName = sanitizeFilename(artistName)
|
||||
|
||||
trackTitle := sanitizeFilename(trackInfo.Title)
|
||||
if trackTitle == "" {
|
||||
trackTitle = fmt.Sprintf("track_%d", trackInfo.ID)
|
||||
trackTitle = trackInfo.Title
|
||||
if trackTitle == "" {
|
||||
trackTitle = fmt.Sprintf("track_%d", trackInfo.ID)
|
||||
}
|
||||
}
|
||||
trackTitle = sanitizeFilename(trackTitle)
|
||||
|
||||
if albumTitle == "" {
|
||||
albumTitle = trackInfo.Album.Title
|
||||
}
|
||||
|
||||
// Build filename based on format settings
|
||||
@@ -387,9 +408,9 @@ func (t *TidalDownloader) Download(query, isrc, outputDir, quality, filenameForm
|
||||
}
|
||||
|
||||
metadata := Metadata{
|
||||
Title: trackInfo.Title,
|
||||
Title: trackTitle,
|
||||
Artist: artistName,
|
||||
Album: trackInfo.Album.Title,
|
||||
Album: albumTitle,
|
||||
Date: releaseYear,
|
||||
TrackNumber: trackInfo.TrackNumber,
|
||||
DiscNumber: trackInfo.VolumeNumber,
|
||||
@@ -406,7 +427,7 @@ func (t *TidalDownloader) Download(query, isrc, outputDir, quality, filenameForm
|
||||
return outputFilename, nil
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) DownloadWithFallback(query, isrc, outputDir, quality, filenameFormat string, includeTrackNumber bool) (string, error) {
|
||||
func (t *TidalDownloader) DownloadWithFallback(query, isrc, outputDir, quality, filenameFormat string, includeTrackNumber bool, spotifyTrackName, spotifyArtistName, spotifyAlbumName string) (string, error) {
|
||||
apis, err := t.GetAvailableAPIs()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no APIs available for fallback: %w", err)
|
||||
@@ -418,7 +439,7 @@ func (t *TidalDownloader) DownloadWithFallback(query, isrc, outputDir, quality,
|
||||
|
||||
fallbackDownloader := NewTidalDownloader(apiURL)
|
||||
|
||||
result, err := fallbackDownloader.Download(query, isrc, outputDir, quality, filenameFormat, includeTrackNumber)
|
||||
result, err := fallbackDownloader.Download(query, isrc, outputDir, quality, filenameFormat, includeTrackNumber, spotifyTrackName, spotifyArtistName, spotifyAlbumName)
|
||||
if err == nil {
|
||||
fmt.Printf("✓ Success with: %s\n", apiURL)
|
||||
return result, nil
|
||||
|
||||
Reference in New Issue
Block a user