mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-06-05 14:18:16 +02:00
feat: improve Tidal metadata (copyright, album artist), remove Qobuz metadata search fallback, fix DATE/YAR tag sync
This commit is contained in:
+1
-10
@@ -2655,17 +2655,8 @@ func resolveQobuzTrackForRequest(req DownloadRequest, downloader *QobuzDownloade
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 5: Metadata search with strict matching (duration tolerance: 10 seconds)
|
||||
if track == nil {
|
||||
GoLog("[%s] Trying metadata search: '%s' by '%s'\n", logPrefix, req.TrackName, req.ArtistName)
|
||||
track, err = qobuzSearchTrackByMetadataWithDurationFunc(downloader, req.TrackName, req.ArtistName, expectedDurationSec)
|
||||
if track != nil && !qobuzTrackMatchesRequest(req, track, logPrefix, "metadata search", false) {
|
||||
track = nil
|
||||
}
|
||||
}
|
||||
|
||||
if track == nil {
|
||||
errMsg := "could not find matching track on Qobuz (artist/duration mismatch)"
|
||||
errMsg := "could not find matching track on Qobuz without identifier match"
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
|
||||
@@ -429,11 +429,9 @@ func TestResolveQobuzTrackForRequestRejectsOdesliMismatch(t *testing.T) {
|
||||
t.Fatal("ISRC fallback should not run without an ISRC")
|
||||
return nil, nil
|
||||
}
|
||||
qobuzSearchTrackByMetadataWithDurationFunc = func(_ *QobuzDownloader, trackName, artistName string, expectedDurationSec int) (*QobuzTrack, error) {
|
||||
if trackName != "Taste Back" || artistName != "Harry Styles" || expectedDurationSec != 181 {
|
||||
t.Fatalf("unexpected metadata fallback arguments: %q / %q / %d", trackName, artistName, expectedDurationSec)
|
||||
}
|
||||
return testQobuzTrack(444, "Taste Back", "Harry Styles", 181), nil
|
||||
qobuzSearchTrackByMetadataWithDurationFunc = func(_ *QobuzDownloader, _, _ string, _ int) (*QobuzTrack, error) {
|
||||
t.Fatal("metadata fallback should not run")
|
||||
return nil, nil
|
||||
}
|
||||
songLinkCheckTrackAvailabilityFunc = func(_ *SongLinkClient, _, _ string) (*TrackAvailability, error) {
|
||||
t.Fatal("SongLink should not run when Odesli QobuzID is provided")
|
||||
@@ -448,11 +446,11 @@ func TestResolveQobuzTrackForRequestRejectsOdesliMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
track, err := resolveQobuzTrackForRequest(req, &QobuzDownloader{}, "Test")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got track %+v", track)
|
||||
}
|
||||
if track == nil || track.ID != 444 || track.Title != "Taste Back" {
|
||||
t.Fatalf("unexpected resolved track: %+v", track)
|
||||
if track != nil {
|
||||
t.Fatalf("expected nil track, got %+v", track)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-3
@@ -51,6 +51,7 @@ type TidalTrack struct {
|
||||
ID int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ISRC string `json:"isrc"`
|
||||
Copyright string `json:"copyright"`
|
||||
AudioQuality string `json:"audioQuality"`
|
||||
TrackNumber int `json:"trackNumber"`
|
||||
VolumeNumber int `json:"volumeNumber"`
|
||||
@@ -135,6 +136,7 @@ type tidalPublicAlbum struct {
|
||||
Type string `json:"type"`
|
||||
Cover string `json:"cover"`
|
||||
ReleaseDate string `json:"releaseDate"`
|
||||
Copyright string `json:"copyright"`
|
||||
URL string `json:"url"`
|
||||
NumberOfTracks int `json:"numberOfTracks"`
|
||||
Explicit bool `json:"explicit"`
|
||||
@@ -306,6 +308,29 @@ func tidalTrackArtistsDisplay(track *TidalTrack) string {
|
||||
return strings.TrimSpace(track.Artist.Name)
|
||||
}
|
||||
|
||||
func tidalTrackAlbumArtistDisplay(track *TidalTrack) string {
|
||||
if track == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(track.Artists) > 0 {
|
||||
names := make([]string, 0, len(track.Artists))
|
||||
for _, artist := range track.Artists {
|
||||
if strings.ToUpper(strings.TrimSpace(artist.Type)) != "MAIN" {
|
||||
continue
|
||||
}
|
||||
if trimmed := strings.TrimSpace(artist.Name); trimmed != "" {
|
||||
names = append(names, trimmed)
|
||||
}
|
||||
}
|
||||
if len(names) > 0 {
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(track.Artist.Name)
|
||||
}
|
||||
|
||||
func tidalAlbumArtistsDisplay(album *tidalPublicAlbum) string {
|
||||
if album == nil {
|
||||
return ""
|
||||
@@ -354,7 +379,7 @@ func tidalTrackToTrackMetadata(track *TidalTrack) TrackMetadata {
|
||||
Artists: tidalTrackArtistsDisplay(track),
|
||||
Name: strings.TrimSpace(track.Title),
|
||||
AlbumName: strings.TrimSpace(track.Album.Title),
|
||||
AlbumArtist: strings.TrimSpace(track.Artist.Name),
|
||||
AlbumArtist: tidalTrackAlbumArtistDisplay(track),
|
||||
DurationMS: track.Duration * 1000,
|
||||
Images: tidalImageURL(track.Album.Cover, "1280x1280"),
|
||||
ReleaseDate: strings.TrimSpace(track.Album.ReleaseDate),
|
||||
@@ -377,7 +402,7 @@ func tidalTrackToAlbumTrackMetadata(track *TidalTrack) AlbumTrackMetadata {
|
||||
Artists: tidalTrackArtistsDisplay(track),
|
||||
Name: strings.TrimSpace(track.Title),
|
||||
AlbumName: strings.TrimSpace(track.Album.Title),
|
||||
AlbumArtist: strings.TrimSpace(track.Artist.Name),
|
||||
AlbumArtist: tidalTrackAlbumArtistDisplay(track),
|
||||
DurationMS: track.Duration * 1000,
|
||||
Images: tidalImageURL(track.Album.Cover, "1280x1280"),
|
||||
ReleaseDate: strings.TrimSpace(track.Album.ReleaseDate),
|
||||
@@ -407,6 +432,7 @@ func tidalAlbumToAlbumInfo(album *tidalPublicAlbum) AlbumInfoMetadata {
|
||||
Artists: tidalAlbumArtistsDisplay(album),
|
||||
ArtistId: artistID,
|
||||
Images: tidalImageURL(album.Cover, "1280x1280"),
|
||||
Copyright: strings.TrimSpace(album.Copyright),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1740,6 +1766,7 @@ type TidalDownloadResult struct {
|
||||
TrackNumber int
|
||||
DiscNumber int
|
||||
ISRC string
|
||||
Copyright string
|
||||
LyricsLRC string // LRC content for embedding in converted files
|
||||
}
|
||||
|
||||
@@ -2348,6 +2375,10 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
|
||||
if actualDiscNumber == 0 {
|
||||
actualDiscNumber = track.VolumeNumber
|
||||
}
|
||||
copyright := strings.TrimSpace(req.Copyright)
|
||||
if copyright == "" {
|
||||
copyright = strings.TrimSpace(track.Copyright)
|
||||
}
|
||||
|
||||
metadata := Metadata{
|
||||
Title: req.TrackName,
|
||||
@@ -2363,7 +2394,7 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
|
||||
ISRC: track.ISRC,
|
||||
Genre: req.Genre,
|
||||
Label: req.Label,
|
||||
Copyright: req.Copyright,
|
||||
Copyright: copyright,
|
||||
Composer: req.Composer,
|
||||
}
|
||||
|
||||
@@ -2474,6 +2505,7 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
|
||||
TrackNumber: resultTrackNumber,
|
||||
DiscNumber: resultDiscNumber,
|
||||
ISRC: track.ISRC,
|
||||
Copyright: copyright,
|
||||
LyricsLRC: lyricsLRC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1978,8 +1978,14 @@ class FFmpegService {
|
||||
break;
|
||||
case 'DATE':
|
||||
vorbis['DATE'] = value;
|
||||
final yearMatch = RegExp(r'^(\d{4})').firstMatch(value);
|
||||
if (yearMatch != null &&
|
||||
(!vorbis.containsKey('YEAR') || vorbis['YEAR']!.isEmpty)) {
|
||||
vorbis['YEAR'] = yearMatch.group(1)!;
|
||||
}
|
||||
break;
|
||||
case 'YEAR':
|
||||
vorbis['YEAR'] = value;
|
||||
if (!vorbis.containsKey('DATE') || vorbis['DATE']!.isEmpty) {
|
||||
vorbis['DATE'] = value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user