From a4f11a5b302faa064bfffb5475b98da66fa474d6 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 14 Apr 2026 19:20:55 +0700 Subject: [PATCH] feat: carry extension download metadata through host pipeline and avoid FLAC-only genre/label pre-embed on non-FLAC files --- README.md | 2 +- go_backend/extension_providers.go | 46 ++++++++++++++++++++++ go_backend/extension_providers_test.go | 7 ++++ lib/providers/download_queue_provider.dart | 28 ++++++++++--- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ac6c553..212688c 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ Interested in contributing? Check out the [Contributing Guide](CONTRIBUTING.md) |---|---|---|---|---| | [hifi-api](https://github.com/binimum/hifi-api) | [music.binimum.org](https://music.binimum.org) | [qqdl.site](https://qqdl.site) | [squid.wtf](https://squid.wtf) | [spotisaver.net](https://spotisaver.net) | | [dabmusic.xyz](https://dabmusic.xyz) | [AfkarXYZ](https://github.com/afkarxyz) | [LRCLib](https://lrclib.net) | [Paxsenix](https://lyrics.paxsenix.org) | [Cobalt](https://cobalt.tools) | -| [qwkuns.me](https://qwkuns.me) | [SpotubeDL](https://spotubedl.com) | [Song.link](https://song.link) | [IDHS](https://github.com/sjdonado/idonthavespotify) | | +| [qwkuns.me](https://qwkuns.me) | [SpotubeDL](https://spotubedl.com) | [Song.link](https://song.link) | [IDHS](https://github.com/sjdonado/idonthavespotify) | [Monochrome](https://monochrome.tf) | --- diff --git a/go_backend/extension_providers.go b/go_backend/extension_providers.go index 5f2f5dc..034244b 100644 --- a/go_backend/extension_providers.go +++ b/go_backend/extension_providers.go @@ -118,9 +118,16 @@ type ExtDownloadResult struct { AlbumArtist string `json:"album_artist,omitempty"` TrackNumber int `json:"track_number,omitempty"` DiscNumber int `json:"disc_number,omitempty"` + TotalTracks int `json:"total_tracks,omitempty"` + TotalDiscs int `json:"total_discs,omitempty"` ReleaseDate string `json:"release_date,omitempty"` CoverURL string `json:"cover_url,omitempty"` ISRC string `json:"isrc,omitempty"` + Genre string `json:"genre,omitempty"` + Label string `json:"label,omitempty"` + Copyright string `json:"copyright,omitempty"` + Composer string `json:"composer,omitempty"` + LyricsLRC string `json:"lyrics_lrc,omitempty"` DecryptionKey string `json:"decryption_key,omitempty"` Decryption *DownloadDecryptionInfo `json:"decryption,omitempty"` } @@ -1415,6 +1422,12 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro if result.DiscNumber > 0 { resp.DiscNumber = result.DiscNumber } + if result.TotalTracks > 0 { + resp.TotalTracks = result.TotalTracks + } + if result.TotalDiscs > 0 { + resp.TotalDiscs = result.TotalDiscs + } if result.ReleaseDate != "" { resp.ReleaseDate = result.ReleaseDate } @@ -1424,8 +1437,29 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro if result.ISRC != "" { resp.ISRC = result.ISRC } + if result.Genre != "" { + resp.Genre = result.Genre + } + if result.Label != "" { + resp.Label = result.Label + } + if result.Copyright != "" { + resp.Copyright = result.Copyright + } + if result.Composer != "" { + resp.Composer = result.Composer + } + if result.LyricsLRC != "" { + resp.LyricsLRC = result.LyricsLRC + } } + if req.TrackName != "" && resp.Title == "" { + resp.Title = req.TrackName + } + if req.ArtistName != "" && resp.Artist == "" { + resp.Artist = req.ArtistName + } if req.AlbumName != "" && resp.Album == "" { resp.Album = req.AlbumName } @@ -1444,9 +1478,18 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro if req.DiscNumber > 0 && resp.DiscNumber == 0 { resp.DiscNumber = req.DiscNumber } + if req.TotalTracks > 0 && resp.TotalTracks == 0 { + resp.TotalTracks = req.TotalTracks + } + if req.TotalDiscs > 0 && resp.TotalDiscs == 0 { + resp.TotalDiscs = req.TotalDiscs + } if req.CoverURL != "" && resp.CoverURL == "" { resp.CoverURL = req.CoverURL } + if req.Composer != "" && resp.Composer == "" { + resp.Composer = req.Composer + } return resp, nil } @@ -1877,6 +1920,9 @@ func canEmbedGenreLabel(filePath string) bool { if path == "" || strings.HasPrefix(path, "content://") || strings.HasPrefix(path, "/proc/self/fd/") { return false } + if strings.ToLower(filepath.Ext(path)) != ".flac" { + return false + } if !filepath.IsAbs(path) { return false } diff --git a/go_backend/extension_providers_test.go b/go_backend/extension_providers_test.go index 7961609..1d30be4 100644 --- a/go_backend/extension_providers_test.go +++ b/go_backend/extension_providers_test.go @@ -185,6 +185,10 @@ func TestCanEmbedGenreLabelRequiresExistingAbsoluteLocalFile(t *testing.T) { if err := os.WriteFile(tempFile, []byte("fLaC"), 0644); err != nil { t.Fatalf("failed to create temp file: %v", err) } + tempM4A := filepath.Join(t.TempDir(), "track.m4a") + if err := os.WriteFile(tempM4A, []byte("not-flac"), 0644); err != nil { + t.Fatalf("failed to create temp m4a file: %v", err) + } if canEmbedGenreLabel("relative.flac") { t.Fatal("expected relative path to be rejected") @@ -195,6 +199,9 @@ func TestCanEmbedGenreLabelRequiresExistingAbsoluteLocalFile(t *testing.T) { if canEmbedGenreLabel(filepath.Join(t.TempDir(), "missing.flac")) { t.Fatal("expected missing file to be rejected") } + if canEmbedGenreLabel(tempM4A) { + t.Fatalf("expected non-FLAC file %q to be rejected", tempM4A) + } if !canEmbedGenreLabel(tempFile) { t.Fatalf("expected existing absolute file %q to be accepted", tempFile) } diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 336642e..4a571e1 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -3704,6 +3704,8 @@ class DownloadQueueNotifier extends Notifier { ) { final backendTrackNum = _parsePositiveInt(backendResult['track_number']); final backendDiscNum = _parsePositiveInt(backendResult['disc_number']); + final backendTotalTracks = _parsePositiveInt(backendResult['total_tracks']); + final backendTotalDiscs = _parsePositiveInt(backendResult['total_discs']); final backendYear = normalizeOptionalString( backendResult['release_date'] as String?, ); @@ -3731,7 +3733,9 @@ class DownloadQueueNotifier extends Notifier { backendIsrc != null || backendCoverUrl != null || backendAlbumArtist != null || - backendComposer != null; + backendComposer != null || + backendTotalTracks != null || + backendTotalDiscs != null; if (!hasOverrides) { return baseTrack; @@ -3750,12 +3754,12 @@ class DownloadQueueNotifier extends Notifier { isrc: backendIsrc ?? baseTrack.isrc, trackNumber: backendTrackNum ?? baseTrack.trackNumber, discNumber: backendDiscNum ?? baseTrack.discNumber, - totalDiscs: baseTrack.totalDiscs, + totalDiscs: backendTotalDiscs ?? baseTrack.totalDiscs, releaseDate: backendYear ?? baseTrack.releaseDate, deezerId: baseTrack.deezerId, availability: baseTrack.availability, albumType: baseTrack.albumType, - totalTracks: baseTrack.totalTracks, + totalTracks: backendTotalTracks ?? baseTrack.totalTracks, composer: backendComposer ?? baseTrack.composer, source: baseTrack.source, ); @@ -5808,12 +5812,15 @@ class DownloadQueueNotifier extends Notifier { final backendYear = result['release_date'] as String?; final backendTrackNum = result['track_number'] as int?; final backendDiscNum = result['disc_number'] as int?; + final backendTotalTracks = result['total_tracks'] as int?; + final backendTotalDiscs = result['total_discs'] as int?; final backendBitDepth = result['actual_bit_depth'] as int?; final backendSampleRate = result['actual_sample_rate'] as int?; final backendISRC = result['isrc'] as String?; final backendGenre = result['genre'] as String?; final backendLabel = result['label'] as String?; final backendCopyright = result['copyright'] as String?; + final backendComposer = result['composer'] as String?; final effectiveGenre = normalizeOptionalString(backendGenre) ?? normalizeOptionalString(genre) ?? @@ -5921,11 +5928,17 @@ class DownloadQueueNotifier extends Notifier { trackNumber: (backendTrackNum != null && backendTrackNum > 0) ? backendTrackNum : trackToDownload.trackNumber, - totalTracks: trackToDownload.totalTracks, + totalTracks: + (backendTotalTracks != null && backendTotalTracks > 0) + ? backendTotalTracks + : trackToDownload.totalTracks, discNumber: (backendDiscNum != null && backendDiscNum > 0) ? backendDiscNum : trackToDownload.discNumber, - totalDiscs: trackToDownload.totalDiscs, + totalDiscs: + (backendTotalDiscs != null && backendTotalDiscs > 0) + ? backendTotalDiscs + : trackToDownload.totalDiscs, duration: trackToDownload.duration, releaseDate: (backendYear != null && backendYear.isNotEmpty) ? backendYear @@ -5934,7 +5947,10 @@ class DownloadQueueNotifier extends Notifier { bitDepth: historyBitDepth, sampleRate: historySampleRate, genre: effectiveGenre, - composer: trackToDownload.composer, + composer: + (backendComposer != null && backendComposer.isNotEmpty) + ? backendComposer + : trackToDownload.composer, label: effectiveLabel, copyright: effectiveCopyright, ),