Commit Graph

1252 Commits

Author SHA1 Message Date
zarzet ddd976de35 fix: respect user provider choice over source extension priority 2026-04-14 02:09:14 +07:00
zarzet d47ac0934d chore: bump version to 4.3.0 and fix SAF document file race condition
- Bump app version to 4.3.0 (build 125)
- Extract createOrReuseDocumentFile() to handle SAF auto-rename races
  between findFile() and createFile(), preferring the exact-named sibling
  and discarding duplicate documents
2026-04-13 23:35:03 +07:00
zarzet dbba4d6630 feat: propagate download cancel to extension HTTP requests and fix SAF filename extension mismatch
- Bind cancel context to all extension HTTP calls (fetch, httpGet, httpPost,
  httpRequest, fileDownload, authExchangeCodeWithPKCE) so in-flight requests
  are aborted when user cancels a download
- Make initDownloadCancel idempotent: return existing context if entry already
  exists and preserve pre-cancelled state
- Force SAF output filename to match actual file extension when extension
  returns a different format than requested (e.g. FLAC requested but M4A produced)
- Map ALAC/AAC quality to .m4a instead of falling through to default .flac
2026-04-13 23:35:03 +07:00
zarzet 7405855e01 fix: handle extension oauth callback on ios 2026-04-13 23:35:03 +07:00
zarzet ed020c9303 feat: native M4A ReplayGain tag writing and SAF picker error handling 2026-04-13 23:35:03 +07:00
zarzet 378742e37a refactor: remove author field from extension manifest and UI 2026-04-13 23:35:03 +07:00
zarzet c79bee534e fix: align default search tab layout with primary provider selector using Row+Expanded 2026-04-13 23:35:03 +07:00
zarzet 1d6df75829 fix: preserve existing M4A metadata during embed and enable BuildConfig generation 2026-04-13 23:35:03 +07:00
zarzet b7f51b5f14 feat: expose extension utils, preserve M4A native container, and bump to v4.2.3+124 2026-04-13 23:35:03 +07:00
zarzet 1c8e9df727 feat: add artist search filter and normalize search filter handling 2026-04-13 23:35:03 +07:00
zarzet 01540fe3fc fix: improve ALAC M4A quality parsing 2026-04-13 23:35:03 +07:00
zarzet 071db2f109 refactor: move deezer search flow to extension 2026-04-13 23:35:02 +07:00
zarzet e097d3f605 fix: stabilize shared extension link handling 2026-04-13 23:35:02 +07:00
zarzet 277f783f62 feat: add default search tab preference 2026-04-13 23:35:02 +07:00
zarzet 7637aaf168 fix: fallback extra metadata genre 2026-04-13 23:35:02 +07:00
zarzet c4878470bf chore: thank Ldav Nico and Feuerstern on donate page 2026-04-13 23:35:02 +07:00
zarzet a3725e8c48 feat: add keep android open link 2026-04-13 23:35:02 +07:00
zarzet 917ba842f5 fix: align metadata sanitization and lyrics editing 2026-04-13 23:32:19 +07:00
zarzet dac17ead33 chore: bump app to v4.2.2 2026-04-13 23:32:19 +07:00
zarzet 6845ebe04c refactor: move deezer to extension 2026-04-13 23:32:18 +07:00
zarzet eff709480d fix: preserve flat singles output for extension releases 2026-04-13 23:32:18 +07:00
zarzet 67833424cc fix: align re-enrich matching with autofill metadata 2026-04-13 23:32:18 +07:00
zarzet 5c48e1b476 fix: persist downloaded metadata and refine metadata navigation 2026-04-13 23:32:18 +07:00
zarzet 5e17c9f238 feat: add configurable extension download fallback 2026-04-13 23:32:18 +07:00
zarzet 7d330fb2ec fix: preserve composer metadata across qobuz and history 2026-04-13 23:32:18 +07:00
zarzet cd6a4594fa chore: bump app version to v4.2.1 2026-04-13 23:32:17 +07:00
zarzet bcf727f4ec fix: remove stale audio service manifest entries causing crashes on some devices 2026-04-13 23:32:17 +07:00
zarzet 4c4553913f fix: harden gomobile extension bindings and m4a cover retention 2026-04-13 23:32:17 +07:00
zarzet f0013fac16 fix: preserve local convert format and library entries 2026-04-13 23:32:17 +07:00
zarzet ce4be0ba97 feat: enrich composer and track totals metadata 2026-04-13 23:32:17 +07:00
zarzet 4bac38ef2a fix: preserve embedded metadata details 2026-04-13 23:32:17 +07:00
zarzet 4b213f47d9 ci: pin iOS release builds to macOS 15 and Xcode 26.1.1 2026-04-13 23:32:16 +07:00
zarzet a1010f72f2 fix: patch device_info_plus iOS build for older Xcode SDKs 2026-04-13 23:32:16 +07:00
zarzet 21077a26d0 feat: add additional search/metadata API with separate rate limiting 2026-04-13 23:32:16 +07:00
zarzet b50eec5a47 perf: incremental download queue lookup updates, async cover cleanup, and native JSON decoding on iOS
- Embed DownloadQueueLookup into DownloadQueueState; add updatedForIndices() for O(changed) incremental updates during frequent progress ticks instead of full O(n) rebuild
- downloadQueueLookupProvider now reads pre-computed lookup from state directly
- Replace sync file deletion in DownloadedEmbeddedCoverResolver with unawaited async cleanup to avoid blocking the main thread
- Parse JSON payloads on iOS native side (parseJsonPayload) so event sinks and method channel responses return native objects, avoiding redundant Dart-side JSON decode
- Use .cast<String, dynamic>() instead of Map.from() in _decodeMapResult for zero-copy map handling
2026-04-13 23:32:16 +07:00
zarzet 38a8b715f8 perf: reduce UI jank via memoization, compute isolates, SQL-backed playlist picker, and viewport-aware image caching
- Move explore JSON decode/encode to compute() isolate to avoid blocking main thread
- Memoize search sort results (artists/albums/playlists/tracks) in HomeTab; invalidate on new query
- Extract _DownloadedOrRemoteCover StatefulWidget with proper embedded-cover lifecycle management
- Replace O(playlists x tracks) in-memory playlist picker check with SQL loadPlaylistPickerSummaries query
- Add FutureProvider.family (libraryPlaylistPickerSummariesProvider) invalidated on all playlist mutations
- Memoize _buildQueueHistoryStats, localPathMatchKeys, and localSingleItems in QueueTab
- Add coverCacheWidthForViewport util; apply memCacheWidth/cacheWidth based on real DPR across all album/playlist/track screens
- Convert sync file ops in TrackMetadataScreen to async; use mtime+size as validation token
- Fetch Deezer album nb_tracks in parallel via fetchAlbumTrackCounts
2026-04-13 23:32:16 +07:00
zarzet 2b47537bb5 fix: route Qobuz API calls through authenticated gateway to resolve 401 errors 2026-04-13 23:32:16 +07:00
zarzet a5cf241846 refactor: consolidate FLAC/MP3/Opus metadata embedding into unified _embedMetadataToFile 2026-04-13 23:32:16 +07:00
zarzet 53a4773480 feat: add skipLyrics manifest field for extensions to opt out of lyrics fetching 2026-04-13 23:32:16 +07:00
zarzet 89603af1f1 chore: remove redundant comments and update donor list 2026-04-13 23:32:15 +07:00
zarzet 2143084d3c fix: resolve missing track/disc numbers from search downloads and suppress FFmpeg log noise
- Tidal: use actual API track_number/disc_number when request values are 0
  (fixes search/popular downloads having no track position in metadata)
- Extension enrichment: copy TrackNumber/DiscNumber back from enriched results
- Extension fallback download: add request metadata fallback for non-source
  extensions (Album, AlbumArtist, ReleaseDate, ISRC, TrackNumber, DiscNumber)
- FFmpeg: add -v error -hide_banner to all commands (embed, convert, CUE split)
  to eliminate banner, build config, and full metadata/lyrics dump in logcat
- ebur128: add framelog=quiet to suppress per-frame loudness measurements
  while keeping the summary needed for ReplayGain parsing
- Track metadata screen: separate embedded lyrics check from online fetch,
  show file-only state with manual online fetch button
2026-04-13 23:32:15 +07:00
zarzet 0e265193b8 fix: improve extension runtime safety, HTTP response URL, SongLink parsing, and recommended service for extensions
- Add 'url' field (final URL after redirects) to all extension HTTP responses and fix fetch polyfill to return final URL instead of original request URL
- Fix RunWithTimeout race condition: increase force-timeout from 1s to 60s to prevent concurrent VM access crashes, add nil guards
- Use lockReadyVM() for thread-safe VM access in GetPlaylistWithExtensionJSON and InvokeAction
- Handle mixed JSON types (string, null, array) in SongLink resolve API SongUrls field
- Fix recommended download service not showing for extension-based searches in download picker
2026-04-13 23:32:15 +07:00
zarzet c7e9749ce4 fix: resolve label and copyright from file metadata on info screen
The info screen was not reading label/copyright from the actual file metadata, so these fields were always empty for local library items and download history items that lacked them in-memory. Now _resolveAudioMetadata() extracts and displays them without requiring a manual save first.
2026-04-13 23:32:15 +07:00
zarzet e21cffff0b fix: validate ISRC in track metadata screen to prevent ID leakage
Sanitize the isrc getter to only return valid ISRC codes (12-char format per ISO 3901). Invalid values such as Spotify/Deezer/Tidal IDs that may leak into the ISRC field are now silently discarded, preventing them from being displayed or embedded into file tags.
2026-04-13 23:32:15 +07:00
zarzet d9e20040be fix: correct track/disc defaults, forward extension metadata, and fix service ID display
- Default track/disc number to 0 (unknown) instead of 1, letting the
  backend use the service-provided value or skip the field entirely
- Add releaseDate to ExploreItem so explore downloads carry release info
- Pass discNumber and releaseDate from extension album/playlist tracks
- Fix isDeezer detection using service field instead of substring match
- Add _displayServiceTrackId() to properly strip prefixes for all services
2026-04-13 23:32:15 +07:00
zarzet 6689173525 chore: bump version to 4.2.0 (build 121) 2026-04-13 23:32:14 +07:00
zarzet f37e4704a6 feat: add ReplayGain scanning, APEv2 tag support, and fix metadata bugs
ReplayGain (track + album):
- Scan track loudness via FFmpeg ebur128 filter (-18 LUFS reference)
- Duration-weighted power-mean for album gain computation
- Support for FLAC (native Vorbis), MP3 (ID3v2 TXXX), Opus, M4A
- Album RG auto-finalizes when all album tracks complete
- Retryable gate: blocks finalization while failed/skipped items exist
- SAF support: lossy album RG writes via temp file + writeTempToSaf
- New embedReplayGain setting (off by default) with UI toggle

APEv2 tag support:
- Full APEv2 reader/writer with header+items+footer format
- Merge-based editing with override keys for explicit deletions
- Binary cover art embedding (Cover Art (Front) item)
- Library scanner support for .ape/.wv/.mpc files
- ReplayGain fields in APE read/write/edit pipeline

Bug fixes (26):
- setArtistComments wiping fields on empty string value
- APEv2 rewrite corrupting files with ID3v1 trailer
- APE edit replacing entire tag instead of merging
- ReplayGain lost on manual MP3/Opus/M4A metadata edit
- Editor metadata save losing custom tags (preserveMetadata)
- Album RG accumulator not cleaned on queue mutation
- Album gain using unweighted mean instead of power-mean
- writeAlbumReplayGainTags return value silently ignored
- SAF album RG writing to deleted temp path
- Cancelled tracks polluting album gain computation
- APE ReplayGain not wired end-to-end
- APE field deletion not working in merge
- APE cover edit was a no-op
- Album RG duplicate entries on retry
- APE apeKeysFromFields missing track/disc/lyrics mappings
- Album RG entries purged by removeItem before computation
- FFmpeg converters discarding empty metadata values
- _appendVorbisArtistEntries skipping empty value (null vs empty)
- Album RG write-back fails for SAF lossy files
- Album RG partial finalization on failed tracks
- FLAC ClearEmpty flag destroying tags on partial callers
- clearCompleted not retriggering album RG checks
- ReadFileMetadata MP3/Ogg missing label and copyright
- Cover embed on CUE split destroying split artist tags
- Album RG gain format inconsistent (missing + prefix)
- FLAC reader/editor missing tag aliases (ALBUMARTIST, LABEL, etc.)
- dart:math log shadowed by logger.dart export
2026-04-13 23:32:14 +07:00
zarzet 65dbd5c8e4 fix: remove deleted local library item from provider state after file deletion
When deleting a non-CUE local library track from the metadata screen,
only the file was removed but the library database entry and provider
state were left untouched, causing the track to persist in the library UI.
Now calls removeItem() on localLibraryProvider after deleteFile().
2026-04-13 23:32:14 +07:00
zarzet d034144e9c feat: add resolve API with SongLink fallback, fix multi-artist tags (#288), and cleanup
Resolve API (api.zarz.moe):
- Refactor songlink.go: Spotify URLs use resolve API, non-Spotify uses SongLink API
- Add SongLink fallback when resolve API fails for Spotify (two-layer resilience)
- Remove dead code: page parser, XOR-obfuscated keys, legacy helpers

Multi-artist tag fix (#288):
- Add RewriteSplitArtistTags() in Go to rewrite ARTIST/ALBUMARTIST as split Vorbis comments
- Wire method channel handler in Android (MainActivity.kt) and iOS (AppDelegate.swift)
- Add PlatformBridge.rewriteSplitArtistTags() in Dart
- Call native FLAC rewriter after FFmpeg embed when split_vorbis mode is active
- Extract deezerTrackArtistDisplay() helper to use Contributors in album/playlist tracks

Code cleanup:
- Remove unused imports, dead code, and redundant comments across Go and Dart
- Fix build: remove stale getQobuzDebugKey() reference in deezer_download.go
2026-04-13 23:32:14 +07:00
zarzet 7c4309955e feat: add separate filename format for singles and EPs (#271)
Add singleFilenameFormat setting so singles/EPs can use a different filename template than albums. The format editor is reused with custom title/description. Dart selects the correct format based on track.isSingle before passing to Go, so no backend changes needed. Also fix isSingle getter to include all EPs regardless of totalTracks. Closes #271
2026-04-13 23:32:13 +07:00