diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index e49c65dd..ccfb4500 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -163,10 +163,6 @@ class MainActivity: FlutterFragmentActivity() { "sm-t225", "hammerhead", ) - /** - * Check if device should use Skia instead of Impeller. - * Returns true for devices with old/problematic GPUs or old Android versions. - */ private fun shouldDisableImpeller(): Boolean { val hardware = Build.HARDWARE.lowercase(Locale.ROOT) val board = Build.BOARD.lowercase(Locale.ROOT) @@ -215,7 +211,6 @@ class MainActivity: FlutterFragmentActivity() { } /** - * Try to get GPU renderer string. * Note: This may return empty on some devices before OpenGL context is created. */ private fun getGpuRenderer(): String { diff --git a/go_backend/ape_tags.go b/go_backend/ape_tags.go index d0b9975a..fe112f97 100644 --- a/go_backend/ape_tags.go +++ b/go_backend/ape_tags.go @@ -80,13 +80,11 @@ func readAPETagAtOffset(f *os.File, fileSize, footerOffset int64) (*APETag, erro return nil, fmt.Errorf("invalid footer offset") } - // Read the 32-byte footer/header footer := make([]byte, apeTagHeaderSize) if _, err := f.ReadAt(footer, footerOffset); err != nil { return nil, fmt.Errorf("failed to read APE footer: %w", err) } - // Verify preamble if string(footer[0:8]) != apeTagPreamble { return nil, fmt.Errorf("APE preamble not found") } @@ -96,7 +94,6 @@ func readAPETagAtOffset(f *os.File, fileSize, footerOffset int64) (*APETag, erro itemCount := binary.LittleEndian.Uint32(footer[16:20]) flags := binary.LittleEndian.Uint32(footer[20:24]) - // Sanity checks if version != apeTagVersion2 && version != 1000 { return nil, fmt.Errorf("unsupported APE tag version: %d", version) } @@ -113,7 +110,6 @@ func readAPETagAtOffset(f *os.File, fileSize, footerOffset int64) (*APETag, erro return nil, fmt.Errorf("expected APE footer but found header") } - // Calculate where the items data starts. // tagSize includes items + footer (32 bytes), but NOT the header. itemsSize := int64(tagSize) - apeTagHeaderSize if itemsSize < 0 { @@ -125,13 +121,11 @@ func readAPETagAtOffset(f *os.File, fileSize, footerOffset int64) (*APETag, erro return nil, fmt.Errorf("APE tag items extend before file start") } - // Read all items data itemsData := make([]byte, itemsSize) if _, err := f.ReadAt(itemsData, itemsOffset); err != nil { return nil, fmt.Errorf("failed to read APE items: %w", err) } - // Parse individual items items, err := parseAPEItems(itemsData, int(itemCount)) if err != nil { return nil, fmt.Errorf("failed to parse APE items: %w", err) @@ -167,9 +161,8 @@ func parseAPEItems(data []byte, count int) ([]APETagItem, error) { } key := string(data[pos:keyEnd]) - pos = keyEnd + 1 // skip null terminator + pos = keyEnd + 1 - // Read value if pos+valueSize > len(data) { break } @@ -190,19 +183,16 @@ func parseAPEItems(data []byte, count int) ([]APETagItem, error) { // If the file already has APEv2 tags, they are replaced. // The tag is written with both header and footer. func WriteAPETags(filePath string, tag *APETag) error { - // First, read existing file to find and strip any existing APE tag existingSize, err := findExistingAPETagSize(filePath) if err != nil { return fmt.Errorf("failed to check existing APE tag: %w", err) } - // Build the new tag data tagData, err := marshalAPETag(tag) if err != nil { return fmt.Errorf("failed to marshal APE tag: %w", err) } - // If there's an existing tag, we need to truncate the file first if existingSize > 0 { fi, err := os.Stat(filePath) if err != nil { @@ -214,7 +204,6 @@ func WriteAPETags(filePath string, tag *APETag) error { } } - // Append the new tag f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("failed to open file for writing: %w", err) @@ -243,7 +232,6 @@ func findExistingAPETagSize(filePath string) (int64, error) { } fileSize := fi.Size() - // Try to read footer offsets := []int64{fileSize - apeTagHeaderSize} if fileSize > apeTagHeaderSize+128 { offsets = append(offsets, fileSize-apeTagHeaderSize-128) @@ -263,7 +251,7 @@ func findExistingAPETagSize(filePath string) (int64, error) { flags := binary.LittleEndian.Uint32(footer[20:24]) if (flags & apeTagFlagHeader) != 0 { - continue // This is a header, not footer + continue } tagSize := int64(binary.LittleEndian.Uint32(footer[12:16])) @@ -292,7 +280,6 @@ func marshalAPETag(tag *APETag) ([]byte, error) { return nil, fmt.Errorf("empty APE tag") } - // Build items data var itemsData []byte for _, item := range tag.Items { keyBytes := []byte(item.Key) @@ -309,7 +296,7 @@ func marshalAPETag(tag *APETag) ([]byte, error) { itemsData = append(itemsData, sizeBuf...) itemsData = append(itemsData, flagsBuf...) itemsData = append(itemsData, keyBytes...) - itemsData = append(itemsData, 0) // null terminator + itemsData = append(itemsData, 0) itemsData = append(itemsData, valueBytes...) } @@ -322,12 +309,10 @@ func marshalAPETag(tag *APETag) ([]byte, error) { version = tag.Version } - // Build header // flags: bit 29 = 1 (is header), bit 31 = 1 (contains header) headerFlags := uint32(apeTagFlagHeader | (1 << 31)) header := buildAPEHeaderFooter(version, tagSize, itemCount, headerFlags) - // Build footer // flags: bit 29 = 0 (is footer), bit 31 = 1 (contains header) footerFlags := uint32(1 << 31) footer := buildAPEHeaderFooter(version, tagSize, itemCount, footerFlags) @@ -463,7 +448,6 @@ func AudioMetadataToAPEItems(metadata *AudioMetadata) []APETagItem { // the metadata fields map sent by the editor. This is used during merge to // ensure that even empty (cleared) fields override old values. func apeKeysFromFields(fields map[string]string) map[string]struct{} { - // Map from fields-map key → APE tag key. mapping := map[string]string{ "title": "TITLE", "artist": "ARTIST", @@ -490,29 +474,25 @@ func apeKeysFromFields(fields map[string]string) map[string]struct{} { result[strings.ToUpper(apeKey)] = struct{}{} } } - // The reader accepts both YEAR and DATE for the date field; the writer - // always emits "Year". Ensure both variants are overridden so that an - // old "DATE" tag from another tagger is removed when the user edits date. + // Some fields have reader aliases that must also be cleared when the + // canonical key is updated (e.g. "Year" writer ↔ DATE/YEAR reader, + // DISC ↔ DISCNUMBER, TRACK ↔ TRACKNUMBER, "ALBUM ARTIST" ↔ ALBUMARTIST, + // LABEL ↔ PUBLISHER, LYRICS ↔ UNSYNCEDLYRICS). if _, present := fields["date"]; present { result["DATE"] = struct{}{} } - // Similarly, DISCNUMBER is an alias for DISC in the reader. if _, present := fields["disc_number"]; present { result["DISCNUMBER"] = struct{}{} } - // TRACKNUMBER is an alias for TRACK in the reader. if _, present := fields["track_number"]; present { result["TRACKNUMBER"] = struct{}{} } - // ALBUMARTIST is an alias for ALBUM ARTIST in the reader. if _, present := fields["album_artist"]; present { result["ALBUMARTIST"] = struct{}{} } - // PUBLISHER is an alias for LABEL in the reader. if _, present := fields["label"]; present { result["PUBLISHER"] = struct{}{} } - // UNSYNCEDLYRICS is an alias for LYRICS in the reader. if _, present := fields["lyrics"]; present { result["UNSYNCEDLYRICS"] = struct{}{} } @@ -538,7 +518,6 @@ func MergeAPEItems(existing, newItems []APETagItem, overrideKeys map[string]stru combined[strings.ToUpper(item.Key)] = struct{}{} } - // Start with existing items whose keys are NOT in the combined set var merged []APETagItem for _, item := range existing { if _, overwritten := combined[strings.ToUpper(item.Key)]; !overwritten { @@ -546,7 +525,6 @@ func MergeAPEItems(existing, newItems []APETagItem, overrideKeys map[string]stru } } - // Append all new items merged = append(merged, newItems...) return merged diff --git a/go_backend/deezer_download.go b/go_backend/deezer_download.go index 2df4bc0e..8bbe97d3 100644 --- a/go_backend/deezer_download.go +++ b/go_backend/deezer_download.go @@ -62,7 +62,6 @@ func resolveDeezerTrackURL(req DownloadRequest) (string, error) { return trackURL, nil } - // Try SongLink spotifyID := strings.TrimSpace(req.SpotifyID) if spotifyID != "" && isLikelySpotifyTrackID(spotifyID) { songlink := NewSongLinkClient() @@ -82,7 +81,6 @@ func resolveDeezerTrackURL(req DownloadRequest) (string, error) { } } - // Try ISRC isrc := strings.TrimSpace(req.ISRC) if isrc != "" { ctx, cancel := context.WithTimeout(context.Background(), SongLinkTimeout) diff --git a/go_backend/exports.go b/go_backend/exports.go index 200dd831..5349cce9 100644 --- a/go_backend/exports.go +++ b/go_backend/exports.go @@ -171,10 +171,6 @@ func applyReEnrichTrackMetadata(req *reEnrichRequest, track ExtTrackMetadata) { req.SpotifyID = track.ID } - if req.shouldUpdateField("basic_tags") { - // Title and Artist are not overwritten — they are used for search matching - // and should remain as the user's original values. - } if req.shouldUpdateField("basic_tags") { if track.AlbumName != "" { req.AlbumName = track.AlbumName @@ -768,8 +764,7 @@ func DownloadTrack(requestJSON string) (string, error) { return string(jsonBytes), nil } -// DownloadByStrategy routes a unified download request to the appropriate flow. -// Routing priority: YouTube service > extension fallback > built-in fallback > direct service. +// DownloadByStrategy routes download requests with priority: YouTube > extension fallback > built-in fallback > direct service. func DownloadByStrategy(requestJSON string) (string, error) { var req DownloadRequest if err := json.Unmarshal([]byte(requestJSON), &req); err != nil { @@ -1067,7 +1062,6 @@ func ReadFileMetadata(filePath string) (string, error) { result["copyright"] = metadata.Copyright result["composer"] = metadata.Composer result["comment"] = metadata.Comment - // ReplayGain fields result["replaygain_track_gain"] = metadata.ReplayGainTrackGain result["replaygain_track_peak"] = metadata.ReplayGainTrackPeak result["replaygain_album_gain"] = metadata.ReplayGainAlbumGain @@ -1214,8 +1208,7 @@ func ReadFileMetadata(filePath string) (string, error) { return string(jsonBytes), nil } -// ParseCueSheet parses a .cue file and returns JSON with split information. -// This is called from Dart to get track listing and timing data for CUE splitting. +// ParseCueSheet is called from Dart to get track listing and timing data for CUE splitting. // audioDir, if non-empty, overrides the directory used for resolving the // referenced audio file (useful for SAF temp file scenarios). func ParseCueSheet(cuePath string, audioDir string) (string, error) { @@ -1260,9 +1253,7 @@ func ScanCueSheetForLibraryWithCoverCacheKey(cuePath, audioDir, virtualPathPrefi return string(jsonBytes), nil } -// EditFileMetadata writes metadata to an audio file. -// For FLAC files, uses native Go FLAC library. -// For MP3/Opus, returns the metadata map so Dart can use FFmpeg. +// EditFileMetadata writes audio file tags: FLAC via native Go library, MP3/Opus returns map for Dart/FFmpeg. func EditFileMetadata(filePath, metadataJSON string) (string, error) { var fields map[string]string if err := json.Unmarshal([]byte(metadataJSON), &fields); err != nil { @@ -1299,20 +1290,19 @@ func EditFileMetadata(filePath, metadataJSON string) (string, error) { } meta := &AudioMetadata{ - Title: fields["title"], - Artist: fields["artist"], - Album: fields["album"], - AlbumArtist: fields["album_artist"], - Date: fields["date"], - TrackNumber: trackNum, - DiscNumber: discNum, - ISRC: fields["isrc"], - Genre: fields["genre"], - Label: fields["label"], - Copyright: fields["copyright"], - Composer: fields["composer"], - Comment: fields["comment"], - // ReplayGain fields + Title: fields["title"], + Artist: fields["artist"], + Album: fields["album"], + AlbumArtist: fields["album_artist"], + Date: fields["date"], + TrackNumber: trackNum, + DiscNumber: discNum, + ISRC: fields["isrc"], + Genre: fields["genre"], + Label: fields["label"], + Copyright: fields["copyright"], + Composer: fields["composer"], + Comment: fields["comment"], ReplayGainTrackGain: fields["replaygain_track_gain"], ReplayGainTrackPeak: fields["replaygain_track_peak"], ReplayGainAlbumGain: fields["replaygain_album_gain"], @@ -2917,7 +2907,7 @@ func CustomSearchWithExtensionJSON(extensionID, query string, optionsJSON string "album_name": track.AlbumName, "album_artist": track.AlbumArtist, "duration_ms": track.DurationMS, - "images": track.ResolvedCoverURL(), // Use helper to get cover URL from either field + "images": track.ResolvedCoverURL(), "release_date": track.ReleaseDate, "track_number": track.TrackNumber, "disc_number": track.DiscNumber, diff --git a/go_backend/extension_timeout.go b/go_backend/extension_timeout.go index 129e05ec..78f3d2db 100644 --- a/go_backend/extension_timeout.go +++ b/go_backend/extension_timeout.go @@ -104,7 +104,6 @@ func RunWithTimeout(vm *goja.Runtime, script string, timeout time.Duration) (goj func RunWithTimeoutAndRecover(vm *goja.Runtime, script string, timeout time.Duration) (goja.Value, error) { result, err := RunWithTimeout(vm, script, timeout) - // Clear any interrupt state so VM can be reused if vm != nil { vm.ClearInterrupt() } diff --git a/go_backend/logbuffer.go b/go_backend/logbuffer.go index 76d46762..517cddef 100644 --- a/go_backend/logbuffer.go +++ b/go_backend/logbuffer.go @@ -51,7 +51,7 @@ func GetLogBuffer() *LogBuffer { globalLogBuffer = &LogBuffer{ entries: make([]LogEntry, 0, defaultLogBufferSize), maxSize: defaultLogBufferSize, - loggingEnabled: false, // Default: disabled for performance (user can enable in settings) + loggingEnabled: false, } }) return globalLogBuffer diff --git a/go_backend/metadata.go b/go_backend/metadata.go index 1aee724b..8f6446e3 100644 --- a/go_backend/metadata.go +++ b/go_backend/metadata.go @@ -309,7 +309,6 @@ func ReadMetadata(filePath string) (*Metadata, error) { metadata.Composer = getComment(cmt, "COMPOSER") metadata.Comment = getComment(cmt, "COMMENT") - // ReplayGain tags metadata.ReplayGainTrackGain = getComment(cmt, "REPLAYGAIN_TRACK_GAIN") metadata.ReplayGainTrackPeak = getComment(cmt, "REPLAYGAIN_TRACK_PEAK") metadata.ReplayGainAlbumGain = getComment(cmt, "REPLAYGAIN_ALBUM_GAIN") @@ -350,7 +349,7 @@ func EditFlacFields(filePath string, fields map[string]string) error { cmt = flacvorbis.New() } - artistMode := fields["artist_tag_mode"] // may be "" + artistMode := fields["artist_tag_mode"] // Mapping from fields-map key → one or more Vorbis Comment keys. // Each entry is handled with set-or-clear semantics. @@ -448,12 +447,10 @@ func EditFlacFields(filePath string, fields map[string]string) error { f.Meta = append(f.Meta, &cmtBlock) } - // Cover art coverPath := strings.TrimSpace(fields["cover_path"]) if coverPath != "" && fileExists(coverPath) { coverData, err := os.ReadFile(coverPath) if err == nil && len(coverData) > 0 { - // Remove existing pictures for i := len(f.Meta) - 1; i >= 0; i-- { if f.Meta[i].Type == flac.Picture { f.Meta = append(f.Meta[:i], f.Meta[i+1:]...) diff --git a/go_backend/songlink.go b/go_backend/songlink.go index d7ba5697..0ef45375 100644 --- a/go_backend/songlink.go +++ b/go_backend/songlink.go @@ -175,7 +175,6 @@ func (s *SongLinkClient) doResolveRequest(payload []byte) (map[string]songLinkPl return nil, fmt.Errorf("resolve API returned success=false") } - // Map resolve API keys to SongLink-compatible platform keys keyMap := map[string]string{ "Spotify": "spotify", "Deezer": "deezer", @@ -509,8 +508,6 @@ func extractYouTubeIDFromURL(youtubeURL string) string { return "" } -// isNumeric is defined in library_scan.go - func (s *SongLinkClient) GetDeezerIDFromSpotify(spotifyTrackID string) (string, error) { availability, err := s.CheckTrackAvailability(spotifyTrackID, "") if err != nil { diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index b7a96844..aa2b4657 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -2473,7 +2473,6 @@ class DownloadQueueNotifier extends Notifier { _locallyCancelledItemIds.remove(id); } - // Clean accumulator entry for non-completed items. if (item.status != DownloadStatus.completed) { final key = _albumRgKey(item.track); final accumulator = _albumRgData[key]; @@ -2499,7 +2498,6 @@ class DownloadQueueNotifier extends Notifier { } void clearCompleted() { - // Purge accumulator entries for failed/skipped items being removed. final removedItems = state.items.where( (item) => item.status == DownloadStatus.completed || @@ -2760,7 +2758,6 @@ class DownloadQueueNotifier extends Notifier { } void clearFailedDownloads() { - // Purge accumulator entries for failed items before removing them. final failedItems = state.items .where((item) => item.status == DownloadStatus.failed) .toList(); @@ -2883,7 +2880,6 @@ class DownloadQueueNotifier extends Notifier { final key = _albumRgKey(track); final accumulator = _albumRgData[key]; if (accumulator == null) return; - // Find the entry for this track and update its file path in-place. for (final entry in accumulator.entries) { if (entry.trackId == track.id) { entry.filePath = finalPath; @@ -2969,7 +2965,6 @@ class DownloadQueueNotifier extends Notifier { 'Album ReplayGain for "$key": gain=$albumGain, peak=$albumPeak (${validEntries.length} tracks, album LUFS=${albumLufs.toStringAsFixed(1)})', ); - // Write album gain to every completed track file. for (final entry in validEntries) { try { await _writeAlbumReplayGain(entry.filePath, albumGain, albumPeak); diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index fa5337ea..27f9a905 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -496,7 +496,6 @@ class _HomeTabState extends ConsumerState } } - /// Check if live search is available (extension is set as search provider) bool _isLiveSearchEnabled() { final settings = ref.read(settingsProvider); final extState = ref.read(extensionProvider); @@ -564,7 +563,6 @@ class _HomeTabState extends ConsumerState } } - /// Built-in search providers that are not extensions static const _builtInSearchProviders = {'tidal', 'qobuz'}; Future _performSearch(String query, {String? filterOverride}) async { @@ -599,7 +597,6 @@ class _HomeTabState extends ConsumerState .read(trackProvider.notifier) .customSearch(searchProvider, query, options: options); } else if (isBuiltInProvider) { - // Use built-in Tidal or Qobuz search await ref .read(trackProvider.notifier) .search( @@ -1122,7 +1119,7 @@ class _HomeTabState extends ConsumerState title: Text( context.l10n.homeTitle, style: TextStyle( - fontSize: 20 + (14 * expandRatio), // 20 -> 34 + fontSize: 20 + (14 * expandRatio), fontWeight: FontWeight.bold, color: colorScheme.onSurface, ), @@ -1496,7 +1493,7 @@ class _HomeTabState extends ConsumerState ) { final hasGreeting = greeting != null && greeting.isNotEmpty; final sectionOffset = hasGreeting ? 1 : 0; - final totalCount = sections.length + sectionOffset + 1; // + bottom padding + final totalCount = sections.length + sectionOffset + 1; return [ SliverList( @@ -2939,7 +2936,7 @@ class _HomeTabState extends ConsumerState albumId: album.id, albumName: album.name, coverUrl: album.imageUrl, - tracks: const [], // Will be fetched by AlbumScreen + tracks: const [], ), ), ); @@ -2965,7 +2962,7 @@ class _HomeTabState extends ConsumerState builder: (context) => PlaylistScreen( playlistName: playlist.name, coverUrl: playlist.imageUrl, - tracks: const [], // Will be fetched + tracks: const [], playlistId: playlist.id, ), ), @@ -3694,7 +3691,7 @@ class _TrackItemWithStatus extends ConsumerWidget { thickness: 1, indent: thumbWidth + - 24, // Adjust divider indent based on thumbnail width + 24, endIndent: 12, color: colorScheme.outlineVariant.withValues(alpha: 0.3), ), diff --git a/lib/screens/queue_tab.dart b/lib/screens/queue_tab.dart index 923fc494..6481f579 100644 --- a/lib/screens/queue_tab.dart +++ b/lib/screens/queue_tab.dart @@ -1132,11 +1132,11 @@ class _QueueTabState extends ConsumerState { String? _filterCacheFormat; String? _filterCacheMetadata; String _filterCacheSortMode = 'latest'; - String? _filterSource; // null = all, 'downloaded', 'local' - String? _filterQuality; // null = all, 'hires', 'cd', 'lossy' - String? _filterFormat; // null = all, 'flac', 'mp3', 'm4a', 'opus', 'ogg' - String? _filterMetadata; // null = all, 'complete', 'missing-*' - String _sortMode = 'latest'; // 'latest', 'oldest', 'a-z', 'z-a' + String? _filterSource; + String? _filterQuality; + String? _filterFormat; + String? _filterMetadata; + String _sortMode = 'latest'; double _effectiveTextScale() { final textScale = MediaQuery.textScalerOf(context).scale(1.0); @@ -2036,7 +2036,6 @@ class _QueueTabState extends ConsumerState { return quality.split('/').first; } - // Supports "MP3 320k", "Opus 256kbps", etc. final bitrateTextMatch = RegExp( r'(\d+)\s*k(?:bps)?', caseSensitive: false, @@ -2045,7 +2044,6 @@ class _QueueTabState extends ConsumerState { return '${bitrateTextMatch.group(1)}k'; } - // Supports legacy quality IDs like "opus_256" / "mp3_320". final bitrateIdMatch = RegExp(r'_(\d+)$').firstMatch(q); if (bitrateIdMatch != null) { return '${bitrateIdMatch.group(1)}k'; @@ -2301,7 +2299,7 @@ class _QueueTabState extends ConsumerState { List _applySorting(List items) { if (_sortMode == 'latest') { - return items; // Already sorted newest first from _getUnifiedItems + return items; } final sorted = List.of(items); switch (_sortMode) { @@ -3364,7 +3362,6 @@ class _QueueTabState extends ConsumerState { final selectionItems = getFilterData( historyFilterMode, ).filteredUnifiedItems; - // Only sync overlays when selection mode is active if (_isSelectionMode || _isPlaylistSelectionMode) { WidgetsBinding.instance.addPostFrameCallback((_) { if (_isSelectionMode) { diff --git a/lib/screens/settings/donate_page.dart b/lib/screens/settings/donate_page.dart index 16b629c4..a84d886f 100644 --- a/lib/screens/settings/donate_page.dart +++ b/lib/screens/settings/donate_page.dart @@ -164,13 +164,7 @@ class _RecentDonorsCard extends StatelessWidget { @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; - const donorNames = [ - 'McNuggets Jimmy', - 'zcc09', - 'micahRichie', - 'a fan', - 'CJBGR', - ]; + const donorNames = ['R4ND0MIZ3D', 'Isra', 'bigJr48']; // Match SettingsGroup color logic final cardColor = isDark diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 506056ea..dd3024ad 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -2210,7 +2210,6 @@ class _TrackMetadataScreenState extends ConsumerState { final baseName = _buildSaveBaseName(); if (_isSafFile) { - // SAF file: save to temp, then copy to SAF tree final tempDir = await Directory.systemTemp.createTemp('cover_'); final tempOutput = '${tempDir.path}${Platform.pathSeparator}$baseName.jpg'; @@ -2293,7 +2292,6 @@ class _TrackMetadataScreenState extends ConsumerState { } } } else { - // No SAF tree info, keep in temp try { await Directory(tempDir.path).delete(recursive: true); } catch (_) {} @@ -5192,7 +5190,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { final method = result['method'] as String?; if (method == 'ffmpeg') { - // MP3/Opus: use FFmpeg to write metadata // For SAF files, Kotlin returns temp_path + saf_uri final tempPath = result['temp_path'] as String?; final safUri = result['saf_uri'] as String?; @@ -5280,7 +5277,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> { } catch (_) {} } } catch (_) { - // No cover to preserve, continue without } } diff --git a/lib/services/ffmpeg_service.dart b/lib/services/ffmpeg_service.dart index 1555f8fb..91c29ccb 100644 --- a/lib/services/ffmpeg_service.dart +++ b/lib/services/ffmpeg_service.dart @@ -892,7 +892,6 @@ class FFmpegService { /// /// Returns a [ReplayGainResult] on success, or null if the scan fails. static Future scanReplayGain(String filePath) async { - // Run FFmpeg with ebur128 filter + astats for true peak. // -nostats suppresses the interactive progress line. // ebur128=peak=true prints integrated loudness + true peak. // framelog=quiet suppresses per-frame measurements (very verbose), @@ -941,7 +940,6 @@ class FFmpegService { } } - // ReplayGain reference level: -18 LUFS const replayGainReferenceLufs = -18.0; final gainDb = replayGainReferenceLufs - integratedLufs; @@ -949,13 +947,11 @@ class FFmpegService { // If no true peak was found, fall back to 1.0 (0 dBFS). double peakLinear; if (truePeakDbfs != null) { - // 10^(dBFS/20) converts dBFS to linear amplitude peakLinear = math.pow(10, truePeakDbfs / 20.0).toDouble(); } else { peakLinear = 1.0; } - // Format to standard ReplayGain precision final trackGain = '${gainDb >= 0 ? "+" : ""}${gainDb.toStringAsFixed(2)} dB'; final trackPeak = peakLinear.toStringAsFixed(6); @@ -1791,7 +1787,6 @@ class FFmpegService { vorbis['LYRICS'] = value; vorbis['UNSYNCEDLYRICS'] = value; break; - // ReplayGain fields case 'REPLAYGAINTRACKGAIN': vorbis['REPLAYGAIN_TRACK_GAIN'] = value; break; diff --git a/lib/services/platform_bridge.dart b/lib/services/platform_bridge.dart index e897727d..7d79eaa7 100644 --- a/lib/services/platform_bridge.dart +++ b/lib/services/platform_bridge.dart @@ -354,7 +354,7 @@ class PlatformBridge { return jsonDecode(result as String) as Map; } - /// Sets the lyrics provider order. Providers not in the list are disabled. + /// Providers not in the list are disabled. static Future setLyricsProviders(List providers) async { final providersJSON = jsonEncode(providers); await _channel.invokeMethod('setLyricsProviders', { @@ -362,14 +362,12 @@ class PlatformBridge { }); } - /// Returns the current lyrics provider order. static Future> getLyricsProviders() async { final result = await _channel.invokeMethod('getLyricsProviders'); final List decoded = jsonDecode(result as String) as List; return decoded.cast(); } - /// Returns metadata about all available lyrics providers. static Future>> getAvailableLyricsProviders() async { final result = await _channel.invokeMethod('getAvailableLyricsProviders'); @@ -387,7 +385,6 @@ class PlatformBridge { }); } - /// Returns current advanced lyrics fetch options. static Future> getLyricsFetchOptions() async { final result = await _channel.invokeMethod('getLyricsFetchOptions'); return jsonDecode(result as String) as Map;