Skip to content

SpotiFLAC v3.1.3

Choose a tag to compare

@github-actions github-actions released this 19 Jan 22:01
· 1927 commits to main since this release

What's New

Added

  • External LRC Lyrics File Support: Option to save lyrics as separate .lrc files for compatibility with external music players

    • New "Lyrics Mode" setting in Settings > Download > Lyrics section
    • Three modes available:
      • Embed in file (default): Lyrics stored inside FLAC metadata
      • External .lrc file: Save lyrics as separate .lrc file next to audio file
      • Both: Embed and save external .lrc file
    • Perfect for players like Samsung Music that prefer external .lrc files
    • LRC files include metadata headers (title, artist, by:SpotiFLAC-Mobile)
    • Works with all download services (Tidal, Qobuz, Amazon)
  • CSV Import Quality Selection: Choose audio quality when importing CSV playlists

    • Quality picker now appears before adding CSV tracks to download queue

    • Select between FLAC qualities (Lossless, Hi-Res, Hi-Res Max) or MP3

    • Respects "Ask quality before download" setting - uses default quality if disabled

    • Persistent Cover Image Cache: Album/track cover images now cached to persistent storage instead of temporary directory

    • Cover images no longer disappear when app is closed or device restarts

    • Cache stored in app_flutter/cover_cache/ directory (not cleared by system)

    • Maximum 1000 images cached for up to 365 days

    • Covers are cached when displayed in History, Home, Album, Artist, or any other screen

    • New CoverCacheManager service with clearCache() and getStats() methods for future cache management

  • Extended Metadata from Deezer Enrichment: Track downloads now include label, copyright, and genre metadata from Deezer

    • New fields in ExtTrackMetadata: label, copyright, genre
    • Metadata fetched during enrichTrack() via Deezer album API
    • Embedded as FLAC Vorbis comments: GENRE, ORGANIZATION (label), COPYRIGHT
    • Works for both extension downloads and built-in provider downloads (Tidal, Qobuz, Amazon)
  • Track Metadata Screen Extended Info: Genre, label, and copyright now displayed in track metadata screen

    • Added genre, label, copyright fields to DownloadHistoryItem model
    • Metadata is stored in download history and persists across app restarts
    • New localization strings: trackGenre, trackLabel, trackCopyright
  • utils.randomUserAgent() for Extensions: New utility function for extensions to get random browser User-Agent strings

    • Returns modern Chrome User-Agent format: Chrome/{120-145}.0.{6000-7499}.{100-299} with Windows NT 10.0
    • Useful for extensions that need to rotate User-Agents to avoid detection

Fixed

  • Portuguese Language Bug: Fixed locale parsing for languages with country codes (e.g., pt_PT, es_ES)

    • App now correctly loads Portuguese and Spanish translations
    • Updated Portuguese label to "Português (Brasil)"
  • VM Race Condition Panic: Fixed panic during execution: runtime error: index out of range [-2] crash when switching search providers

    • Root cause: Goja VM was being accessed concurrently by multiple goroutines without synchronization
    • Added VMMu sync.Mutex to LoadedExtension struct
    • Added mutex lock/unlock to ALL ExtensionProviderWrapper methods:
      • SearchTracks, GetTrack, GetAlbum, GetArtist
      • EnrichTrack, CheckAvailability, GetDownloadURL, Download
      • CustomSearch, HandleURL, MatchTrack, PostProcess
    • Prevents race conditions when rapidly switching between extension search providers
  • Tidal Release Date Fallback: Fixed missing release date in FLAC metadata when downloading from Tidal

    • Now uses Tidal API's release date when req.ReleaseDate is empty
    • Ensures release date is always embedded in downloaded files
  • Extended Metadata for M4A→FLAC Conversion: Fixed genre, label, and copyright not being embedded when converting Amazon M4A to FLAC

    • Flutter now extracts extended metadata from Go backend response
    • Passes genre, label, copyright parameters to _embedMetadataAndCover()
    • Tags correctly embedded during FFmpeg conversion
  • Extended Metadata for MP3 Conversion: Genre, label, and copyright now embedded in MP3 files when converting from FLAC

    • Added genre, label, copyright parameters to _embedMetadataToMp3()
    • Tags embedded as ID3v2: GENRE, ORGANIZATION (label), COPYRIGHT

Extensions

  • spotify-web Extension: Updated to v1.7.0
    • Added getMetadataFromDeezer() function to fetch extended metadata:
      • ISRC from track
      • Label from album
      • Copyright (generated as "YEAR LABEL")
      • Genre from album genres
      • Release date
    • enrichTrack() now returns all extended metadata to Go backend
    • Replaced all hardcoded User-Agent strings with utils.randomUserAgent()

Performance

  • Faster App Startup: Notification, Share Intent, and Cover Cache Manager initialization now run in parallel
  • Download Queue Polling: Batched progress updates reduce rebuilds and list allocations during active downloads
  • Queue Item Updates: Status/progress updates now skip no-op changes and update by index for fewer allocations
  • Directory Creation: Download output folders are created once per path, reducing repeated I/O for albums/singles
  • Search Results Rendering: Single-pass filtering avoids repeated indexOf calls for large result sets
  • Queue Lookups in UI: O(1) lookup for queue status in Home/Album/Playlist/Artist track lists
  • History Filtering: Album/single counts and grouping are computed once per build
  • Downloaded Album View: Tracks are grouped by disc in one pass to reduce filtering overhead
  • Track Metadata Screen:
    • Palette extraction deferred until after transition; reduced sample size for smoother navigation
    • File stat uses a single syscall and only triggers state updates on change
    • Static regex/month table avoids repeated allocations
    • Cover precached before opening metadata from history/queue/recents
  • Flutter Provider Optimizations:
    • Cache SharedPreferences instance in DownloadHistoryNotifier and DownloadQueueNotifier to avoid repeated getInstance() calls
    • Precompile regex for folder name sanitization and year extraction (top-level final)
    • Use indexWhere instead of firstWhere with placeholder object to reduce allocations in queue processing
  • Flutter UI Optimizations:
    • Selective ref.watch() for downloadQueueProvider (watch only queuedCount or items instead of entire state)
    • Pass Track directly to _buildTrackTile() instead of index lookup inside builder
    • Pass historyItems as parameter to _buildRecentAccess() to avoid ref.read() inside method
  • M4A Metadata Embedding: Streaming implementation reduces memory usage for large files
    • Uses os.Open() + ReadAt instead of os.ReadFile() (no full file load into memory)
    • Atomic file replacement via temp file + rename for safer writes
    • New helper functions: findAtomInRange(), readAtomHeaderAt(), copyRange()

Backend

  • Deezer ISRC Fetching: Uses ISRCs already present in payloads and caches them, cutting extra API calls
  • SearchAll Allocation: Preallocated slices to reduce allocations during Deezer search
  • HTTP Client Helper: Refactored HTTP client creation to use NewHTTPClientWithTimeout() helper function across lyrics.go, qobuz.go, tidal.go

Technical

  • Go Backend Changes:

    • go_backend/extension_providers.go: Added Label, Copyright, Genre fields to ExtTrackMetadata; added mutex locks to all provider methods
    • go_backend/extension_manager.go: Added VMMu sync.Mutex to LoadedExtension struct
    • go_backend/extension_runtime.go: Added utils.randomUserAgent function
    • go_backend/extension_runtime_utils.go: Added randomUserAgent() implementation
    • go_backend/httputil.go: Updated getRandomUserAgent() to use modern Chrome versions
    • go_backend/tidal.go: Added release date fallback logic
    • go_backend/exports.go: Added Genre, Label, Copyright fields to DownloadResponse
  • Flutter Changes:

    • lib/services/cover_cache_manager.dart: New persistent cache manager for cover images (365 days, 1000 images max)
    • lib/widgets/cached_cover_image.dart: Wrapper widget for CachedNetworkImage with persistent cache
    • lib/main.dart: Added CoverCacheManager.initialize() to app startup
    • lib/screens/*.dart: All 11 screens updated to use persistent cache manager for CachedNetworkImage
    • lib/providers/download_queue_provider.dart: Updated _embedMetadataAndCover() to accept and embed genre, label, copyright; added genre, label, copyright fields to DownloadHistoryItem
    • lib/screens/track_metadata_screen.dart: Display genre, label, copyright in metadata grid
    • lib/l10n/arb/app_en.arb: Added trackGenre, trackLabel, trackCopyright localization strings

Dependencies

  • Added flutter_cache_manager: ^3.4.1 (explicit dependency for persistent cache)
  • Added path: ^1.9.0 (for cache directory path handling)

Downloads

Android

  • arm64: SpotiFLAC-v3.1.3-arm64.apk (recommended for modern devices)
  • arm32: SpotiFLAC-v3.1.3-arm32.apk (older devices)

iOS

  • iOS: SpotiFLAC-v3.1.3-ios-unsigned.ipa (sideload required)

Installation

Android: Enable "Install from unknown sources" and install the APK
iOS: Use AltStore, Sideloadly, or similar tools to sideload the IPA

arm64 arm32 iOS