mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-17 16:37:17 +02:00
feat: YouTube customizable bitrate, improved title matching, SpotubeDL engine fallback
- Add configurable YouTube Opus (96-256kbps) and MP3 (96-320kbps) bitrates - Improve title matching with loose normalization for symbol-heavy tracks - Add SpotubeDL engine v2 fallback for MP3 requests - Improve filename sanitization in track metadata screen - Bump version to 3.6.9+82
This commit is contained in:
@@ -2771,7 +2771,29 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
settings,
|
||||
);
|
||||
|
||||
final quality = item.qualityOverride ?? state.audioQuality;
|
||||
var quality = item.qualityOverride ?? state.audioQuality;
|
||||
if (item.service.toLowerCase() == 'youtube') {
|
||||
final normalized = quality.toLowerCase();
|
||||
final isYoutubeQuality =
|
||||
normalized.startsWith('mp3_') || normalized.startsWith('opus_');
|
||||
if (!isYoutubeQuality) {
|
||||
final mp3Bitrate = (() {
|
||||
const supported = [128, 256, 320];
|
||||
var nearest = supported.first;
|
||||
var nearestDistance = (settings.youtubeMp3Bitrate - nearest).abs();
|
||||
for (final option in supported.skip(1)) {
|
||||
final distance = (settings.youtubeMp3Bitrate - option).abs();
|
||||
if (distance < nearestDistance ||
|
||||
(distance == nearestDistance && option > nearest)) {
|
||||
nearest = option;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
})();
|
||||
quality = 'mp3_$mp3Bitrate';
|
||||
}
|
||||
}
|
||||
final isSafMode = _isSafMode(settings);
|
||||
final relativeOutputDir = isSafMode
|
||||
? await _buildRelativeOutputDir(
|
||||
@@ -3032,8 +3054,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
outputDir: outputDir,
|
||||
filenameFormat: state.filenameFormat,
|
||||
quality: quality,
|
||||
// Keep prior behavior: non-YouTube paths were implicitly true.
|
||||
embedLyrics: isYouTube ? settings.embedLyrics : true,
|
||||
embedLyrics: settings.embedLyrics,
|
||||
embedMaxQualityCover: settings.maxQualityCover,
|
||||
trackNumber: normalizedTrackNumber,
|
||||
discNumber: normalizedDiscNumber,
|
||||
|
||||
@@ -13,6 +13,9 @@ const _spotifyClientSecretKey = 'spotify_client_secret';
|
||||
final _log = AppLogger('SettingsProvider');
|
||||
|
||||
class SettingsNotifier extends Notifier<AppSettings> {
|
||||
static const List<int> _youtubeOpusSupportedBitrates = [128, 256];
|
||||
static const List<int> _youtubeMp3SupportedBitrates = [128, 256, 320];
|
||||
|
||||
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
|
||||
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
|
||||
bool _isSavingSettings = false;
|
||||
@@ -32,6 +35,7 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
state = AppSettings.fromJson(jsonDecode(json));
|
||||
|
||||
await _runMigrations(prefs);
|
||||
await _normalizeYouTubeBitratesIfNeeded();
|
||||
}
|
||||
|
||||
await _loadSpotifyClientSecret(prefs);
|
||||
@@ -107,6 +111,49 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
}
|
||||
}
|
||||
|
||||
int _nearestSupportedBitrate(int value, List<int> supported) {
|
||||
var nearest = supported.first;
|
||||
var nearestDistance = (value - nearest).abs();
|
||||
|
||||
for (final option in supported.skip(1)) {
|
||||
final distance = (value - option).abs();
|
||||
// On tie, prefer higher quality bitrate.
|
||||
if (distance < nearestDistance ||
|
||||
(distance == nearestDistance && option > nearest)) {
|
||||
nearest = option;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
|
||||
int _normalizeYouTubeOpusBitrate(int bitrate) {
|
||||
return _nearestSupportedBitrate(bitrate, _youtubeOpusSupportedBitrates);
|
||||
}
|
||||
|
||||
int _normalizeYouTubeMp3Bitrate(int bitrate) {
|
||||
return _nearestSupportedBitrate(bitrate, _youtubeMp3SupportedBitrates);
|
||||
}
|
||||
|
||||
Future<void> _normalizeYouTubeBitratesIfNeeded() async {
|
||||
final normalizedOpus = _normalizeYouTubeOpusBitrate(
|
||||
state.youtubeOpusBitrate,
|
||||
);
|
||||
final normalizedMp3 = _normalizeYouTubeMp3Bitrate(state.youtubeMp3Bitrate);
|
||||
|
||||
if (normalizedOpus == state.youtubeOpusBitrate &&
|
||||
normalizedMp3 == state.youtubeMp3Bitrate) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
youtubeOpusBitrate: normalizedOpus,
|
||||
youtubeMp3Bitrate: normalizedMp3,
|
||||
);
|
||||
await _saveSettings();
|
||||
}
|
||||
|
||||
Future<void> _loadSpotifyClientSecret(SharedPreferences prefs) async {
|
||||
final storedSecret = await _secureStorage.read(
|
||||
key: _spotifyClientSecretKey,
|
||||
@@ -230,7 +277,9 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
}
|
||||
|
||||
void setMusixmatchLanguage(String languageCode) {
|
||||
state = state.copyWith(musixmatchLanguage: languageCode.trim().toLowerCase());
|
||||
state = state.copyWith(
|
||||
musixmatchLanguage: languageCode.trim().toLowerCase(),
|
||||
);
|
||||
_saveSettings();
|
||||
_syncLyricsSettingsToBackend();
|
||||
}
|
||||
@@ -390,6 +439,18 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setYoutubeOpusBitrate(int bitrate) {
|
||||
final normalized = _normalizeYouTubeOpusBitrate(bitrate);
|
||||
state = state.copyWith(youtubeOpusBitrate: normalized);
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setYoutubeMp3Bitrate(int bitrate) {
|
||||
final normalized = _normalizeYouTubeMp3Bitrate(bitrate);
|
||||
state = state.copyWith(youtubeMp3Bitrate: normalized);
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setUseAllFilesAccess(bool enabled) {
|
||||
state = state.copyWith(useAllFilesAccess: enabled);
|
||||
_saveSettings();
|
||||
|
||||
Reference in New Issue
Block a user