v7.0.9
This commit is contained in:
+45
-5
@@ -261,7 +261,7 @@ func (a *AmazonDownloader) DownloadFromService(amazonURL, outputDir, quality str
|
||||
return a.DownloadFromAfkarXYZ(amazonURL, outputDir, quality)
|
||||
}
|
||||
|
||||
func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string) (string, error) {
|
||||
func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, useFirstArtistOnly bool) (string, error) {
|
||||
|
||||
if outputDir != "." {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
@@ -270,7 +270,13 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
||||
}
|
||||
|
||||
if spotifyTrackName != "" && spotifyArtistName != "" {
|
||||
expectedFilename := BuildExpectedFilename(spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyDiscNumber, false)
|
||||
filenameArtist := spotifyArtistName
|
||||
filenameAlbumArtist := spotifyAlbumArtist
|
||||
if useFirstArtistOnly {
|
||||
filenameArtist = GetFirstArtist(spotifyArtistName)
|
||||
filenameAlbumArtist = GetFirstArtist(spotifyAlbumArtist)
|
||||
}
|
||||
expectedFilename := BuildExpectedFilename(spotifyTrackName, filenameArtist, spotifyAlbumName, filenameAlbumArtist, spotifyReleaseDate, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyDiscNumber, false)
|
||||
expectedPath := filepath.Join(outputDir, expectedFilename)
|
||||
|
||||
if fileInfo, err := os.Stat(expectedPath); err == nil && fileInfo.Size() > 0 {
|
||||
@@ -279,6 +285,26 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
||||
}
|
||||
}
|
||||
|
||||
isrcChan := make(chan string, 1)
|
||||
if spotifyURL != "" {
|
||||
go func() {
|
||||
var isrc string
|
||||
parts := strings.Split(spotifyURL, "/")
|
||||
if len(parts) > 0 {
|
||||
sID := strings.Split(parts[len(parts)-1], "?")[0]
|
||||
if sID != "" {
|
||||
client := NewSongLinkClient()
|
||||
if val, err := client.GetISRC(sID); err == nil {
|
||||
isrc = val
|
||||
}
|
||||
}
|
||||
}
|
||||
isrcChan <- isrc
|
||||
}()
|
||||
} else {
|
||||
close(isrcChan)
|
||||
}
|
||||
|
||||
fmt.Printf("Using Amazon URL: %s\n", amazonURL)
|
||||
|
||||
filePath, err := a.DownloadFromService(amazonURL, outputDir, quality)
|
||||
@@ -286,14 +312,25 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
||||
return "", err
|
||||
}
|
||||
|
||||
var isrc string
|
||||
if spotifyURL != "" {
|
||||
isrc = <-isrcChan
|
||||
}
|
||||
|
||||
originalFileDir := filepath.Dir(filePath)
|
||||
originalFileBase := strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filePath))
|
||||
|
||||
if spotifyTrackName != "" && spotifyArtistName != "" {
|
||||
safeArtist := sanitizeFilename(spotifyArtistName)
|
||||
safeAlbumArtist := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
if useFirstArtistOnly {
|
||||
safeArtist = sanitizeFilename(GetFirstArtist(spotifyArtistName))
|
||||
safeAlbumArtist = sanitizeFilename(GetFirstArtist(spotifyAlbumArtist))
|
||||
}
|
||||
|
||||
safeTitle := sanitizeFilename(spotifyTrackName)
|
||||
safeAlbum := sanitizeFilename(spotifyAlbumName)
|
||||
safeAlbumArtist := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
year := ""
|
||||
if len(spotifyReleaseDate) >= 4 {
|
||||
@@ -390,6 +427,7 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
||||
Copyright: spotifyCopyright,
|
||||
Publisher: spotifyPublisher,
|
||||
Description: "https://github.com/afkarxyz/SpotiFLAC",
|
||||
ISRC: isrc,
|
||||
}
|
||||
|
||||
if err := EmbedMetadataToConvertedFile(filePath, metadata, coverPath); err != nil {
|
||||
@@ -415,12 +453,14 @@ func (a *AmazonDownloader) DownloadByURL(amazonURL, outputDir, quality, filename
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string) (string, error) {
|
||||
func (a *AmazonDownloader) DownloadBySpotifyID(spotifyTrackID, outputDir, quality, filenameFormat, playlistName, playlistOwner string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL string, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, embedMaxQualityCover bool, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string,
|
||||
useFirstArtistOnly bool,
|
||||
) (string, error) {
|
||||
|
||||
amazonURL, err := a.GetAmazonURLFromSpotify(spotifyTrackID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return a.DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, embedMaxQualityCover, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyURL)
|
||||
return a.DownloadByURL(amazonURL, outputDir, quality, filenameFormat, playlistName, playlistOwner, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, spotifyCoverURL, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, embedMaxQualityCover, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyURL, useFirstArtistOnly)
|
||||
}
|
||||
|
||||
@@ -118,6 +118,19 @@ func SanitizeFilename(name string) string {
|
||||
return sanitized
|
||||
}
|
||||
|
||||
func GetFirstArtist(artistString string) string {
|
||||
if artistString == "" {
|
||||
return ""
|
||||
}
|
||||
delimiters := []string{", ", " & ", " feat. ", " ft. ", " featuring "}
|
||||
for _, d := range delimiters {
|
||||
if idx := strings.Index(strings.ToLower(artistString), d); idx != -1 {
|
||||
return strings.TrimSpace(artistString[:idx])
|
||||
}
|
||||
}
|
||||
return artistString
|
||||
}
|
||||
|
||||
func NormalizePath(folderPath string) string {
|
||||
|
||||
return strings.ReplaceAll(folderPath, "/", string(filepath.Separator))
|
||||
|
||||
+28
-13
@@ -31,6 +31,7 @@ type Metadata struct {
|
||||
Publisher string
|
||||
Lyrics string
|
||||
Description string
|
||||
ISRC string
|
||||
}
|
||||
|
||||
func EmbedMetadata(filepath string, metadata Metadata, coverPath string) error {
|
||||
@@ -86,6 +87,10 @@ func EmbedMetadata(filepath string, metadata Metadata, coverPath string) error {
|
||||
_ = cmt.Add("DESCRIPTION", metadata.Description)
|
||||
}
|
||||
|
||||
if metadata.ISRC != "" {
|
||||
_ = cmt.Add("ISRC", metadata.ISRC)
|
||||
}
|
||||
|
||||
if metadata.Lyrics != "" {
|
||||
_ = cmt.Add("LYRICS", metadata.Lyrics)
|
||||
}
|
||||
@@ -504,6 +509,13 @@ func EmbedLyricsOnlyUniversal(filepath string, lyrics string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
validatedLyrics, err := validateLyricsDuration(lyrics, filepath)
|
||||
if err != nil {
|
||||
fmt.Printf("[EmbedLyricsOnlyUniversal] Warning: Failed to validate lyrics duration: %v, using original lyrics\n", err)
|
||||
validatedLyrics = lyrics
|
||||
}
|
||||
lyrics = validatedLyrics
|
||||
|
||||
ext := strings.ToLower(pathfilepath.Ext(filepath))
|
||||
switch ext {
|
||||
case ".mp3":
|
||||
@@ -635,27 +647,22 @@ func validateLyricsDuration(lyrics string, filepath string) (string, error) {
|
||||
|
||||
if strings.HasPrefix(trimmedLine, "[") {
|
||||
|
||||
if strings.Index(trimmedLine, ":") > 0 {
|
||||
|
||||
validLines = append(validLines, line)
|
||||
continue
|
||||
}
|
||||
|
||||
closeBracket := strings.Index(trimmedLine, "]")
|
||||
if closeBracket > 0 {
|
||||
timestampStr := trimmedLine[1:closeBracket]
|
||||
|
||||
ms := parseLRCTimestamp(timestampStr)
|
||||
if ms >= 0 && ms <= durationMs {
|
||||
|
||||
validLines = append(validLines, line)
|
||||
if ms >= 0 {
|
||||
if ms <= durationMs {
|
||||
validLines = append(validLines, line)
|
||||
} else {
|
||||
fmt.Printf("[ValidateLyrics] Filtered out line with timestamp %s (exceeds duration %d ms): %s\n", timestampStr, durationMs, trimmedLine)
|
||||
}
|
||||
} else {
|
||||
|
||||
fmt.Printf("[ValidateLyrics] Filtered out line with timestamp %s (exceeds duration %d ms): %s\n", timestampStr, durationMs, trimmedLine)
|
||||
validLines = append(validLines, line)
|
||||
}
|
||||
} else {
|
||||
|
||||
validLines = append(validLines, line)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -858,6 +865,11 @@ func embedMetadataToMP3(filePath string, metadata Metadata, coverPath string) er
|
||||
tag.AddTextFrame("TPUB", id3v2.EncodingUTF8, metadata.Publisher)
|
||||
}
|
||||
|
||||
if metadata.ISRC != "" {
|
||||
tag.DeleteFrames("TSRC")
|
||||
tag.AddTextFrame("TSRC", id3v2.EncodingUTF8, metadata.ISRC)
|
||||
}
|
||||
|
||||
if coverPath != "" && fileExists(coverPath) {
|
||||
|
||||
tag.DeleteFrames(tag.CommonID("Attached picture"))
|
||||
@@ -941,6 +953,9 @@ func embedMetadataToM4A(filePath string, metadata Metadata, coverPath string) er
|
||||
if metadata.Publisher != "" {
|
||||
args = append(args, "-metadata", "publisher="+metadata.Publisher)
|
||||
}
|
||||
if metadata.ISRC != "" {
|
||||
args = append(args, "-metadata", "isrc="+metadata.ISRC)
|
||||
}
|
||||
|
||||
tmpOutputFile := strings.TrimSuffix(filePath, pathfilepath.Ext(filePath)) + ".tmp" + pathfilepath.Ext(filePath)
|
||||
defer func() {
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ type DownloadItem struct {
|
||||
TrackName string `json:"track_name"`
|
||||
ArtistName string `json:"artist_name"`
|
||||
AlbumName string `json:"album_name"`
|
||||
ISRC string `json:"isrc"`
|
||||
SpotifyID string `json:"spotify_id"`
|
||||
Status DownloadStatus `json:"status"`
|
||||
Progress float64 `json:"progress"`
|
||||
TotalSize float64 `json:"total_size"`
|
||||
@@ -184,7 +184,7 @@ func (pw *ProgressWriter) GetTotal() int64 {
|
||||
return pw.total
|
||||
}
|
||||
|
||||
func AddToQueue(id, trackName, artistName, albumName, isrc string) {
|
||||
func AddToQueue(id, trackName, artistName, albumName, spotifyID string) {
|
||||
downloadQueueLock.Lock()
|
||||
defer downloadQueueLock.Unlock()
|
||||
|
||||
@@ -193,7 +193,7 @@ func AddToQueue(id, trackName, artistName, albumName, isrc string) {
|
||||
TrackName: trackName,
|
||||
ArtistName: artistName,
|
||||
AlbumName: albumName,
|
||||
ISRC: isrc,
|
||||
SpotifyID: spotifyID,
|
||||
Status: StatusQueued,
|
||||
Progress: 0,
|
||||
TotalSize: 0,
|
||||
|
||||
+27
-4
@@ -77,7 +77,7 @@ func NewQobuzDownloader() *QobuzDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) SearchByISRC(isrc string) (*QobuzTrack, error) {
|
||||
func (q *QobuzDownloader) searchByISRC(isrc string) (*QobuzTrack, error) {
|
||||
apiBase := "https://www.qobuz.com/api.json/0.2/track/search?query="
|
||||
url := fmt.Sprintf("%s%s&limit=1&app_id=%s", apiBase, isrc, q.appID)
|
||||
|
||||
@@ -433,7 +433,23 @@ func buildQobuzFilename(title, artist, album, albumArtist, releaseDate string, t
|
||||
return filename + ".flac"
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) DownloadByISRC(deezerISRC, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool) (string, error) {
|
||||
func (q *QobuzDownloader) DownloadTrack(spotifyID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool, useFirstArtistOnly bool) (string, error) {
|
||||
var deezerISRC string
|
||||
if spotifyID != "" {
|
||||
songlinkClient := NewSongLinkClient()
|
||||
isrc, err := songlinkClient.GetISRC(spotifyID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get ISRC: %v", err)
|
||||
}
|
||||
deezerISRC = isrc
|
||||
} else {
|
||||
return "", fmt.Errorf("spotify ID is required for Qobuz download")
|
||||
}
|
||||
|
||||
return q.DownloadTrackWithISRC(deezerISRC, spotifyID, outputDir, quality, filenameFormat, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, useAlbumTrackNumber, spotifyCoverURL, embedMaxQualityCover, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyURL, allowFallback, useFirstArtistOnly)
|
||||
}
|
||||
|
||||
func (q *QobuzDownloader) DownloadTrackWithISRC(deezerISRC, spotifyID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool, useFirstArtistOnly bool) (string, error) {
|
||||
fmt.Printf("Fetching track info for ISRC: %s\n", deezerISRC)
|
||||
|
||||
if outputDir != "." {
|
||||
@@ -442,7 +458,7 @@ func (q *QobuzDownloader) DownloadByISRC(deezerISRC, outputDir, quality, filenam
|
||||
}
|
||||
}
|
||||
|
||||
track, err := q.SearchByISRC(deezerISRC)
|
||||
track, err := q.searchByISRC(deezerISRC)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -477,9 +493,15 @@ func (q *QobuzDownloader) DownloadByISRC(deezerISRC, outputDir, quality, filenam
|
||||
fmt.Printf("Download URL obtained: %s\n", urlPreview)
|
||||
|
||||
safeArtist := sanitizeFilename(artists)
|
||||
safeAlbumArtist := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
if useFirstArtistOnly {
|
||||
safeArtist = sanitizeFilename(GetFirstArtist(artists))
|
||||
safeAlbumArtist = sanitizeFilename(GetFirstArtist(spotifyAlbumArtist))
|
||||
}
|
||||
|
||||
safeTitle := sanitizeFilename(trackTitle)
|
||||
safeAlbum := sanitizeFilename(albumTitle)
|
||||
safeAlbumArtist := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
filename := buildQobuzFilename(safeTitle, safeArtist, safeAlbum, safeAlbumArtist, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
||||
filepath := filepath.Join(outputDir, filename)
|
||||
@@ -531,6 +553,7 @@ func (q *QobuzDownloader) DownloadByISRC(deezerISRC, outputDir, quality, filenam
|
||||
Copyright: spotifyCopyright,
|
||||
Publisher: spotifyPublisher,
|
||||
Description: "https://github.com/afkarxyz/SpotiFLAC",
|
||||
ISRC: deezerISRC,
|
||||
}
|
||||
|
||||
if err := EmbedMetadata(filepath, metadata, coverPath); err != nil {
|
||||
|
||||
+18
-3
@@ -21,6 +21,7 @@ type SongLinkClient struct {
|
||||
type SongLinkURLs struct {
|
||||
TidalURL string `json:"tidal_url"`
|
||||
AmazonURL string `json:"amazon_url"`
|
||||
ISRC string `json:"isrc"`
|
||||
}
|
||||
|
||||
type TrackAvailability struct {
|
||||
@@ -158,6 +159,12 @@ func (s *SongLinkClient) GetAllURLsFromSpotify(spotifyTrackID string, region str
|
||||
}
|
||||
}
|
||||
|
||||
if deezerLink, ok := songLinkResp.LinksByPlatform["deezer"]; ok && deezerLink.URL != "" {
|
||||
if isrc, err := getDeezerISRC(deezerLink.URL); err == nil && isrc != "" {
|
||||
urls.ISRC = isrc
|
||||
}
|
||||
}
|
||||
|
||||
if urls.TidalURL == "" && urls.AmazonURL == "" {
|
||||
return nil, fmt.Errorf("no streaming URLs found")
|
||||
}
|
||||
@@ -165,7 +172,7 @@ func (s *SongLinkClient) GetAllURLsFromSpotify(spotifyTrackID string, region str
|
||||
return urls, nil
|
||||
}
|
||||
|
||||
func (s *SongLinkClient) CheckTrackAvailability(spotifyTrackID string, isrc string) (*TrackAvailability, error) {
|
||||
func (s *SongLinkClient) CheckTrackAvailability(spotifyTrackID string) (*TrackAvailability, error) {
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(s.apiCallResetTime) >= time.Minute {
|
||||
@@ -278,7 +285,7 @@ func (s *SongLinkClient) CheckTrackAvailability(spotifyTrackID string, isrc stri
|
||||
if deezerLink, ok := songLinkResp.LinksByPlatform["deezer"]; ok && deezerLink.URL != "" {
|
||||
deezerURL := deezerLink.URL
|
||||
|
||||
deezerISRC, err := GetDeezerISRC(deezerURL)
|
||||
deezerISRC, err := getDeezerISRC(deezerURL)
|
||||
if err == nil && deezerISRC != "" {
|
||||
qobuzAvailable := checkQobuzAvailability(deezerISRC)
|
||||
availability.Qobuz = qobuzAvailable
|
||||
@@ -408,7 +415,7 @@ func (s *SongLinkClient) GetDeezerURLFromSpotify(spotifyTrackID string) (string,
|
||||
return deezerURL, nil
|
||||
}
|
||||
|
||||
func GetDeezerISRC(deezerURL string) (string, error) {
|
||||
func getDeezerISRC(deezerURL string) (string, error) {
|
||||
|
||||
var trackID string
|
||||
if strings.Contains(deezerURL, "/track/") {
|
||||
@@ -452,3 +459,11 @@ func GetDeezerISRC(deezerURL string) (string, error) {
|
||||
fmt.Printf("Found ISRC from Deezer: %s (track: %s)\n", deezerTrack.ISRC, deezerTrack.Title)
|
||||
return deezerTrack.ISRC, nil
|
||||
}
|
||||
|
||||
func (s *SongLinkClient) GetISRC(spotifyID string) (string, error) {
|
||||
deezerURL, err := s.GetDeezerURLFromSpotify(spotifyID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getDeezerISRC(deezerURL)
|
||||
}
|
||||
|
||||
+29
-32
@@ -364,9 +364,6 @@ func getBool(m map[string]interface{}, key string) bool {
|
||||
|
||||
func extractArtists(artistsData map[string]interface{}) []map[string]interface{} {
|
||||
items := getSlice(artistsData, "items")
|
||||
if items == nil {
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
|
||||
artists := []map[string]interface{}{}
|
||||
for _, item := range items {
|
||||
@@ -384,7 +381,7 @@ func extractArtists(artistsData map[string]interface{}) []map[string]interface{}
|
||||
}
|
||||
|
||||
func extractCoverImage(coverData map[string]interface{}) map[string]interface{} {
|
||||
if coverData == nil || len(coverData) == 0 {
|
||||
if len(coverData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -401,7 +398,7 @@ func extractCoverImage(coverData map[string]interface{}) map[string]interface{}
|
||||
}
|
||||
}
|
||||
|
||||
if sources == nil || len(sources) == 0 {
|
||||
if len(sources) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -532,7 +529,7 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
}
|
||||
|
||||
var albumFetchDataMap map[string]interface{}
|
||||
if len(albumFetchData) > 0 && albumFetchData[0] != nil {
|
||||
if len(albumFetchData) > 0 {
|
||||
albumFetchDataMap = albumFetchData[0]
|
||||
}
|
||||
|
||||
@@ -541,39 +538,35 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
if len(artists) == 0 {
|
||||
artists = []map[string]interface{}{}
|
||||
firstArtistItems := getSlice(getMap(trackData, "firstArtist"), "items")
|
||||
if firstArtistItems != nil {
|
||||
for _, item := range firstArtistItems {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if profile, exists := itemMap["profile"]; exists {
|
||||
profileMap, ok := profile.(map[string]interface{})
|
||||
if ok {
|
||||
artistInfo := map[string]interface{}{
|
||||
"name": getString(profileMap, "name"),
|
||||
}
|
||||
artists = append(artists, artistInfo)
|
||||
for _, item := range firstArtistItems {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if profile, exists := itemMap["profile"]; exists {
|
||||
profileMap, ok := profile.(map[string]interface{})
|
||||
if ok {
|
||||
artistInfo := map[string]interface{}{
|
||||
"name": getString(profileMap, "name"),
|
||||
}
|
||||
artists = append(artists, artistInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
otherArtistItems := getSlice(getMap(trackData, "otherArtists"), "items")
|
||||
if otherArtistItems != nil {
|
||||
for _, item := range otherArtistItems {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if profile, exists := itemMap["profile"]; exists {
|
||||
profileMap, ok := profile.(map[string]interface{})
|
||||
if ok {
|
||||
artistInfo := map[string]interface{}{
|
||||
"name": getString(profileMap, "name"),
|
||||
}
|
||||
artists = append(artists, artistInfo)
|
||||
for _, item := range otherArtistItems {
|
||||
itemMap, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if profile, exists := itemMap["profile"]; exists {
|
||||
profileMap, ok := profile.(map[string]interface{})
|
||||
if ok {
|
||||
artistInfo := map[string]interface{}{
|
||||
"name": getString(profileMap, "name"),
|
||||
}
|
||||
artists = append(artists, artistInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -710,6 +703,9 @@ func FilterTrack(data map[string]interface{}, albumFetchData ...map[string]inter
|
||||
}
|
||||
albumArtistsString = strings.Join(albumArtistNames, ", ")
|
||||
}
|
||||
if albumArtistsString == "" {
|
||||
albumArtistsString = getString(albumUnionData, "artists")
|
||||
}
|
||||
albumLabel = getString(albumUnionData, "label")
|
||||
}
|
||||
}
|
||||
@@ -977,6 +973,7 @@ func FilterAlbum(data map[string]interface{}) map[string]interface{} {
|
||||
"discs": map[string]interface{}{
|
||||
"totalCount": totalDiscs,
|
||||
},
|
||||
"label": getString(albumData, "label"),
|
||||
}
|
||||
|
||||
return filtered
|
||||
|
||||
@@ -42,7 +42,6 @@ type TrackMetadata struct {
|
||||
DiscNumber int `json:"disc_number,omitempty"`
|
||||
TotalDiscs int `json:"total_discs,omitempty"`
|
||||
ExternalURL string `json:"external_urls"`
|
||||
ISRC string `json:"isrc"`
|
||||
Copyright string `json:"copyright,omitempty"`
|
||||
Publisher string `json:"publisher,omitempty"`
|
||||
Plays string `json:"plays,omitempty"`
|
||||
@@ -70,7 +69,6 @@ type AlbumTrackMetadata struct {
|
||||
DiscNumber int `json:"disc_number,omitempty"`
|
||||
TotalDiscs int `json:"total_discs,omitempty"`
|
||||
ExternalURL string `json:"external_urls"`
|
||||
ISRC string `json:"isrc"`
|
||||
AlbumType string `json:"album_type,omitempty"`
|
||||
AlbumID string `json:"album_id,omitempty"`
|
||||
AlbumURL string `json:"album_url,omitempty"`
|
||||
@@ -210,6 +208,7 @@ type apiAlbumResponse struct {
|
||||
Cover string `json:"cover"`
|
||||
ReleaseDate string `json:"releaseDate"`
|
||||
Count int `json:"count"`
|
||||
Label string `json:"label"`
|
||||
Discs struct {
|
||||
TotalCount int `json:"totalCount"`
|
||||
} `json:"discs"`
|
||||
@@ -472,6 +471,8 @@ func (c *SpotifyMetadataClient) fetchTrack(ctx context.Context, trackID string)
|
||||
"items": tracksItems,
|
||||
"totalCount": albumResponse.Count,
|
||||
},
|
||||
"artists": albumResponse.Artists,
|
||||
"label": albumResponse.Label,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -886,7 +887,6 @@ func (c *SpotifyMetadataClient) formatTrackData(raw *apiTrackResponse) TrackResp
|
||||
DiscNumber: raw.Disc,
|
||||
TotalDiscs: raw.Discs,
|
||||
ExternalURL: externalURL,
|
||||
ISRC: raw.ID,
|
||||
Copyright: raw.Copyright,
|
||||
Publisher: raw.Album.Label,
|
||||
Plays: raw.Plays,
|
||||
@@ -945,7 +945,6 @@ func (c *SpotifyMetadataClient) formatAlbumData(raw *apiAlbumResponse) (*AlbumRe
|
||||
DiscNumber: item.DiscNumber,
|
||||
TotalDiscs: raw.Discs.TotalCount,
|
||||
ExternalURL: fmt.Sprintf("https://open.spotify.com/track/%s", item.ID),
|
||||
ISRC: item.ID,
|
||||
AlbumID: raw.ID,
|
||||
AlbumURL: fmt.Sprintf("https://open.spotify.com/album/%s", raw.ID),
|
||||
ArtistID: artistID,
|
||||
@@ -1005,7 +1004,6 @@ func (c *SpotifyMetadataClient) formatPlaylistData(raw *apiPlaylistResponse) Pla
|
||||
DiscNumber: item.DiscNumber,
|
||||
TotalDiscs: 0,
|
||||
ExternalURL: fmt.Sprintf("https://open.spotify.com/track/%s", item.ID),
|
||||
ISRC: item.ID,
|
||||
AlbumID: item.AlbumID,
|
||||
AlbumURL: fmt.Sprintf("https://open.spotify.com/album/%s", item.AlbumID),
|
||||
ArtistID: artistID,
|
||||
@@ -1124,7 +1122,6 @@ func (c *SpotifyMetadataClient) formatArtistDiscographyData(ctx context.Context,
|
||||
TotalTracks: albumData.Count,
|
||||
DiscNumber: tr.DiscNumber,
|
||||
ExternalURL: fmt.Sprintf("https://open.spotify.com/track/%s", tr.ID),
|
||||
ISRC: tr.ID,
|
||||
AlbumID: albumID,
|
||||
AlbumURL: fmt.Sprintf("https://open.spotify.com/album/%s", albumID),
|
||||
ArtistID: artistID,
|
||||
|
||||
+70
-6
@@ -446,7 +446,7 @@ func (t *TidalDownloader) DownloadFromManifest(manifestB64, outputPath string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool) (string, error) {
|
||||
func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool, useFirstArtistOnly bool) (string, error) {
|
||||
if outputDir != "." {
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("directory error: %w", err)
|
||||
@@ -469,9 +469,15 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
||||
albumTitle := spotifyAlbumName
|
||||
|
||||
artistNameForFile := sanitizeFilename(artistName)
|
||||
albumArtistForFile := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
if useFirstArtistOnly {
|
||||
artistNameForFile = sanitizeFilename(GetFirstArtist(artistName))
|
||||
albumArtistForFile = sanitizeFilename(GetFirstArtist(spotifyAlbumArtist))
|
||||
}
|
||||
|
||||
trackTitleForFile := sanitizeFilename(trackTitle)
|
||||
albumTitleForFile := sanitizeFilename(albumTitle)
|
||||
albumArtistForFile := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
||||
outputFilename := filepath.Join(outputDir, filename)
|
||||
@@ -494,11 +500,36 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
||||
}
|
||||
}
|
||||
|
||||
isrcChan := make(chan string, 1)
|
||||
if spotifyURL != "" {
|
||||
go func() {
|
||||
var isrc string
|
||||
parts := strings.Split(spotifyURL, "/")
|
||||
if len(parts) > 0 {
|
||||
sID := strings.Split(parts[len(parts)-1], "?")[0]
|
||||
if sID != "" {
|
||||
client := NewSongLinkClient()
|
||||
if val, err := client.GetISRC(sID); err == nil {
|
||||
isrc = val
|
||||
}
|
||||
}
|
||||
}
|
||||
isrcChan <- isrc
|
||||
}()
|
||||
} else {
|
||||
close(isrcChan)
|
||||
}
|
||||
|
||||
fmt.Printf("Downloading to: %s\n", outputFilename)
|
||||
if err := t.DownloadFile(downloadURL, outputFilename); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var isrc string
|
||||
if spotifyURL != "" {
|
||||
isrc = <-isrcChan
|
||||
}
|
||||
|
||||
fmt.Println("Adding metadata...")
|
||||
|
||||
coverPath := ""
|
||||
@@ -534,6 +565,7 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
||||
Copyright: spotifyCopyright,
|
||||
Publisher: spotifyPublisher,
|
||||
Description: "https://github.com/afkarxyz/SpotiFLAC",
|
||||
ISRC: isrc,
|
||||
}
|
||||
|
||||
if err := EmbedMetadata(outputFilename, metadata, coverPath); err != nil {
|
||||
@@ -547,7 +579,7 @@ func (t *TidalDownloader) DownloadByURL(tidalURL, outputDir, quality, filenameFo
|
||||
return outputFilename, nil
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool) (string, error) {
|
||||
func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool, useFirstArtistOnly bool) (string, error) {
|
||||
apis, err := t.GetAvailableAPIs()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("no APIs available for fallback: %w", err)
|
||||
@@ -575,9 +607,15 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
||||
albumTitle := spotifyAlbumName
|
||||
|
||||
artistNameForFile := sanitizeFilename(artistName)
|
||||
albumArtistForFile := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
if useFirstArtistOnly {
|
||||
artistNameForFile = sanitizeFilename(GetFirstArtist(artistName))
|
||||
albumArtistForFile = sanitizeFilename(GetFirstArtist(spotifyAlbumArtist))
|
||||
}
|
||||
|
||||
trackTitleForFile := sanitizeFilename(trackTitle)
|
||||
albumTitleForFile := sanitizeFilename(albumTitle)
|
||||
albumArtistForFile := sanitizeFilename(spotifyAlbumArtist)
|
||||
|
||||
filename := buildTidalFilename(trackTitleForFile, artistNameForFile, albumTitleForFile, albumArtistForFile, spotifyReleaseDate, spotifyTrackNumber, spotifyDiscNumber, filenameFormat, includeTrackNumber, position, useAlbumTrackNumber)
|
||||
outputFilename := filepath.Join(outputDir, filename)
|
||||
@@ -600,12 +638,37 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
||||
}
|
||||
}
|
||||
|
||||
isrcChan := make(chan string, 1)
|
||||
if spotifyURL != "" {
|
||||
go func() {
|
||||
var isrc string
|
||||
parts := strings.Split(spotifyURL, "/")
|
||||
if len(parts) > 0 {
|
||||
sID := strings.Split(parts[len(parts)-1], "?")[0]
|
||||
if sID != "" {
|
||||
client := NewSongLinkClient()
|
||||
if val, err := client.GetISRC(sID); err == nil {
|
||||
isrc = val
|
||||
}
|
||||
}
|
||||
}
|
||||
isrcChan <- isrc
|
||||
}()
|
||||
} else {
|
||||
close(isrcChan)
|
||||
}
|
||||
|
||||
fmt.Printf("Downloading to: %s\n", outputFilename)
|
||||
downloader := NewTidalDownloader(successAPI)
|
||||
if err := downloader.DownloadFile(downloadURL, outputFilename); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var isrc string
|
||||
if spotifyURL != "" {
|
||||
isrc = <-isrcChan
|
||||
}
|
||||
|
||||
fmt.Println("Adding metadata...")
|
||||
|
||||
coverPath := ""
|
||||
@@ -641,6 +704,7 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
||||
Copyright: spotifyCopyright,
|
||||
Publisher: spotifyPublisher,
|
||||
Description: "https://github.com/afkarxyz/SpotiFLAC",
|
||||
ISRC: isrc,
|
||||
}
|
||||
|
||||
if err := EmbedMetadata(outputFilename, metadata, coverPath); err != nil {
|
||||
@@ -654,14 +718,14 @@ func (t *TidalDownloader) DownloadByURLWithFallback(tidalURL, outputDir, quality
|
||||
return outputFilename, nil
|
||||
}
|
||||
|
||||
func (t *TidalDownloader) Download(spotifyTrackID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool) (string, error) {
|
||||
func (t *TidalDownloader) Download(spotifyTrackID, outputDir, quality, filenameFormat string, includeTrackNumber bool, position int, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate string, useAlbumTrackNumber bool, spotifyCoverURL string, embedMaxQualityCover bool, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks int, spotifyTotalDiscs int, spotifyCopyright, spotifyPublisher, spotifyURL string, allowFallback bool, useFirstArtistOnly bool) (string, error) {
|
||||
|
||||
tidalURL, err := t.GetTidalURLFromSpotify(spotifyTrackID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("songlink couldn't find Tidal URL: %w", err)
|
||||
}
|
||||
|
||||
return t.DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, useAlbumTrackNumber, spotifyCoverURL, embedMaxQualityCover, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyURL, allowFallback)
|
||||
return t.DownloadByURLWithFallback(tidalURL, outputDir, quality, filenameFormat, includeTrackNumber, position, spotifyTrackName, spotifyArtistName, spotifyAlbumName, spotifyAlbumArtist, spotifyReleaseDate, useAlbumTrackNumber, spotifyCoverURL, embedMaxQualityCover, spotifyTrackNumber, spotifyDiscNumber, spotifyTotalTracks, spotifyTotalDiscs, spotifyCopyright, spotifyPublisher, spotifyURL, allowFallback, useFirstArtistOnly)
|
||||
}
|
||||
|
||||
type SegmentTemplate struct {
|
||||
|
||||
Reference in New Issue
Block a user