Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 580e2b6ab8 | |||
| 298b89acf1 | |||
| b6e2675b86 | |||
| 7786501cd1 | |||
| bc4b5a5b17 | |||
| 5d160f71f1 | |||
| 20cf7d49e5 | |||
| 88d22477d5 | |||
| b77def62f4 | |||
| a15313e573 | |||
| 4a90d3f38a | |||
| d4e56567a2 | |||
| 277a7f24fa | |||
| 3735aaf3bd | |||
| 3bbe8553ab | |||
| ca0cfa4524 | |||
| 8b185e964a | |||
| c104a5d8a3 | |||
| 8615cde898 | |||
| 207c0653cc | |||
| de756e5d86 | |||
| fd5db3f7b6 | |||
| d087da9409 | |||
| 43469a7ef2 | |||
| add4af831e | |||
| 4e530ffbc3 | |||
| 14f6776fdc | |||
| da1c6e9171 | |||
| 9c3e934395 | |||
| 15d2c3b465 | |||
| 8aaa6d5cbe | |||
| 9158d0228d | |||
| 2bbcda3320 | |||
| a7622676dd | |||
| 5779f910a2 | |||
| 030f44a444 | |||
| 1248270fb4 | |||
| 413e3b0686 | |||
| ac711efadc | |||
| 59f2fe880a | |||
| 355f2eba2a | |||
| f2f45fa31d | |||
| 042937a8ed | |||
| 674e9af3d0 | |||
| 76d50fab3a | |||
| 81e25d7dab | |||
| 26f26f792a | |||
| 4dfa76b49e | |||
| f511f30ad0 | |||
| a1aa1319ce | |||
| c936bd7dd0 | |||
| 3a60ea2f4e | |||
| 7dba938299 | |||
| 93e77aeb84 | |||
| dd750b95ca | |||
| e42e44f28b | |||
| 67daefdf60 | |||
| fabaf0a3ff | |||
| fb90c73f42 | |||
| c6cf65f075 | |||
| 25de009ebc | |||
| 8918d74bb5 | |||
| f9de8d45d9 | |||
| 48eef0853d | |||
| fc70a912bf | |||
| cd3e5b4b28 | |||
| 482ca82eb4 | |||
| 6d87ae5484 | |||
| bd3e2b999b | |||
| 186196e12b |
@@ -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) | [Monochrome](https://monochrome.tf) |
|
||||
| [qwkuns.me](https://qwkuns.me) | [SpotubeDL](https://spotubedl.com) | [Song.link](https://song.link) | [IDHS](https://github.com/sjdonado/idonthavespotify) | |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:ReservedCodeCacheSize=256m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
"name": "SpotiFLAC",
|
||||
"bundleIdentifier": "com.zarzet.spotiflac",
|
||||
"developerName": "zarzet",
|
||||
"version": "4.3.1",
|
||||
"versionDate": "2026-04-14",
|
||||
"downloadURL": "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/v4.3.1/SpotiFLAC-v4.3.1-ios-unsigned.ipa",
|
||||
"version": "3.9.0",
|
||||
"versionDate": "2026-03-25",
|
||||
"downloadURL": "https://github.com/zarzet/SpotiFLAC-Mobile/releases/download/v3.9.0/SpotiFLAC-v3.9.0-ios-unsigned.ipa",
|
||||
"localizedDescription": "Mobile version of SpotiFLAC written in Flutter. Download Tracks in true FLAC from Tidal, Qobuz, & Amazon Music.",
|
||||
"iconURL": "https://raw.githubusercontent.com/zarzet/SpotiFLAC-Mobile/main/assets/images/logo.png",
|
||||
"size": 34773644
|
||||
"size": 34477323
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 70 KiB |
@@ -6,7 +6,6 @@ files:
|
||||
# Short codes for single-variant languages
|
||||
de: de
|
||||
es: es
|
||||
es-ES: es_ES
|
||||
fr: fr
|
||||
hi: hi
|
||||
id: id
|
||||
@@ -14,11 +13,7 @@ files:
|
||||
ko: ko
|
||||
nl: nl
|
||||
pt: pt
|
||||
pt-PT: pt_PT
|
||||
ru: ru
|
||||
tr: tr
|
||||
uk: uk
|
||||
zh: zh
|
||||
# Full codes for Chinese variants
|
||||
zh-CN: zh_CN
|
||||
zh-TW: zh_TW
|
||||
|
||||
@@ -118,16 +118,9 @@ 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"`
|
||||
}
|
||||
@@ -958,19 +951,6 @@ func isBuiltInDownloadProvider(providerID string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeQualityForBuiltIn(quality string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(quality)) {
|
||||
case "alac", "hi_res_lossless", "lossless":
|
||||
return "HI_RES_LOSSLESS"
|
||||
case "atmos", "ac3", "dolby_atmos":
|
||||
return "LOSSLESS"
|
||||
case "aac", "aac-legacy":
|
||||
return "LOSSLESS"
|
||||
default:
|
||||
return quality
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeBuiltInMetadataTrack(track TrackMetadata, providerID string) ExtTrackMetadata {
|
||||
deezerID := ""
|
||||
tidalID := ""
|
||||
@@ -1339,8 +1319,8 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
|
||||
if req.Source != "" &&
|
||||
!isBuiltInProvider(strings.ToLower(req.Source)) &&
|
||||
selectedProvider == req.Source {
|
||||
GoLog("[DownloadWithExtensionFallback] Track source is extension '%s' matching selected provider, trying it first\n", req.Source)
|
||||
(!strictMode || selectedProvider == "" || strings.EqualFold(selectedProvider, req.Source)) {
|
||||
GoLog("[DownloadWithExtensionFallback] Track source is extension '%s', trying it first\n", req.Source)
|
||||
|
||||
ext, err := extManager.GetExtension(req.Source)
|
||||
if err == nil && ext.Enabled && ext.Error == "" && ext.Manifest.IsDownloadProvider() {
|
||||
@@ -1422,12 +1402,6 @@ 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
|
||||
}
|
||||
@@ -1437,29 +1411,8 @@ 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
|
||||
}
|
||||
@@ -1478,18 +1431,9 @@ 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
|
||||
}
|
||||
@@ -1546,17 +1490,13 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
GoLog("[DownloadWithExtensionFallback] Trying provider: %s\n", providerID)
|
||||
|
||||
if isBuiltInDownloadProvider(providerIDNormalized) {
|
||||
req.OutputExt = ""
|
||||
if (req.Genre == "" || req.Label == "" || req.Copyright == "") &&
|
||||
req.ISRC != "" {
|
||||
GoLog("[DownloadWithExtensionFallback] Enriching extra metadata from ISRC: %s\n", req.ISRC)
|
||||
enrichExtraMetadataByISRC("DownloadWithExtensionFallback", req.ISRC, &req.Genre, &req.Label, &req.Copyright)
|
||||
}
|
||||
|
||||
origQuality := req.Quality
|
||||
req.Quality = normalizeQualityForBuiltIn(req.Quality)
|
||||
result, err := tryBuiltInProvider(providerIDNormalized, req)
|
||||
req.Quality = origQuality
|
||||
if err == nil && result.Success {
|
||||
result.Service = providerIDNormalized
|
||||
if req.Label != "" {
|
||||
@@ -1607,7 +1547,6 @@ func DownloadWithExtensionFallback(req DownloadRequest) (*DownloadResponse, erro
|
||||
continue
|
||||
}
|
||||
|
||||
req.OutputExt = ""
|
||||
outputPath := buildOutputPathForExtension(req, ext)
|
||||
if req.ItemID != "" {
|
||||
StartItemProgress(req.ItemID)
|
||||
@@ -1920,9 +1859,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -185,10 +185,6 @@ 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")
|
||||
@@ -199,9 +195,6 @@ 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)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -137,60 +136,12 @@ func newExtensionRuntime(ext *loadedExtension) *extensionRuntime {
|
||||
storageFlushDelay: defaultStorageFlushDelay,
|
||||
}
|
||||
|
||||
runtime.httpClient = newExtensionHTTPClient(ext, jar, extensionHTTPTimeout(ext, 30*time.Second))
|
||||
runtime.httpClient = newExtensionHTTPClient(ext, jar, 30*time.Second)
|
||||
runtime.downloadClient = newExtensionHTTPClient(ext, jar, DownloadTimeout)
|
||||
|
||||
return runtime
|
||||
}
|
||||
|
||||
func extensionHTTPTimeout(ext *loadedExtension, fallback time.Duration) time.Duration {
|
||||
if ext == nil || ext.Manifest == nil || ext.Manifest.Capabilities == nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
raw, ok := ext.Manifest.Capabilities["networkTimeoutSeconds"]
|
||||
if !ok {
|
||||
return fallback
|
||||
}
|
||||
|
||||
seconds := parseExtensionTimeoutSeconds(raw)
|
||||
if seconds <= 0 {
|
||||
return fallback
|
||||
}
|
||||
|
||||
if seconds < 5 {
|
||||
seconds = 5
|
||||
}
|
||||
if seconds > 300 {
|
||||
seconds = 300
|
||||
}
|
||||
|
||||
return time.Duration(seconds) * time.Second
|
||||
}
|
||||
|
||||
func parseExtensionTimeoutSeconds(raw interface{}) int {
|
||||
switch v := raw.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int32:
|
||||
return int(v)
|
||||
case int64:
|
||||
return int(v)
|
||||
case float32:
|
||||
return int(v)
|
||||
case float64:
|
||||
return int(v)
|
||||
case string:
|
||||
parsed, err := strconv.Atoi(strings.TrimSpace(v))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return parsed
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func (r *extensionRuntime) setActiveDownloadItemID(itemID string) {
|
||||
r.activeDownloadMu.Lock()
|
||||
defer r.activeDownloadMu.Unlock()
|
||||
|
||||
@@ -2655,8 +2655,17 @@ func resolveQobuzTrackForRequest(req DownloadRequest, downloader *QobuzDownloade
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 5: Metadata search with strict matching (duration tolerance: 10 seconds)
|
||||
if track == nil {
|
||||
errMsg := "could not find matching track on Qobuz without identifier match"
|
||||
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)"
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
|
||||
@@ -429,9 +429,11 @@ func TestResolveQobuzTrackForRequestRejectsOdesliMismatch(t *testing.T) {
|
||||
t.Fatal("ISRC fallback should not run without an ISRC")
|
||||
return nil, nil
|
||||
}
|
||||
qobuzSearchTrackByMetadataWithDurationFunc = func(_ *QobuzDownloader, _, _ string, _ int) (*QobuzTrack, error) {
|
||||
t.Fatal("metadata fallback should not run")
|
||||
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
|
||||
}
|
||||
songLinkCheckTrackAvailabilityFunc = func(_ *SongLinkClient, _, _ string) (*TrackAvailability, error) {
|
||||
t.Fatal("SongLink should not run when Odesli QobuzID is provided")
|
||||
@@ -446,11 +448,11 @@ func TestResolveQobuzTrackForRequestRejectsOdesliMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
track, err := resolveQobuzTrackForRequest(req, &QobuzDownloader{}, "Test")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got track %+v", track)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if track != nil {
|
||||
t.Fatalf("expected nil track, got %+v", track)
|
||||
if track == nil || track.ID != 444 || track.Title != "Taste Back" {
|
||||
t.Fatalf("unexpected resolved track: %+v", track)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ 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"`
|
||||
@@ -136,7 +135,6 @@ 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"`
|
||||
@@ -308,29 +306,6 @@ 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 ""
|
||||
@@ -379,7 +354,7 @@ func tidalTrackToTrackMetadata(track *TidalTrack) TrackMetadata {
|
||||
Artists: tidalTrackArtistsDisplay(track),
|
||||
Name: strings.TrimSpace(track.Title),
|
||||
AlbumName: strings.TrimSpace(track.Album.Title),
|
||||
AlbumArtist: tidalTrackAlbumArtistDisplay(track),
|
||||
AlbumArtist: strings.TrimSpace(track.Artist.Name),
|
||||
DurationMS: track.Duration * 1000,
|
||||
Images: tidalImageURL(track.Album.Cover, "1280x1280"),
|
||||
ReleaseDate: strings.TrimSpace(track.Album.ReleaseDate),
|
||||
@@ -402,7 +377,7 @@ func tidalTrackToAlbumTrackMetadata(track *TidalTrack) AlbumTrackMetadata {
|
||||
Artists: tidalTrackArtistsDisplay(track),
|
||||
Name: strings.TrimSpace(track.Title),
|
||||
AlbumName: strings.TrimSpace(track.Album.Title),
|
||||
AlbumArtist: tidalTrackAlbumArtistDisplay(track),
|
||||
AlbumArtist: strings.TrimSpace(track.Artist.Name),
|
||||
DurationMS: track.Duration * 1000,
|
||||
Images: tidalImageURL(track.Album.Cover, "1280x1280"),
|
||||
ReleaseDate: strings.TrimSpace(track.Album.ReleaseDate),
|
||||
@@ -432,7 +407,6 @@ func tidalAlbumToAlbumInfo(album *tidalPublicAlbum) AlbumInfoMetadata {
|
||||
Artists: tidalAlbumArtistsDisplay(album),
|
||||
ArtistId: artistID,
|
||||
Images: tidalImageURL(album.Cover, "1280x1280"),
|
||||
Copyright: strings.TrimSpace(album.Copyright),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,10 +688,6 @@ func findTidalArtistPageModule(page *tidalPublicArtistPage, moduleType string) *
|
||||
|
||||
func (t *TidalDownloader) GetAvailableAPIs() []string {
|
||||
return []string{
|
||||
"https://eu-central.monochrome.tf",
|
||||
"https://us-west.monochrome.tf",
|
||||
"https://api.monochrome.tf",
|
||||
"https://monochrome-api.samidy.com",
|
||||
"https://tidal-api.binimum.org",
|
||||
"https://tidal.kinoplus.online",
|
||||
"https://triton.squid.wtf",
|
||||
@@ -1766,7 +1736,6 @@ type TidalDownloadResult struct {
|
||||
TrackNumber int
|
||||
DiscNumber int
|
||||
ISRC string
|
||||
Copyright string
|
||||
LyricsLRC string // LRC content for embedding in converted files
|
||||
}
|
||||
|
||||
@@ -2080,6 +2049,18 @@ func resolveTidalTrackForRequest(req DownloadRequest, downloader *TidalDownloade
|
||||
}
|
||||
}
|
||||
|
||||
if !gotTidalID && req.ISRC != "" {
|
||||
GoLog("[%s] Trying direct Tidal ISRC search: %s\n", logPrefix, req.ISRC)
|
||||
directTrack, directErr := downloader.SearchTrackByISRC(req.ISRC)
|
||||
if directErr == nil && directTrack != nil && directTrack.ID > 0 {
|
||||
trackID = directTrack.ID
|
||||
gotTidalID = true
|
||||
GoLog("[%s] Got Tidal ID %d from direct ISRC search\n", logPrefix, trackID)
|
||||
} else if directErr != nil {
|
||||
GoLog("[%s] Direct Tidal ISRC search failed: %v\n", logPrefix, directErr)
|
||||
}
|
||||
}
|
||||
|
||||
if !gotTidalID && req.ISRC != "" && req.TrackName != "" && req.ArtistName != "" {
|
||||
GoLog("[%s] Trying Tidal public metadata search with ISRC\n", logPrefix)
|
||||
searchTrack, searchErr := downloader.SearchTrackByMetadataWithISRC(
|
||||
@@ -2375,10 +2356,6 @@ 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,
|
||||
@@ -2394,7 +2371,7 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
|
||||
ISRC: track.ISRC,
|
||||
Genre: req.Genre,
|
||||
Label: req.Label,
|
||||
Copyright: copyright,
|
||||
Copyright: req.Copyright,
|
||||
Composer: req.Composer,
|
||||
}
|
||||
|
||||
@@ -2505,7 +2482,6 @@ func downloadFromTidal(req DownloadRequest) (TidalDownloadResult, error) {
|
||||
TrackNumber: resultTrackNumber,
|
||||
DiscNumber: resultDiscNumber,
|
||||
ISRC: track.ISRC,
|
||||
Copyright: copyright,
|
||||
LyricsLRC: lyricsLRC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
@@ -3,8 +3,8 @@ import 'package:flutter/foundation.dart';
|
||||
/// App version and info constants
|
||||
/// Update version here only - all other files will reference this
|
||||
class AppInfo {
|
||||
static const String version = '4.3.1';
|
||||
static const String buildNumber = '126';
|
||||
static const String version = '4.3.0';
|
||||
static const String buildNumber = '125';
|
||||
static const String fullVersion = '$version+$buildNumber';
|
||||
|
||||
/// Shows "Internal" in debug builds, actual version in release.
|
||||
|
||||
@@ -17,7 +17,6 @@ import 'app_localizations_nl.dart';
|
||||
import 'app_localizations_pt.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
import 'app_localizations_tr.dart';
|
||||
import 'app_localizations_uk.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@@ -120,7 +119,6 @@ abstract class AppLocalizations {
|
||||
Locale('pt', 'PT'),
|
||||
Locale('ru'),
|
||||
Locale('tr'),
|
||||
Locale('uk'),
|
||||
Locale('zh'),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('zh', 'TW'),
|
||||
@@ -5859,7 +5857,6 @@ class _AppLocalizationsDelegate
|
||||
'pt',
|
||||
'ru',
|
||||
'tr',
|
||||
'uk',
|
||||
'zh',
|
||||
].contains(locale.languageCode);
|
||||
|
||||
@@ -5924,8 +5921,6 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
return AppLocalizationsRu();
|
||||
case 'tr':
|
||||
return AppLocalizationsTr();
|
||||
case 'uk':
|
||||
return AppLocalizationsUk();
|
||||
case 'zh':
|
||||
return AppLocalizationsZh();
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get navSettings => 'Einstellungen';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Store';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Startseite';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Unterstützte URL einfügen oder nach Namen suchen';
|
||||
String get homeSubtitle => 'Spotify-Link einfügen oder nach Namen suchen';
|
||||
|
||||
@override
|
||||
String get homeSupports =>
|
||||
@@ -184,21 +184,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Disabled: no loudness normalization tags';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagMode => 'Künstler Tag-Modus';
|
||||
String get optionsArtistTagMode => 'Artist Tag Mode';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagModeDescription =>
|
||||
'Wähle aus, wie mehrere Künstler in eingebetteten Tags geschrieben sind.';
|
||||
'Choose how multiple artists are written into embedded tags.';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagModeJoined => 'Einzelne beigefügte Werte';
|
||||
String get optionsArtistTagModeJoined => 'Single joined value';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagModeJoinedSubtitle =>
|
||||
'Einen Künstler wert wie \"Artist A, Artist B\" für maximale Spieler-Kompatibilität schreiben.';
|
||||
'Write one ARTIST value like \"Artist A, Artist B\" for maximum player compatibility.';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagModeSplitVorbis => 'Tags für FLAC/Opus aufteilen';
|
||||
String get optionsArtistTagModeSplitVorbis => 'Split tags for FLAC/Opus';
|
||||
|
||||
@override
|
||||
String get optionsArtistTagModeSplitVorbisSubtitle =>
|
||||
@@ -220,11 +220,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Parallele Downloads können Ratenlimitierung auslösen';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Erweiterungs-Repo';
|
||||
String get optionsExtensionStore => 'Erweiterungs-Store';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle =>
|
||||
'Repo-Tab in der Navigation anzeigen';
|
||||
'Store-Tab in Navigation anzeigen';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Nach Updates suchen';
|
||||
@@ -303,7 +303,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get extensionsUninstall => 'Deinstallieren';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Erweiterungs-Repo';
|
||||
String get storeTitle => 'Erweiterungs-Store';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Erweiterungen suchen...';
|
||||
@@ -586,7 +586,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get dialogImport => 'Importieren';
|
||||
|
||||
@override
|
||||
String get dialogDownload => 'Herunterladen';
|
||||
String get dialogDownload => 'Download';
|
||||
|
||||
@override
|
||||
String get dialogDiscard => 'Verwerfen';
|
||||
@@ -819,37 +819,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get searchAlbums => 'Alben';
|
||||
|
||||
@override
|
||||
String get searchPlaylists => 'Playlists';
|
||||
String get searchPlaylists => 'Playlisten';
|
||||
|
||||
@override
|
||||
String get searchSortTitle => 'Ergebnisse sortieren';
|
||||
String get searchSortTitle => 'Sort Results';
|
||||
|
||||
@override
|
||||
String get searchSortDefault => 'Standard';
|
||||
String get searchSortDefault => 'Default';
|
||||
|
||||
@override
|
||||
String get searchSortTitleAZ => 'Titel (A-Z)';
|
||||
String get searchSortTitleAZ => 'Title (A-Z)';
|
||||
|
||||
@override
|
||||
String get searchSortTitleZA => 'Titel (Z-A)';
|
||||
String get searchSortTitleZA => 'Title (Z-A)';
|
||||
|
||||
@override
|
||||
String get searchSortArtistAZ => 'Künstler (A-Z)';
|
||||
String get searchSortArtistAZ => 'Artist (A-Z)';
|
||||
|
||||
@override
|
||||
String get searchSortArtistZA => 'Künstler (Z-A)';
|
||||
String get searchSortArtistZA => 'Artist (Z-A)';
|
||||
|
||||
@override
|
||||
String get searchSortDurationShort => 'Dauer (kürzeste)';
|
||||
String get searchSortDurationShort => 'Duration (Shortest)';
|
||||
|
||||
@override
|
||||
String get searchSortDurationLong => 'Dauer (längste)';
|
||||
String get searchSortDurationLong => 'Duration (Longest)';
|
||||
|
||||
@override
|
||||
String get searchSortDateOldest => 'Veröffentlichungsdatum (älteste)';
|
||||
String get searchSortDateOldest => 'Release Date (Oldest)';
|
||||
|
||||
@override
|
||||
String get searchSortDateNewest => 'Veröffentlichungsdatum (Neueste)';
|
||||
String get searchSortDateNewest => 'Release Date (Newest)';
|
||||
|
||||
@override
|
||||
String get tooltipPlay => 'Abspielen';
|
||||
@@ -1315,36 +1315,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get storeClearFilters => 'Filter entfernen';
|
||||
|
||||
@override
|
||||
String get storeAddRepoTitle => 'Erweiterungs-Repository hinzufügen';
|
||||
String get storeAddRepoTitle => 'Add Extension Repository';
|
||||
|
||||
@override
|
||||
String get storeAddRepoDescription =>
|
||||
'Gib eine GitHub Repository-URL ein, die eine Registry.json Datei enthält, um Erweiterungen zu durchsuchen und zu installieren.';
|
||||
'Enter a GitHub repository URL that contains a registry.json file to browse and install extensions.';
|
||||
|
||||
@override
|
||||
String get storeRepoUrlLabel => 'Repository-URL';
|
||||
String get storeRepoUrlLabel => 'Repository URL';
|
||||
|
||||
@override
|
||||
String get storeRepoUrlHint => 'https://github.com/user/repo';
|
||||
|
||||
@override
|
||||
String get storeRepoUrlHelper =>
|
||||
'z.B. https://github.com/user/extensions-repo';
|
||||
'e.g. https://github.com/user/extensions-repo';
|
||||
|
||||
@override
|
||||
String get storeAddRepoButton => 'Repository hinzufügen';
|
||||
String get storeAddRepoButton => 'Add Repository';
|
||||
|
||||
@override
|
||||
String get storeChangeRepoTooltip => 'Repository ändern';
|
||||
String get storeChangeRepoTooltip => 'Change repository';
|
||||
|
||||
@override
|
||||
String get storeRepoDialogTitle => 'Erweiterungs-Repository';
|
||||
String get storeRepoDialogTitle => 'Extension Repository';
|
||||
|
||||
@override
|
||||
String get storeRepoDialogCurrent => 'Aktuelles Repository:';
|
||||
String get storeRepoDialogCurrent => 'Current repository:';
|
||||
|
||||
@override
|
||||
String get storeNewRepoUrlLabel => 'Neue Repository-URL';
|
||||
String get storeNewRepoUrlLabel => 'New Repository URL';
|
||||
|
||||
@override
|
||||
String get storeLoadError => 'Failed to load repository';
|
||||
@@ -1356,7 +1356,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Standard (Deezer)';
|
||||
String get extensionDefaultProvider => 'Standard (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Eingebaute Suche verwenden';
|
||||
@@ -1517,36 +1517,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-Bit / bis 192kHz';
|
||||
|
||||
@override
|
||||
String get downloadLossy320 => 'Verlustbehaftet 320kbps';
|
||||
String get downloadLossy320 => 'Lossy 320kbps';
|
||||
|
||||
@override
|
||||
String get downloadLossyFormat => 'Verlustbehaftetes Format';
|
||||
String get downloadLossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get downloadLossy320Format => 'Lossy 320kbps Format';
|
||||
|
||||
@override
|
||||
String get downloadLossy320FormatDesc =>
|
||||
'Wähle das Ausgabeformat für Tidal 320kbps verlustbehaftete Downloads. Der ursprüngliche AAC Stream wird in das ausgewählte Format konvertiert.';
|
||||
'Choose the output format for Tidal 320kbps lossy downloads. The original AAC stream will be converted to your selected format.';
|
||||
|
||||
@override
|
||||
String get downloadLossyMp3 => 'MP3 320kbps';
|
||||
|
||||
@override
|
||||
String get downloadLossyMp3Subtitle =>
|
||||
'Beste Kompatibilität, ~10MB pro Titel';
|
||||
String get downloadLossyMp3Subtitle => 'Best compatibility, ~10MB per track';
|
||||
|
||||
@override
|
||||
String get downloadLossyOpus256 => 'Opus 256kbps';
|
||||
|
||||
@override
|
||||
String get downloadLossyOpus256Subtitle => 'Beste Qualität, ~8MB pro Titel';
|
||||
String get downloadLossyOpus256Subtitle =>
|
||||
'Best quality Opus, ~8MB per track';
|
||||
|
||||
@override
|
||||
String get downloadLossyOpus128 => 'Opus 128kbps';
|
||||
|
||||
@override
|
||||
String get downloadLossyOpus128Subtitle => 'Kleinste Größe, ~4MB pro Track';
|
||||
String get downloadLossyOpus128Subtitle => 'Smallest size, ~4MB per track';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
@@ -1856,23 +1856,23 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Bei der Suche nach vorhandenen Titeln anzeigen';
|
||||
|
||||
@override
|
||||
String get libraryAutoScan => 'Auto-Scan';
|
||||
String get libraryAutoScan => 'Auto Scan';
|
||||
|
||||
@override
|
||||
String get libraryAutoScanSubtitle =>
|
||||
'Automatically scan your library for new files';
|
||||
|
||||
@override
|
||||
String get libraryAutoScanOff => 'Aus';
|
||||
String get libraryAutoScanOff => 'Off';
|
||||
|
||||
@override
|
||||
String get libraryAutoScanOnOpen => 'Bei jeder App Öffnung';
|
||||
String get libraryAutoScanOnOpen => 'Every app open';
|
||||
|
||||
@override
|
||||
String get libraryAutoScanDaily => 'Täglich';
|
||||
String get libraryAutoScanDaily => 'Daily';
|
||||
|
||||
@override
|
||||
String get libraryAutoScanWeekly => 'Wöchentlich';
|
||||
String get libraryAutoScanWeekly => 'Weekly';
|
||||
|
||||
@override
|
||||
String get libraryActions => 'Aktionen';
|
||||
@@ -1929,8 +1929,8 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count Datein',
|
||||
one: '1 Datei',
|
||||
other: 'files',
|
||||
one: 'file',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -1947,7 +1947,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get libraryScanning => 'Scannen...';
|
||||
|
||||
@override
|
||||
String get libraryScanFinalizing => 'Bibliothek wird aktualisiert...';
|
||||
String get libraryScanFinalizing => 'Finalizing library...';
|
||||
|
||||
@override
|
||||
String libraryScanProgress(String progress, int total) {
|
||||
@@ -2018,23 +2018,22 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get libraryFilterFormat => 'Format';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadata => 'Metadaten';
|
||||
String get libraryFilterMetadata => 'Metadata';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadataComplete => 'Komplette Metadaten';
|
||||
String get libraryFilterMetadataComplete => 'Complete metadata';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadataMissingAny => 'Metadaten fehlen';
|
||||
String get libraryFilterMetadataMissingAny => 'Missing any metadata';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadataMissingYear => 'Jahr fehlt';
|
||||
String get libraryFilterMetadataMissingYear => 'Missing year';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadataMissingGenre => 'Genre fehlt';
|
||||
String get libraryFilterMetadataMissingGenre => 'Missing genre';
|
||||
|
||||
@override
|
||||
String get libraryFilterMetadataMissingAlbumArtist =>
|
||||
'Fehlender Album-Künstler';
|
||||
String get libraryFilterMetadataMissingAlbumArtist => 'Missing album artist';
|
||||
|
||||
@override
|
||||
String get libraryFilterSort => 'Sortieren';
|
||||
@@ -2066,7 +2065,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'vor $count Minuten',
|
||||
one: 'vor 1 Minute',
|
||||
one: 'vor $count Minute',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -2077,7 +2076,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'vor $count Stunden',
|
||||
one: 'vor 1 Stunde',
|
||||
one: 'vor $count Stunde',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -2143,7 +2142,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Im Store Tab findest du nützliche Erweiterungen';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2397,11 +2396,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'FFmpeg Metadaten-Einbettung fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get queueFlacAction => 'Warteschlange FLAC';
|
||||
String get queueFlacAction => 'Queue FLAC';
|
||||
|
||||
@override
|
||||
String queueFlacConfirmMessage(int count) {
|
||||
return 'Suche Online-Matches für ausgewählte Titel und Playlists für FLAC-Downloads.\n\nVorhandene Dateien werden weder geändert noch gelöscht.\n\nNur eindeutige Treffer werden automatisch zur Warteschlange hinzugefügt.\n\n$count ausgewählt';
|
||||
return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2427,8 +2426,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Format konvertieren';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'In MP3 oder Opus konvertieren';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Audio konvertieren';
|
||||
@@ -2456,7 +2454,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Konvertieren von $sourceFormat in $targetFormat? (kein Qualitätsverlust)\n\nDie Originaldatei wird nach der Konvertierung gelöscht.';
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2536,7 +2534,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get collectionLoved => 'Lieblingssongs';
|
||||
|
||||
@override
|
||||
String get collectionPlaylists => 'Playlists';
|
||||
String get collectionPlaylists => 'Playlisten';
|
||||
|
||||
@override
|
||||
String get collectionPlaylist => 'Playlist';
|
||||
@@ -2723,10 +2721,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'Titel',
|
||||
one: 'Titel',
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Konvertiere $count $_temp0 in $format? (kein Qualitätsverlust)\n\nOriginaldateien werden nach der Konvertierung gelöscht.';
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2753,24 +2751,24 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Künstler-Ordner nur für Titel-Künstler';
|
||||
|
||||
@override
|
||||
String get lyricsProvidersTitle => 'Lyrics-Anbieter';
|
||||
String get lyricsProvidersTitle => 'Lyrics Providers';
|
||||
|
||||
@override
|
||||
String get lyricsProvidersDescription =>
|
||||
'Lyrics aktivieren, deaktivieren und neu ordnen. Anbieter werden von oben nach unten ausprobiert, bis Lyrics gefunden werden.';
|
||||
'Enable, disable and reorder lyrics sources. Providers are tried top-to-bottom until lyrics are found.';
|
||||
|
||||
@override
|
||||
String get lyricsProvidersInfoText =>
|
||||
'Erweiterungsanbieter werden immer vor eingebauten ausgeführt. Mindestens ein Anbieter muss aktiviert bleiben.';
|
||||
'Extension lyrics providers always run before built-in providers. At least one provider must remain enabled.';
|
||||
|
||||
@override
|
||||
String lyricsProvidersEnabledSection(int count) {
|
||||
return '($count) aktiviert';
|
||||
return 'Enabled ($count)';
|
||||
}
|
||||
|
||||
@override
|
||||
String lyricsProvidersDisabledSection(int count) {
|
||||
return '($count) deaktiviert';
|
||||
return 'Disabled ($count)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2789,53 +2787,52 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get lyricsProviderNeteaseDesc =>
|
||||
'NetEase Cloud Music (gut für asiatische Lieder)';
|
||||
'NetEase Cloud Music (good for Asian songs)';
|
||||
|
||||
@override
|
||||
String get lyricsProviderMusixmatchDesc =>
|
||||
'Größte Lyrics-Datenbank (mehrsprachig)';
|
||||
'Largest lyrics database (multi-language)';
|
||||
|
||||
@override
|
||||
String get lyricsProviderAppleMusicDesc =>
|
||||
'Wort-für-Wort-synchronisierte Lyrics (via Proxy)';
|
||||
'Word-by-word synced lyrics (via proxy)';
|
||||
|
||||
@override
|
||||
String get lyricsProviderQqMusicDesc =>
|
||||
'QQ Music (gut für chinesische Lieder, via Proxy)';
|
||||
'QQ Music (good for Chinese songs, via proxy)';
|
||||
|
||||
@override
|
||||
String get lyricsProviderExtensionDesc => 'Erweiterungsanbieter';
|
||||
String get lyricsProviderExtensionDesc => 'Extension provider';
|
||||
|
||||
@override
|
||||
String get safMigrationTitle => 'Speicheraktualisierung erforderlich';
|
||||
String get safMigrationTitle => 'Storage Update Required';
|
||||
|
||||
@override
|
||||
String get safMigrationMessage1 =>
|
||||
'SpotiFLAC verwendet jetzt Android Storage Access Framework (SAF) beim Herunterladen. Dies behebt Fehler bei Android 10+.';
|
||||
'SpotiFLAC now uses Android Storage Access Framework (SAF) for downloads. This fixes \"permission denied\" errors on Android 10+.';
|
||||
|
||||
@override
|
||||
String get safMigrationMessage2 =>
|
||||
'Bitte wähle dein Download-Ordner erneut aus, um zum neuen System zu wechseln.';
|
||||
'Please select your download folder again to switch to the new storage system.';
|
||||
|
||||
@override
|
||||
String get safMigrationSuccess => 'Download folder updated to SAF mode';
|
||||
|
||||
@override
|
||||
String get settingsDonate => 'Unterstützen';
|
||||
String get settingsDonate => 'Donate';
|
||||
|
||||
@override
|
||||
String get settingsDonateSubtitle =>
|
||||
'Unterstütze die SpotiFLAC-Mobile Entwickler';
|
||||
String get settingsDonateSubtitle => 'Support SpotiFLAC-Mobile development';
|
||||
|
||||
@override
|
||||
String get tooltipLoveAll => 'Alle lieben';
|
||||
String get tooltipLoveAll => 'Love All';
|
||||
|
||||
@override
|
||||
String get tooltipAddToPlaylist => 'Zur Wiedergabeliste hinzufügen';
|
||||
String get tooltipAddToPlaylist => 'Add to Playlist';
|
||||
|
||||
@override
|
||||
String snackbarRemovedTracksFromLoved(int count) {
|
||||
return '$count Titel von geliebt entfernt';
|
||||
return 'Removed $count tracks from Loved';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2844,7 +2841,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get dialogDownloadAllTitle => 'Alle Herunterladen';
|
||||
String get dialogDownloadAllTitle => 'Download All';
|
||||
|
||||
@override
|
||||
String dialogDownloadAllMessage(int count) {
|
||||
@@ -2855,7 +2852,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get homeSkipAlreadyDownloaded => 'Skip already downloaded songs';
|
||||
|
||||
@override
|
||||
String get homeGoToAlbum => 'Zum Album gehen';
|
||||
String get homeGoToAlbum => 'Go to Album';
|
||||
|
||||
@override
|
||||
String get homeAlbumInfoUnavailable => 'Album info not available';
|
||||
@@ -2874,7 +2871,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String snackbarError(String error) {
|
||||
return 'Fehler: $error';
|
||||
return 'Error: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2894,7 +2891,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get storageModeAppFolderSubtitle => 'Use default Music/SpotiFLAC path';
|
||||
|
||||
@override
|
||||
String get storageModeSaf => 'SAF-Ordner';
|
||||
String get storageModeSaf => 'SAF folder';
|
||||
|
||||
@override
|
||||
String get storageModeSafSubtitle =>
|
||||
@@ -2905,7 +2902,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Customize how your files are named.';
|
||||
|
||||
@override
|
||||
String get downloadFilenameInsertTag => 'Tippe, um Tag einzufügen:';
|
||||
String get downloadFilenameInsertTag => 'Tap to insert tag:';
|
||||
|
||||
@override
|
||||
String get downloadSeparateSinglesEnabled => 'Albums/ and Singles/ folders';
|
||||
@@ -2933,10 +2930,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'By Playlist already places downloads inside a playlist folder.';
|
||||
|
||||
@override
|
||||
String get downloadSongLinkRegion => 'SongLink-Region';
|
||||
String get downloadSongLinkRegion => 'SongLink Region';
|
||||
|
||||
@override
|
||||
String get downloadNetworkCompatibilityMode => 'Netzwerkkompatibilitätsmodus';
|
||||
String get downloadNetworkCompatibilityMode => 'Network compatibility mode';
|
||||
|
||||
@override
|
||||
String get downloadNetworkCompatibilityModeEnabled =>
|
||||
@@ -2979,7 +2976,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Append romanized lyrics when available';
|
||||
|
||||
@override
|
||||
String get downloadNeteaseIncludeRomanizationDisabled => 'Deaktiviert';
|
||||
String get downloadNeteaseIncludeRomanizationDisabled => 'Disabled';
|
||||
|
||||
@override
|
||||
String get downloadAppleQqMultiPerson => 'Apple/QQ Multi-Person Word-by-Word';
|
||||
@@ -3011,10 +3008,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Keep full Album Artist metadata value';
|
||||
|
||||
@override
|
||||
String get downloadProvidersNoneEnabled => 'Keine aktiviert';
|
||||
String get downloadProvidersNoneEnabled => 'None enabled';
|
||||
|
||||
@override
|
||||
String get downloadMusixmatchLanguageCode => 'Sprach-Code';
|
||||
String get downloadMusixmatchLanguageCode => 'Language code';
|
||||
|
||||
@override
|
||||
String get downloadMusixmatchLanguageHint => 'auto / en / es / ja';
|
||||
@@ -3027,7 +3024,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get downloadMusixmatchAuto => 'Auto';
|
||||
|
||||
@override
|
||||
String get downloadNetworkAnySubtitle => 'WLAN + Mobile Daten';
|
||||
String get downloadNetworkAnySubtitle => 'WiFi + Mobile Data';
|
||||
|
||||
@override
|
||||
String get downloadNetworkWifiOnlySubtitle =>
|
||||
@@ -3041,23 +3038,23 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get snackbarUnsupportedAudioFormat => 'Unsupported audio format';
|
||||
|
||||
@override
|
||||
String get cacheRefresh => 'Aktualisieren';
|
||||
String get cacheRefresh => 'Refresh';
|
||||
|
||||
@override
|
||||
String dialogDownloadPlaylistsMessage(int trackCount, int playlistCount) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
trackCount,
|
||||
locale: localeName,
|
||||
other: 'Titel',
|
||||
one: 'Titel',
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
String _temp1 = intl.Intl.pluralLogic(
|
||||
playlistCount,
|
||||
locale: localeName,
|
||||
other: 'Playlists',
|
||||
one: 'Playlist',
|
||||
other: 'playlists',
|
||||
one: 'playlist',
|
||||
);
|
||||
return 'Lade $trackCount $_temp0 von $playlistCount $_temp1?';
|
||||
return 'Download $trackCount $_temp0 from $playlistCount $_temp1?';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -3097,7 +3094,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Abrufen & Ausfüllen';
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
@@ -3122,25 +3119,25 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Titel';
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Künstler';
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Künstler';
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Datum';
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Titel #';
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disk #';
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
@@ -3152,16 +3149,16 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Urheberrecht';
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover-Art';
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'Alle';
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Nur leer';
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
|
||||
@override
|
||||
String queueDownloadingCount(int count) {
|
||||
@@ -3169,10 +3166,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueDownloadedHeader => 'Heruntergeladen';
|
||||
String get queueDownloadedHeader => 'Downloaded';
|
||||
|
||||
@override
|
||||
String get queueFilteringIndicator => 'Filtere...';
|
||||
String get queueFilteringIndicator => 'Filtering...';
|
||||
|
||||
@override
|
||||
String queueTrackCount(int count) {
|
||||
@@ -3197,7 +3194,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueEmptyAlbums => 'Keine Album-Downloads';
|
||||
String get queueEmptyAlbums => 'No album downloads';
|
||||
|
||||
@override
|
||||
String get queueEmptyAlbumsSubtitle =>
|
||||
@@ -3223,7 +3220,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get selectionTapPlaylistsToSelect => 'Tap playlists to select';
|
||||
|
||||
@override
|
||||
String get selectionSelectPlaylistsToDelete => 'Playlist zum Löschen wählen';
|
||||
String get selectionSelectPlaylistsToDelete => 'Select playlists to delete';
|
||||
|
||||
@override
|
||||
String get audioAnalysisTitle => 'Audio Quality Analysis';
|
||||
@@ -3233,37 +3230,37 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
'Verify lossless quality with spectrum analysis';
|
||||
|
||||
@override
|
||||
String get audioAnalysisAnalyzing => 'Audio wird analysiert...';
|
||||
String get audioAnalysisAnalyzing => 'Analyzing audio...';
|
||||
|
||||
@override
|
||||
String get audioAnalysisSampleRate => 'Sample Rate';
|
||||
|
||||
@override
|
||||
String get audioAnalysisBitDepth => 'Bit-Tiefe';
|
||||
String get audioAnalysisBitDepth => 'Bit Depth';
|
||||
|
||||
@override
|
||||
String get audioAnalysisChannels => 'Kanäle';
|
||||
String get audioAnalysisChannels => 'Channels';
|
||||
|
||||
@override
|
||||
String get audioAnalysisDuration => 'Länge';
|
||||
String get audioAnalysisDuration => 'Duration';
|
||||
|
||||
@override
|
||||
String get audioAnalysisNyquist => 'Nyquist';
|
||||
|
||||
@override
|
||||
String get audioAnalysisFileSize => 'Größe';
|
||||
String get audioAnalysisFileSize => 'Size';
|
||||
|
||||
@override
|
||||
String get audioAnalysisDynamicRange => 'Dynamischer Bereich';
|
||||
String get audioAnalysisDynamicRange => 'Dynamic Range';
|
||||
|
||||
@override
|
||||
String get audioAnalysisPeak => 'Maximum';
|
||||
String get audioAnalysisPeak => 'Peak';
|
||||
|
||||
@override
|
||||
String get audioAnalysisRms => 'RMS';
|
||||
|
||||
@override
|
||||
String get audioAnalysisSamples => 'Proben';
|
||||
String get audioAnalysisSamples => 'Samples';
|
||||
|
||||
@override
|
||||
String extensionsSearchWith(String providerName) {
|
||||
@@ -3271,7 +3268,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get extensionsHomeFeedProvider => 'Home Feed Anbieter';
|
||||
String get extensionsHomeFeedProvider => 'Home Feed Provider';
|
||||
|
||||
@override
|
||||
String get extensionsHomeFeedDescription =>
|
||||
@@ -3299,7 +3296,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get sortAlphaDesc => 'Z-A';
|
||||
|
||||
@override
|
||||
String get cancelDownloadTitle => 'Download abbrechen?';
|
||||
String get cancelDownloadTitle => 'Cancel download?';
|
||||
|
||||
@override
|
||||
String cancelDownloadContent(String trackName) {
|
||||
@@ -3307,7 +3304,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get cancelDownloadKeep => 'Behalten';
|
||||
String get cancelDownloadKeep => 'Keep';
|
||||
|
||||
@override
|
||||
String get metadataSaveFailedFfmpeg => 'Failed to save metadata via FFmpeg';
|
||||
@@ -3322,22 +3319,22 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get errorLoadAlbum => 'Fehler beim Laden des Albums';
|
||||
String get errorLoadAlbum => 'Failed to load album';
|
||||
|
||||
@override
|
||||
String get errorLoadPlaylist => 'Fehler beim Laden der Playlist';
|
||||
String get errorLoadPlaylist => 'Failed to load playlist';
|
||||
|
||||
@override
|
||||
String get errorLoadArtist => 'Fehler beim Laden des Interpreten';
|
||||
String get errorLoadArtist => 'Failed to load artist';
|
||||
|
||||
@override
|
||||
String get notifChannelDownloadName => 'Download Fortschritt';
|
||||
String get notifChannelDownloadName => 'Download Progress';
|
||||
|
||||
@override
|
||||
String get notifChannelDownloadDesc => 'Shows download progress for tracks';
|
||||
|
||||
@override
|
||||
String get notifChannelLibraryScanName => 'Bibliotheksscan';
|
||||
String get notifChannelLibraryScanName => 'Library Scan';
|
||||
|
||||
@override
|
||||
String get notifChannelLibraryScanDesc => 'Shows local library scan progress';
|
||||
@@ -3361,7 +3358,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get notifAlreadyInLibrary => 'Bereits in der Bibliothek';
|
||||
String get notifAlreadyInLibrary => 'Already in Library';
|
||||
|
||||
@override
|
||||
String notifDownloadCompleteCount(int completed, int total) {
|
||||
@@ -3369,7 +3366,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get notifDownloadComplete => 'Download abgeschlossen';
|
||||
String get notifDownloadComplete => 'Download Complete';
|
||||
|
||||
@override
|
||||
String notifDownloadsFinished(int completed, int failed) {
|
||||
@@ -3411,12 +3408,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String notifLibraryScanExcluded(int count) {
|
||||
return '$count ausgeschlossen';
|
||||
return '$count excluded';
|
||||
}
|
||||
|
||||
@override
|
||||
String notifLibraryScanErrors(int count) {
|
||||
return '$count Fehler';
|
||||
return '$count errors';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -3439,7 +3436,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get notifUpdateReady => 'Update bereit';
|
||||
String get notifUpdateReady => 'Update Ready';
|
||||
|
||||
@override
|
||||
String notifUpdateReadyBody(String version) {
|
||||
@@ -3447,7 +3444,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get notifUpdateFailed => 'Update fehlgeschlagen';
|
||||
String get notifUpdateFailed => 'Update Failed';
|
||||
|
||||
@override
|
||||
String get notifUpdateFailedBody =>
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get navSettings => 'Paramètres';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Magasin';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Accueil';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Coller un lien Spotify ou rechercher par nom';
|
||||
|
||||
@override
|
||||
String get homeSupports => 'Supports: Piste, Album, Playlist, Artiste URLs';
|
||||
@@ -218,10 +218,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Parallel downloads may trigger rate limiting';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => 'Extension Store';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Check for Updates';
|
||||
@@ -282,7 +282,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get extensionsTitle => 'Extensions';
|
||||
|
||||
@override
|
||||
String get extensionsDisabled => 'Désactivée';
|
||||
String get extensionsDisabled => 'Disabled';
|
||||
|
||||
@override
|
||||
String extensionsVersion(String version) {
|
||||
@@ -291,38 +291,38 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String extensionsAuthor(String author) {
|
||||
return 'par $author';
|
||||
return 'by $author';
|
||||
}
|
||||
|
||||
@override
|
||||
String get extensionsUninstall => 'Désinstaller';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => 'Magasin d\'extension';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Recherche d\'extensions...';
|
||||
|
||||
@override
|
||||
String get storeInstall => 'Installer';
|
||||
String get storeInstall => 'Install';
|
||||
|
||||
@override
|
||||
String get storeInstalled => 'Installé';
|
||||
String get storeInstalled => 'Installed';
|
||||
|
||||
@override
|
||||
String get storeUpdate => 'Mettre à jour';
|
||||
String get storeUpdate => 'Update';
|
||||
|
||||
@override
|
||||
String get aboutTitle => 'À propos de';
|
||||
String get aboutTitle => 'About';
|
||||
|
||||
@override
|
||||
String get aboutContributors => 'Contributeurs';
|
||||
String get aboutContributors => 'Contributors';
|
||||
|
||||
@override
|
||||
String get aboutMobileDeveloper => 'Développeur de la version mobile';
|
||||
String get aboutMobileDeveloper => 'Mobile version developer';
|
||||
|
||||
@override
|
||||
String get aboutOriginalCreator => 'Créateur de SpotiFLAC original';
|
||||
String get aboutOriginalCreator => 'Creator of the original SpotiFLAC';
|
||||
|
||||
@override
|
||||
String get aboutLogoArtist =>
|
||||
@@ -362,7 +362,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get aboutTelegramChannel => 'Telegram Channel';
|
||||
|
||||
@override
|
||||
String get aboutTelegramChannelSubtitle => 'Annonces et mises à jour';
|
||||
String get aboutTelegramChannelSubtitle => 'Announcements and updates';
|
||||
|
||||
@override
|
||||
String get aboutTelegramChat => 'Telegram Community';
|
||||
@@ -520,11 +520,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'SpotiFLAC needs storage permission to save your downloaded music files.';
|
||||
|
||||
@override
|
||||
String get setupNotificationGranted =>
|
||||
'Autorisation de notifications accordée!';
|
||||
String get setupNotificationGranted => 'Notification Permission Granted!';
|
||||
|
||||
@override
|
||||
String get setupNotificationEnable => 'Activer les notifications';
|
||||
String get setupNotificationEnable => 'Enable Notifications';
|
||||
|
||||
@override
|
||||
String get setupFolderChoose => 'Choisissez le dossier pour télécharger';
|
||||
@@ -534,39 +533,39 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Sélectionnez un dossier dans lequel votre musique téléchargée sera enregistrée.';
|
||||
|
||||
@override
|
||||
String get setupSelectFolder => 'Sélectionner un dossier';
|
||||
String get setupSelectFolder => 'Select Folder';
|
||||
|
||||
@override
|
||||
String get setupEnableNotifications => 'Activer les notifications';
|
||||
String get setupEnableNotifications => 'Enable Notifications';
|
||||
|
||||
@override
|
||||
String get setupNotificationBackgroundDescription =>
|
||||
'Get notified about download progress and completion. This helps you track downloads when the app is in background.';
|
||||
|
||||
@override
|
||||
String get setupSkipForNow => 'Ignorer pour le moment';
|
||||
String get setupSkipForNow => 'Skip for now';
|
||||
|
||||
@override
|
||||
String get setupNext => 'Suivant';
|
||||
String get setupNext => 'Next';
|
||||
|
||||
@override
|
||||
String get setupGetStarted => 'Démarrer';
|
||||
String get setupGetStarted => 'Get Started';
|
||||
|
||||
@override
|
||||
String get setupAllowAccessToManageFiles =>
|
||||
'Veuillez activer \"Autoriser l\'accès à tous les fichiers\" sur l\'écran suivant.';
|
||||
'Please enable \"Allow access to manage all files\" in the next screen.';
|
||||
|
||||
@override
|
||||
String get dialogCancel => 'Annuler';
|
||||
String get dialogCancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get dialogSave => 'Sauvegarder';
|
||||
String get dialogSave => 'Save';
|
||||
|
||||
@override
|
||||
String get dialogDelete => 'Supprimer';
|
||||
String get dialogDelete => 'Delete';
|
||||
|
||||
@override
|
||||
String get dialogRetry => 'Réessayer';
|
||||
String get dialogRetry => 'Retry';
|
||||
|
||||
@override
|
||||
String get dialogClear => 'Clear';
|
||||
@@ -578,7 +577,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get dialogImport => 'Import';
|
||||
|
||||
@override
|
||||
String get dialogDownload => 'Télécharger';
|
||||
String get dialogDownload => 'Download';
|
||||
|
||||
@override
|
||||
String get dialogDiscard => 'Discard';
|
||||
@@ -587,10 +586,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get dialogRemove => 'Remove';
|
||||
|
||||
@override
|
||||
String get dialogUninstall => 'Désinstaller';
|
||||
String get dialogUninstall => 'Uninstall';
|
||||
|
||||
@override
|
||||
String get dialogDiscardChanges => 'Ignorer les modifications ?';
|
||||
String get dialogDiscardChanges => 'Discard Changes?';
|
||||
|
||||
@override
|
||||
String get dialogUnsavedChanges =>
|
||||
@@ -1339,7 +1338,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'Default (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Use built-in search';
|
||||
@@ -2117,7 +2116,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Browse the Store tab to discover useful extensions';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2398,8 +2397,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Convert Format';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Convert Audio';
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String get navSettings => 'विकल्प';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Store';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Home';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Paste a Spotify link or search by name';
|
||||
|
||||
@override
|
||||
String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs';
|
||||
@@ -216,10 +216,10 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
'Parallel downloads may trigger rate limiting';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => 'Extension Store';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Check for Updates';
|
||||
@@ -296,7 +296,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String get extensionsUninstall => 'Uninstall';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => 'Extension Store';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Search extensions...';
|
||||
@@ -1336,7 +1336,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'Default (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Use built-in search';
|
||||
@@ -2114,7 +2114,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Browse the Store tab to discover useful extensions';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2395,8 +2395,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Convert Format';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Convert Audio';
|
||||
|
||||
@@ -27,7 +27,8 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get homeTitle => 'Beranda';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle =>
|
||||
'Tempel URL yang didukung atau cari berdasarkan nama';
|
||||
|
||||
@override
|
||||
String get homeSupports => 'Mendukung: URL Track, Album, Playlist, Artis';
|
||||
@@ -129,11 +130,11 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get optionsDefaultSearchTab => 'Default Search Tab';
|
||||
String get optionsDefaultSearchTab => 'Tab Pencarian Default';
|
||||
|
||||
@override
|
||||
String get optionsDefaultSearchTabSubtitle =>
|
||||
'Choose which tab opens first for new search results.';
|
||||
'Pilih tab yang dibuka lebih dulu untuk hasil pencarian baru.';
|
||||
|
||||
@override
|
||||
String get optionsSwitchBack =>
|
||||
@@ -219,10 +220,10 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
'Unduhan paralel dapat memicu pembatasan rate';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => 'Repo Ekstensi';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => 'Tampilkan tab Repo di navigasi';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Periksa Pembaruan';
|
||||
@@ -298,7 +299,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get extensionsUninstall => 'Copot';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => 'Repo Ekstensi';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Cari ekstensi...';
|
||||
@@ -744,15 +745,15 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get errorNoTracksFound => 'Tidak ada lagu ditemukan';
|
||||
|
||||
@override
|
||||
String get errorUrlNotRecognized => 'Tautan tidak dikenali';
|
||||
String get errorUrlNotRecognized => 'Link tidak dikenali';
|
||||
|
||||
@override
|
||||
String get errorUrlNotRecognizedMessage =>
|
||||
'Tautan ini tidak didukung. Pastikan URL sudah benar dan ekstensi yang kompatibel telah terpasang.';
|
||||
'Link ini tidak didukung. Pastikan URL benar dan ekstensi yang kompatibel sudah terpasang.';
|
||||
|
||||
@override
|
||||
String get errorUrlFetchFailed =>
|
||||
'Konten dari tautan ini gagal dimuat. Silakan coba lagi.';
|
||||
'Gagal memuat konten dari link ini. Silakan coba lagi.';
|
||||
|
||||
@override
|
||||
String errorMissingExtensionSource(String item) {
|
||||
@@ -940,15 +941,15 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
'Jika lagu tidak tersedia di provider pertama, aplikasi akan otomatis mencoba yang berikutnya.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsTitle => 'Extension Fallback';
|
||||
String get providerPriorityFallbackExtensionsTitle => 'Fallback Ekstensi';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsDescription =>
|
||||
'Choose which installed download extensions can be used during automatic fallback. Built-in providers still follow the priority order above.';
|
||||
'Pilih ekstensi unduhan terpasang mana yang boleh dipakai saat fallback otomatis. Provider bawaan tetap mengikuti urutan prioritas di atas.';
|
||||
|
||||
@override
|
||||
String get providerPriorityFallbackExtensionsHint =>
|
||||
'Only enabled extensions with download-provider capability are listed here.';
|
||||
'Hanya ekstensi aktif dengan kemampuan download provider yang ditampilkan di sini.';
|
||||
|
||||
@override
|
||||
String get providerBuiltIn => 'Bawaan';
|
||||
@@ -1333,7 +1334,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get storeNewRepoUrlLabel => 'New Repository URL';
|
||||
|
||||
@override
|
||||
String get storeLoadError => 'Failed to load repository';
|
||||
String get storeLoadError => 'Gagal memuat repo';
|
||||
|
||||
@override
|
||||
String get storeEmptyNoExtensions => 'No extensions available';
|
||||
@@ -1342,7 +1343,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'Bawaan (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Gunakan pencarian bawaan';
|
||||
@@ -1448,7 +1449,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get extensionsFallbackSubtitle =>
|
||||
'Choose which installed download extensions can be used as fallback';
|
||||
'Pilih ekstensi unduhan terpasang yang boleh dipakai saat fallback';
|
||||
|
||||
@override
|
||||
String get extensionsNoDownloadProvider =>
|
||||
@@ -2123,7 +2124,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Buka tab Repo untuk menemukan ekstensi yang berguna';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2374,25 +2375,25 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get trackReEnrichFfmpegFailed => 'FFmpeg metadata embed failed';
|
||||
|
||||
@override
|
||||
String get queueFlacAction => 'Queue FLAC';
|
||||
String get queueFlacAction => 'Antrekan FLAC';
|
||||
|
||||
@override
|
||||
String queueFlacConfirmMessage(int count) {
|
||||
return 'Search online matches for the selected tracks and queue FLAC downloads.\n\nExisting files will not be modified or deleted.\n\nOnly high-confidence matches are queued automatically.\n\n$count selected';
|
||||
return 'Cari kecocokan online untuk track yang dipilih lalu antrekan download FLAC.\n\nFile yang sudah ada tidak akan diubah atau dihapus.\n\nHanya kecocokan dengan keyakinan tinggi yang akan diantrikan otomatis.\n\n$count dipilih';
|
||||
}
|
||||
|
||||
@override
|
||||
String queueFlacFindingProgress(int current, int total) {
|
||||
return 'Finding FLAC matches... ($current/$total)';
|
||||
return 'Mencari kecocokan FLAC... ($current/$total)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get queueFlacNoReliableMatches =>
|
||||
'No reliable online matches found for the selection';
|
||||
'Tidak ada kecocokan online yang cukup meyakinkan untuk pilihan ini';
|
||||
|
||||
@override
|
||||
String queueFlacQueuedWithSkipped(int addedCount, int skippedCount) {
|
||||
return 'Added $addedCount tracks to queue, skipped $skippedCount';
|
||||
return 'Menambahkan $addedCount track ke antrean, melewati $skippedCount';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2405,7 +2406,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
'Konversi ke MP3, Opus, ALAC, atau FLAC';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Convert Audio';
|
||||
@@ -2433,12 +2434,12 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
return 'Konversi dari $sourceFormat ke $targetFormat? (Lossless — tanpa kehilangan kualitas)\n\nFile asli akan dihapus setelah konversi.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
'Konversi lossless — tanpa kehilangan kualitas';
|
||||
|
||||
@override
|
||||
String get trackConvertConverting => 'Converting audio...';
|
||||
@@ -2892,19 +2893,19 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get downloadCreatePlaylistSourceFolder =>
|
||||
'Create playlist source folder';
|
||||
'Buat folder sumber playlist';
|
||||
|
||||
@override
|
||||
String get downloadCreatePlaylistSourceFolderEnabled =>
|
||||
'Playlist downloads use Playlist/ plus your normal folder structure.';
|
||||
'Unduhan dari playlist memakai Playlist/ lalu struktur folder normal Anda.';
|
||||
|
||||
@override
|
||||
String get downloadCreatePlaylistSourceFolderDisabled =>
|
||||
'Playlist downloads use the normal folder structure only.';
|
||||
'Unduhan dari playlist hanya memakai struktur folder normal.';
|
||||
|
||||
@override
|
||||
String get downloadCreatePlaylistSourceFolderRedundant =>
|
||||
'By Playlist already places downloads inside a playlist folder.';
|
||||
'Mode Berdasarkan Playlist sudah menaruh unduhan ke dalam folder playlist.';
|
||||
|
||||
@override
|
||||
String get downloadSongLinkRegion => 'SongLink Region';
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get navSettings => '設定';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'ストア';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'ホーム';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Spotify のリンクを貼り付けるか、名前で検索します';
|
||||
|
||||
@override
|
||||
String get homeSupports => 'サポート: トラック、アルバム、プレイリスト、アーティスト、URL';
|
||||
@@ -214,10 +214,10 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
'Parallel downloads may trigger rate limiting';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => '拡張ストア';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => 'ナビゲーションにストアタブを表示';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => '更新を確認';
|
||||
@@ -293,7 +293,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get extensionsUninstall => 'アンインストール';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => '拡張ストア';
|
||||
|
||||
@override
|
||||
String get storeSearch => '拡張を検索...';
|
||||
@@ -1330,7 +1330,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'デフォルト (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => '内蔵の検索を使用する';
|
||||
@@ -2101,7 +2101,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Browse the Store tab to discover useful extensions';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2382,8 +2382,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get trackConvertFormat => '変換の形式';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'MP3 または Opus に変換';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'オーディオを変換';
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get navSettings => 'Settings';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Store';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Home';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Spotify URL을 붙여 넣거나 검색';
|
||||
|
||||
@override
|
||||
String get homeSupports => '지원 항목: 트랙, 앨범, 플레이리스트, 아티스트 URLs';
|
||||
@@ -209,10 +209,10 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get optionsConcurrentWarning => '동시에 다수의 음반을 다운로드하면 속도 제한이 발생할 수 있습니다';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => '확장 기능 스토어';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => '탐색 메뉴에 스토어 탭 표시';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => '업데이트 확인';
|
||||
@@ -286,7 +286,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get extensionsUninstall => '삭제';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => '확장 기능 스토어';
|
||||
|
||||
@override
|
||||
String get storeSearch => '확장 기능 검색';
|
||||
@@ -1316,7 +1316,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'Default (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Use built-in search';
|
||||
@@ -2094,7 +2094,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Browse the Store tab to discover useful extensions';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2375,8 +2375,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Convert Format';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Convert Audio';
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get navSettings => 'Settings';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Store';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Home';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Paste a Spotify link or search by name';
|
||||
|
||||
@override
|
||||
String get homeSupports => 'Supports: Track, Album, Playlist, Artist URLs';
|
||||
@@ -216,10 +216,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
'Parallel downloaden kan leiden tot rate-limiting';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => 'Extension Store';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle => 'Show Store tab in navigation';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Check for Updates';
|
||||
@@ -296,7 +296,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get extensionsUninstall => 'Uninstall';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => 'Extension Store';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Search extensions...';
|
||||
@@ -1336,7 +1336,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'Default (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle => 'Use built-in search';
|
||||
@@ -2114,7 +2114,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Browse the Store tab to discover useful extensions';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2395,8 +2395,7 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Convert Format';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'Convert to MP3 or Opus';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Convert Audio';
|
||||
|
||||
@@ -21,13 +21,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get navSettings => 'Настройки';
|
||||
|
||||
@override
|
||||
String get navStore => 'Repo';
|
||||
String get navStore => 'Магазин';
|
||||
|
||||
@override
|
||||
String get homeTitle => 'Главная';
|
||||
|
||||
@override
|
||||
String get homeSubtitle => 'Paste a supported URL or search by name';
|
||||
String get homeSubtitle => 'Вставьте ссылку Spotify или ищите по названию';
|
||||
|
||||
@override
|
||||
String get homeSupports =>
|
||||
@@ -221,10 +221,11 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
'Параллельные загрузки могут вызвать ограничение скорости';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStore => 'Extension Repo';
|
||||
String get optionsExtensionStore => 'Магазин расширений';
|
||||
|
||||
@override
|
||||
String get optionsExtensionStoreSubtitle => 'Show Repo tab in navigation';
|
||||
String get optionsExtensionStoreSubtitle =>
|
||||
'Показывать вкладку Магазин в гл. меню';
|
||||
|
||||
@override
|
||||
String get optionsCheckUpdates => 'Проверить обновления';
|
||||
@@ -301,7 +302,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get extensionsUninstall => 'Удалить';
|
||||
|
||||
@override
|
||||
String get storeTitle => 'Extension Repo';
|
||||
String get storeTitle => 'Магазин расширений';
|
||||
|
||||
@override
|
||||
String get storeSearch => 'Поиск расширений...';
|
||||
@@ -635,9 +636,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Удалить $count $_temp0 из истории?\n\nЭто также удалит файлы из хранилища.';
|
||||
}
|
||||
@@ -690,9 +691,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Удалено $count $_temp0';
|
||||
}
|
||||
@@ -1160,9 +1161,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count треков',
|
||||
one: '1 трек',
|
||||
many: '$count треков',
|
||||
few: '$count трека',
|
||||
one: '$count трек',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -1356,7 +1357,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get storeEmptyNoResults => 'No extensions found';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProvider => 'Default (Deezer)';
|
||||
String get extensionDefaultProvider => 'По умолчанию (Deezer/Spotify)';
|
||||
|
||||
@override
|
||||
String get extensionDefaultProviderSubtitle =>
|
||||
@@ -1669,9 +1670,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Удалить $count $_temp0 из этого альбома?\n\nЭто также удалит файлы из хранилища.';
|
||||
}
|
||||
@@ -1693,9 +1694,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Удалить $count $_temp0';
|
||||
}
|
||||
@@ -1926,9 +1927,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -2082,9 +2083,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count минут',
|
||||
one: '1 минуту',
|
||||
many: '$count минут',
|
||||
few: '$count минуты',
|
||||
one: '$count минуту',
|
||||
);
|
||||
return '$_temp0 назад';
|
||||
}
|
||||
@@ -2095,9 +2096,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count часов',
|
||||
one: '1 час',
|
||||
many: '$count часов',
|
||||
few: '$count часа',
|
||||
one: '$count час',
|
||||
);
|
||||
return '$_temp0 назад';
|
||||
}
|
||||
@@ -2163,7 +2164,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip1 =>
|
||||
'Browse the Repo tab to discover useful extensions';
|
||||
'Просмотрите вкладку Магазина, чтобы найти полезные расширения';
|
||||
|
||||
@override
|
||||
String get tutorialExtensionsTip2 =>
|
||||
@@ -2447,8 +2448,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get trackConvertFormat => 'Переконвертировать формат';
|
||||
|
||||
@override
|
||||
String get trackConvertFormatSubtitle =>
|
||||
'Convert to MP3, Opus, ALAC, or FLAC';
|
||||
String get trackConvertFormatSubtitle => 'Конвертировать в MP3 или Opus';
|
||||
|
||||
@override
|
||||
String get trackConvertTitle => 'Конвертировать аудио';
|
||||
@@ -2579,9 +2579,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: '$count треков',
|
||||
one: '1 трек',
|
||||
many: '$count треков',
|
||||
few: '$count трека',
|
||||
one: '$count трек',
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
@@ -2698,9 +2698,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Отправить $count $_temp0';
|
||||
}
|
||||
@@ -2715,9 +2715,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'треков',
|
||||
one: 'трек',
|
||||
many: 'треков',
|
||||
few: 'трека',
|
||||
one: 'трек',
|
||||
);
|
||||
return 'Конвертировать $count $_temp0';
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ const List<Locale> filteredSupportedLocales = <Locale>[
|
||||
Locale('pt', 'PT'),
|
||||
Locale('ja'),
|
||||
Locale('tr'),
|
||||
Locale('uk'),
|
||||
];
|
||||
|
||||
/// Set of locale codes for quick lookup.
|
||||
@@ -32,5 +31,4 @@ const Set<String> filteredLocaleCodes = <String>{
|
||||
'pt_PT',
|
||||
'ja',
|
||||
'tr',
|
||||
'uk',
|
||||
};
|
||||
|
||||
@@ -574,7 +574,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
if (trimmed.startsWith('content://')) return true;
|
||||
return trimmed.endsWith('.flac') ||
|
||||
trimmed.endsWith('.m4a') ||
|
||||
trimmed.endsWith('.mp4') ||
|
||||
trimmed.endsWith('.aac') ||
|
||||
trimmed.endsWith('.mp3') ||
|
||||
trimmed.endsWith('.opus') ||
|
||||
@@ -596,7 +595,6 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
|
||||
!hasResolvedSpecs &&
|
||||
(trimmedPath.endsWith('.flac') ||
|
||||
trimmedPath.endsWith('.m4a') ||
|
||||
trimmedPath.endsWith('.mp4') ||
|
||||
trimmedPath.endsWith('.aac') ||
|
||||
trimmedPath.startsWith('content://'));
|
||||
|
||||
@@ -2361,9 +2359,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
final normalized = preferred.startsWith('.')
|
||||
? preferred.toLowerCase()
|
||||
: '.${preferred.toLowerCase()}';
|
||||
if (normalized == '.mp4') {
|
||||
return '.m4a';
|
||||
}
|
||||
const allowed = <String>{'.flac', '.m4a', '.mp3', '.opus'};
|
||||
if (allowed.contains(normalized)) {
|
||||
return normalized;
|
||||
@@ -2374,21 +2369,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _extensionPreservesNativeOutputExt(String service, String ext) {
|
||||
final normalizedService = service.trim().toLowerCase();
|
||||
final normalizedExt = ext.trim().toLowerCase();
|
||||
if (normalizedService.isEmpty || normalizedExt.isEmpty) return false;
|
||||
|
||||
final extensionState = ref.read(extensionProvider);
|
||||
return extensionState.extensions.any(
|
||||
(ext) =>
|
||||
ext.enabled &&
|
||||
ext.hasDownloadProvider &&
|
||||
ext.id.toLowerCase() == normalizedService &&
|
||||
ext.preservedNativeOutputExtensions.contains(normalizedExt),
|
||||
);
|
||||
}
|
||||
|
||||
String _determineOutputExt(String quality, String service) {
|
||||
final extensionPreferred = _extensionPreferredOutputExt(service);
|
||||
if (extensionPreferred != null) {
|
||||
@@ -2407,7 +2387,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
String _mimeTypeForExt(String ext) {
|
||||
switch (ext.toLowerCase()) {
|
||||
case '.m4a':
|
||||
case '.mp4':
|
||||
return 'audio/mp4';
|
||||
case '.mp3':
|
||||
return 'audio/mpeg';
|
||||
@@ -3710,8 +3689,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
) {
|
||||
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?,
|
||||
);
|
||||
@@ -3739,9 +3716,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
backendIsrc != null ||
|
||||
backendCoverUrl != null ||
|
||||
backendAlbumArtist != null ||
|
||||
backendComposer != null ||
|
||||
backendTotalTracks != null ||
|
||||
backendTotalDiscs != null;
|
||||
backendComposer != null;
|
||||
|
||||
if (!hasOverrides) {
|
||||
return baseTrack;
|
||||
@@ -3760,12 +3735,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
isrc: backendIsrc ?? baseTrack.isrc,
|
||||
trackNumber: backendTrackNum ?? baseTrack.trackNumber,
|
||||
discNumber: backendDiscNum ?? baseTrack.discNumber,
|
||||
totalDiscs: backendTotalDiscs ?? baseTrack.totalDiscs,
|
||||
totalDiscs: baseTrack.totalDiscs,
|
||||
releaseDate: backendYear ?? baseTrack.releaseDate,
|
||||
deezerId: baseTrack.deezerId,
|
||||
availability: baseTrack.availability,
|
||||
albumType: baseTrack.albumType,
|
||||
totalTracks: backendTotalTracks ?? baseTrack.totalTracks,
|
||||
totalTracks: baseTrack.totalTracks,
|
||||
composer: backendComposer ?? baseTrack.composer,
|
||||
source: baseTrack.source,
|
||||
);
|
||||
@@ -4898,10 +4873,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
final actualService =
|
||||
((result['service'] as String?)?.toLowerCase()) ??
|
||||
item.service.toLowerCase();
|
||||
final preferredOutputExt = _extensionPreferredOutputExt(actualService);
|
||||
final shouldPreserveNativeM4a =
|
||||
preferredOutputExt == '.m4a' ||
|
||||
_extensionPreservesNativeOutputExt(actualService, '.m4a');
|
||||
final decryptionDescriptor =
|
||||
DownloadDecryptionDescriptor.fromDownloadResult(result);
|
||||
trackToDownload = _buildTrackForMetadataEmbedding(
|
||||
@@ -5027,7 +4998,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
final isM4aFile =
|
||||
filePath != null &&
|
||||
(filePath.endsWith('.m4a') ||
|
||||
filePath.endsWith('.mp4') ||
|
||||
(mimeType != null && mimeType.contains('mp4')));
|
||||
final isFlacFile =
|
||||
filePath != null &&
|
||||
@@ -5043,7 +5013,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
|
||||
if (shouldForceTidalSafM4aHandling) {
|
||||
_log.w(
|
||||
'Tidal SAF file is labeled FLAC but backend returned DASH/M4A stream; converting it back to FLAC.',
|
||||
'Tidal SAF file is labeled FLAC but backend returned DASH/M4A stream; preserving it as M4A instead.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5160,7 +5130,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (shouldPreserveNativeM4a) {
|
||||
} else {
|
||||
_log.d('M4A file detected (SAF), preserving native container...');
|
||||
final tempPath = await _copySafToTemp(currentFilePath);
|
||||
if (tempPath != null) {
|
||||
@@ -5218,85 +5188,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_log.d('M4A file detected (SAF), converting to FLAC...');
|
||||
final tempPath = await _copySafToTemp(currentFilePath);
|
||||
if (tempPath != null) {
|
||||
String? flacPath;
|
||||
try {
|
||||
final length = await File(tempPath).length();
|
||||
if (length < 1024) {
|
||||
_log.w('Temp M4A is too small (<1KB), skipping conversion');
|
||||
} else {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
flacPath = await FFmpegService.convertM4aToFlac(tempPath);
|
||||
if (flacPath != null) {
|
||||
_log.d('Converted to FLAC (temp): $flacPath');
|
||||
_log.d(
|
||||
'Embedding metadata and cover to converted FLAC...',
|
||||
);
|
||||
final finalTrack = _buildTrackForMetadataEmbedding(
|
||||
trackToDownload,
|
||||
result,
|
||||
resolvedAlbumArtist,
|
||||
);
|
||||
|
||||
final backendGenre = result['genre'] as String?;
|
||||
final backendLabel = result['label'] as String?;
|
||||
final backendCopyright = result['copyright'] as String?;
|
||||
|
||||
await _embedMetadataToFile(
|
||||
flacPath,
|
||||
finalTrack,
|
||||
format: 'flac',
|
||||
genre: backendGenre ?? genre,
|
||||
label: backendLabel ?? label,
|
||||
copyright: backendCopyright,
|
||||
downloadService: item.service,
|
||||
writeExternalLrc: false,
|
||||
);
|
||||
|
||||
final newFileName = '${safBaseName ?? 'track'}.flac';
|
||||
final newUri = await _writeTempToSaf(
|
||||
treeUri: settings.downloadTreeUri,
|
||||
relativeDir: effectiveOutputDir,
|
||||
fileName: newFileName,
|
||||
mimeType: _mimeTypeForExt('.flac'),
|
||||
srcPath: flacPath,
|
||||
);
|
||||
|
||||
if (newUri != null) {
|
||||
if (newUri != currentFilePath) {
|
||||
await _deleteSafFile(currentFilePath);
|
||||
}
|
||||
filePath = newUri;
|
||||
finalSafFileName = newFileName;
|
||||
} else {
|
||||
_log.w('Failed to write FLAC to SAF, keeping M4A');
|
||||
}
|
||||
} else {
|
||||
_log.w(
|
||||
'FFmpeg conversion returned null, keeping M4A file',
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_log.w('SAF M4A->FLAC conversion failed: $e');
|
||||
} finally {
|
||||
try {
|
||||
await File(tempPath).delete();
|
||||
} catch (_) {}
|
||||
if (flacPath != null) {
|
||||
try {
|
||||
await File(flacPath).delete();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (quality == 'HIGH') {
|
||||
@@ -5373,7 +5264,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
_log.w('M4A conversion process failed: $e, keeping M4A file');
|
||||
actualQuality = 'AAC 320kbps';
|
||||
}
|
||||
} else if (shouldPreserveNativeM4a) {
|
||||
} else {
|
||||
_log.d('M4A file detected, preserving native container...');
|
||||
|
||||
try {
|
||||
@@ -5382,8 +5273,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
if (!await file.exists()) {
|
||||
_log.e('File does not exist at path: $filePath');
|
||||
} else {
|
||||
if (!(targetPath.toLowerCase().endsWith('.m4a') ||
|
||||
targetPath.toLowerCase().endsWith('.mp4'))) {
|
||||
if (!targetPath.toLowerCase().endsWith('.m4a')) {
|
||||
final renamedPath = targetPath.replaceAll(
|
||||
RegExp(r'\.[^.]+$'),
|
||||
'.m4a',
|
||||
@@ -5428,84 +5318,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
} catch (e) {
|
||||
_log.w('Native M4A handling failed: $e');
|
||||
}
|
||||
} else {
|
||||
_log.d(
|
||||
'M4A file detected (Hi-Res DASH stream), attempting conversion to FLAC...',
|
||||
);
|
||||
|
||||
try {
|
||||
final file = File(currentFilePath);
|
||||
if (!await file.exists()) {
|
||||
_log.e('File does not exist at path: $filePath');
|
||||
} else {
|
||||
final length = await file.length();
|
||||
_log.i('File size before conversion: ${length / 1024} KB');
|
||||
|
||||
if (length < 1024) {
|
||||
_log.w(
|
||||
'File is too small (<1KB), skipping conversion. Download might be corrupt.',
|
||||
);
|
||||
} else {
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.finalizing,
|
||||
progress: 0.95,
|
||||
);
|
||||
final flacPath = await FFmpegService.convertM4aToFlac(
|
||||
currentFilePath,
|
||||
);
|
||||
|
||||
if (flacPath != null) {
|
||||
filePath = flacPath;
|
||||
_log.d('Converted to FLAC: $flacPath');
|
||||
|
||||
_log.d(
|
||||
'Embedding metadata and cover to converted FLAC...',
|
||||
);
|
||||
try {
|
||||
final finalTrack = _buildTrackForMetadataEmbedding(
|
||||
trackToDownload,
|
||||
result,
|
||||
resolvedAlbumArtist,
|
||||
);
|
||||
|
||||
final backendGenre = result['genre'] as String?;
|
||||
final backendLabel = result['label'] as String?;
|
||||
final backendCopyright = result['copyright'] as String?;
|
||||
|
||||
if (backendGenre != null ||
|
||||
backendLabel != null ||
|
||||
backendCopyright != null) {
|
||||
_log.d(
|
||||
'Extended metadata from backend - Genre: $backendGenre, Label: $backendLabel, Copyright: $backendCopyright',
|
||||
);
|
||||
}
|
||||
|
||||
await _embedMetadataToFile(
|
||||
flacPath,
|
||||
finalTrack,
|
||||
format: 'flac',
|
||||
genre: backendGenre ?? genre,
|
||||
label: backendLabel ?? label,
|
||||
copyright: backendCopyright,
|
||||
downloadService: item.service,
|
||||
);
|
||||
_log.d('Metadata and cover embedded successfully');
|
||||
} catch (e) {
|
||||
_log.w('Warning: Failed to embed metadata/cover: $e');
|
||||
}
|
||||
} else {
|
||||
_log.w(
|
||||
'FFmpeg conversion returned null, keeping M4A file',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_log.w(
|
||||
'FFmpeg conversion process failed: $e, keeping M4A file',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (metadataEmbeddingEnabled &&
|
||||
@@ -5820,15 +5632,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
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) ??
|
||||
@@ -5849,7 +5658,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
filePath.startsWith('content://') ||
|
||||
lowerFilePath.endsWith('.flac') ||
|
||||
lowerFilePath.endsWith('.m4a') ||
|
||||
lowerFilePath.endsWith('.mp4') ||
|
||||
lowerFilePath.endsWith('.aac') ||
|
||||
lowerFilePath.endsWith('.mp3') ||
|
||||
lowerFilePath.endsWith('.opus') ||
|
||||
@@ -5937,17 +5745,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
trackNumber: (backendTrackNum != null && backendTrackNum > 0)
|
||||
? backendTrackNum
|
||||
: trackToDownload.trackNumber,
|
||||
totalTracks:
|
||||
(backendTotalTracks != null && backendTotalTracks > 0)
|
||||
? backendTotalTracks
|
||||
: trackToDownload.totalTracks,
|
||||
totalTracks: trackToDownload.totalTracks,
|
||||
discNumber: (backendDiscNum != null && backendDiscNum > 0)
|
||||
? backendDiscNum
|
||||
: trackToDownload.discNumber,
|
||||
totalDiscs:
|
||||
(backendTotalDiscs != null && backendTotalDiscs > 0)
|
||||
? backendTotalDiscs
|
||||
: trackToDownload.totalDiscs,
|
||||
totalDiscs: trackToDownload.totalDiscs,
|
||||
duration: trackToDownload.duration,
|
||||
releaseDate: (backendYear != null && backendYear.isNotEmpty)
|
||||
? backendYear
|
||||
@@ -5956,10 +5758,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
bitDepth: historyBitDepth,
|
||||
sampleRate: historySampleRate,
|
||||
genre: effectiveGenre,
|
||||
composer:
|
||||
(backendComposer != null && backendComposer.isNotEmpty)
|
||||
? backendComposer
|
||||
: trackToDownload.composer,
|
||||
composer: trackToDownload.composer,
|
||||
label: effectiveLabel,
|
||||
copyright: effectiveCopyright,
|
||||
),
|
||||
|
||||
@@ -179,20 +179,6 @@ class Extension {
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? null : trimmed;
|
||||
}
|
||||
|
||||
List<String> get preservedNativeOutputExtensions {
|
||||
final value = capabilities['preserveNativeOutputExtensions'];
|
||||
if (value is! List) return const [];
|
||||
|
||||
final normalized = <String>[];
|
||||
for (final item in value) {
|
||||
if (item is! String) continue;
|
||||
final trimmed = item.trim().toLowerCase();
|
||||
if (trimmed.isEmpty) continue;
|
||||
normalized.add(trimmed.startsWith('.') ? trimmed : '.$trimmed');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
class SearchFilter {
|
||||
|
||||
@@ -609,16 +609,17 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
.map((ext) => ext.id)
|
||||
.firstOrNull;
|
||||
}
|
||||
resolvedProvider ??= 'tidal';
|
||||
}
|
||||
|
||||
final isEnabledExtensionProvider =
|
||||
resolvedProvider != null &&
|
||||
resolvedProvider.isNotEmpty &&
|
||||
extensionState.extensions.any(
|
||||
(ext) => ext.enabled && ext.id == resolvedProvider,
|
||||
);
|
||||
|
||||
if (resolvedProvider.isNotEmpty &&
|
||||
if (resolvedProvider != null &&
|
||||
resolvedProvider.isNotEmpty &&
|
||||
resolvedProvider != 'tidal' &&
|
||||
resolvedProvider != 'qobuz' &&
|
||||
!isEnabledExtensionProvider &&
|
||||
@@ -638,10 +639,10 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
.where((ext) => ext.enabled && ext.hasCustomSearch)
|
||||
.map((ext) => ext.id)
|
||||
.firstOrNull;
|
||||
resolvedProvider ??= 'tidal';
|
||||
}
|
||||
|
||||
if (resolvedProvider.isNotEmpty &&
|
||||
if (resolvedProvider != null &&
|
||||
resolvedProvider.isNotEmpty &&
|
||||
resolvedProvider != 'tidal' &&
|
||||
resolvedProvider != 'qobuz' &&
|
||||
extensionState.extensions.any(
|
||||
@@ -662,9 +663,7 @@ class TrackNotifier extends Notifier<TrackState> {
|
||||
final effectiveBuiltInProvider =
|
||||
resolvedProvider == 'tidal' || resolvedProvider == 'qobuz'
|
||||
? resolvedProvider
|
||||
: (builtInSearchProvider?.isNotEmpty == true
|
||||
? builtInSearchProvider
|
||||
: 'tidal');
|
||||
: builtInSearchProvider;
|
||||
|
||||
if (effectiveBuiltInProvider == null || effectiveBuiltInProvider.isEmpty) {
|
||||
state = TrackState(
|
||||
|
||||
@@ -480,7 +480,7 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
))) {
|
||||
return explicit;
|
||||
}
|
||||
return _defaultSearchExtension(extensions)?.id ?? 'tidal';
|
||||
return _defaultSearchExtension(extensions)?.id;
|
||||
}
|
||||
|
||||
String? _sanitizeSearchFilterForProvider(
|
||||
@@ -524,7 +524,8 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
_canonicalSearchFilterId(candidate.label!) ==
|
||||
canonicalFilter) ||
|
||||
(candidate.icon != null &&
|
||||
_canonicalSearchFilterId(candidate.icon!) == canonicalFilter),
|
||||
_canonicalSearchFilterId(candidate.icon!) ==
|
||||
canonicalFilter),
|
||||
)
|
||||
.firstOrNull;
|
||||
return match?.id;
|
||||
@@ -1288,28 +1289,28 @@ class _HomeTabState extends ConsumerState<HomeTab>
|
||||
(hasHomeFeedExtension || hasExploreContent) &&
|
||||
hasExploreContent;
|
||||
|
||||
ref.listen<String>(settingsProvider.select((s) => s.defaultSearchTab), (
|
||||
previous,
|
||||
next,
|
||||
) {
|
||||
if (previous == next) return;
|
||||
final selectedSearchFilter = ref.read(
|
||||
trackProvider.select((s) => s.selectedSearchFilter),
|
||||
);
|
||||
if (selectedSearchFilter != null && selectedSearchFilter.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
ref.listen<String>(
|
||||
settingsProvider.select((s) => s.defaultSearchTab),
|
||||
(previous, next) {
|
||||
if (previous == next) return;
|
||||
final selectedSearchFilter = ref.read(
|
||||
trackProvider.select((s) => s.selectedSearchFilter),
|
||||
);
|
||||
if (selectedSearchFilter != null && selectedSearchFilter.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final text = _urlController.text.trim();
|
||||
if (text.isEmpty || text.length < _minLiveSearchChars) return;
|
||||
if (text.startsWith('http') || text.startsWith('spotify:')) return;
|
||||
final text = _urlController.text.trim();
|
||||
if (text.isEmpty || text.length < _minLiveSearchChars) return;
|
||||
if (text.startsWith('http') || text.startsWith('spotify:')) return;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_lastSearchQuery = null;
|
||||
_performSearch(text);
|
||||
});
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_lastSearchQuery = null;
|
||||
_performSearch(text);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (hasActualResults &&
|
||||
isShowingRecentAccess &&
|
||||
@@ -3553,10 +3554,8 @@ class _SearchProviderDropdown extends ConsumerWidget {
|
||||
.where((ext) => ext.enabled && ext.hasCustomSearch)
|
||||
.toList();
|
||||
final primarySearchExtension = _defaultSearchExtension(searchProviders);
|
||||
final defaultProviderTarget =
|
||||
primarySearchExtension?.displayName ?? 'Tidal';
|
||||
final defaultProviderLabel =
|
||||
'${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)';
|
||||
primarySearchExtension?.displayName ?? 'Deezer';
|
||||
final defaultProviderIconPath = primarySearchExtension?.iconPath;
|
||||
final currentProvider =
|
||||
rawCurrentProvider != null &&
|
||||
|
||||
@@ -4333,40 +4333,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
),
|
||||
|
||||
if (filterMode == 'all' &&
|
||||
totalTrackCount == 0 &&
|
||||
!showFilteringIndicator &&
|
||||
(_activeFilterCount > 0 || unifiedItems.isNotEmpty))
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
if (!_isSelectionMode)
|
||||
_buildFilterButton(context, unifiedItems),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (filterMode == 'singles' &&
|
||||
totalTrackCount == 0 &&
|
||||
!showFilteringIndicator &&
|
||||
(_activeFilterCount > 0 || unifiedItems.isNotEmpty))
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
if (!_isSelectionMode)
|
||||
_buildFilterButton(context, unifiedItems),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
if (historyItems.isNotEmpty && hasQueueItems)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
|
||||
@@ -735,7 +735,6 @@ class _LanguageSelector extends StatelessWidget {
|
||||
('pt_PT', 'Português (Brasil)', Icons.language),
|
||||
('ru', 'Русский', Icons.language),
|
||||
('tr', 'Türkçe', Icons.language),
|
||||
('uk', 'Українська', Icons.language),
|
||||
('zh', '简体中文', Icons.language),
|
||||
('zh_CN', '简体中文 (中国)', Icons.language),
|
||||
('zh_TW', '繁體中文', Icons.language),
|
||||
|
||||
@@ -742,10 +742,8 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
final rawSearchProvider = settings.searchProvider?.trim() ?? '';
|
||||
final isValidBuiltIn = _builtInProviders.containsKey(rawSearchProvider);
|
||||
final primarySearchExtension = _defaultSearchExtension(extState.extensions);
|
||||
final defaultProviderTarget =
|
||||
primarySearchExtension?.displayName ?? 'Tidal';
|
||||
final defaultProviderLabel =
|
||||
'${context.l10n.extensionsHomeFeedAuto} ($defaultProviderTarget)';
|
||||
primarySearchExtension?.displayName ?? 'Deezer';
|
||||
final searchProvider =
|
||||
isValidBuiltIn ||
|
||||
extState.extensions.any(
|
||||
|
||||
@@ -1978,14 +1978,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path fill="#000000" d="M0 0h1024v1024H0z"/><g fill="none" stroke="#1DB954" stroke-linecap="round" stroke-linejoin="round" transform="matrix(.9 0 0 .9 51.2 1.2)"><path stroke-width="38" d="M512 148v592"/><path stroke-width="36.1" d="M422 217.4v241.2m180-241.2v241.2"/><path stroke-width="34.2" d="M341 290v96m342-96v96"/><path stroke-width="38" d="m420 642 92 110 92-110M290 762v90q0 72 72 72h300q72 0 72-72v-90"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 488 B |
@@ -1,7 +1,7 @@
|
||||
name: spotiflac_android
|
||||
description: Download Spotify tracks in FLAC from Tidal, Qobuz & Deezer
|
||||
publish_to: "none"
|
||||
version: 4.3.1+126
|
||||
version: 4.3.0+125
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.0
|
||||
|
||||