mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-16 05:29:15 +02:00
feat: replace custom FFmpeg AAR with ffmpeg_kit_flutter plugin, add Lossy format support (MP3/Opus)
- Replace custom ffmpeg-kit-with-lame.aar with ffmpeg_kit_flutter_new_audio plugin - Rename MP3 option to Lossy with format selection (MP3 320kbps or Opus 128kbps) - Add convertFlacToOpus() and convertFlacToLossy() functions in FFmpegService - Update settings model: enableMp3Option -> enableLossyOption, add lossyFormat field - Update download_queue_provider to use LOSSY quality with format from settings - Remove FFMPEG_CHANNEL MethodChannel from MainActivity.kt - Delete custom FFmpeg AAR files from android/app/libs/ - Add new localization strings for lossy format options
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,8 +7,6 @@ import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.engine.FlutterShellArgs
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import gobackend.Gobackend
|
||||
import com.arthenica.ffmpegkit.FFmpegKit
|
||||
import com.arthenica.ffmpegkit.ReturnCode
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -18,7 +16,6 @@ import java.util.Locale
|
||||
|
||||
class MainActivity: FlutterActivity() {
|
||||
private val CHANNEL = "com.zarz.spotiflac/backend"
|
||||
private val FFMPEG_CHANNEL = "com.zarz.spotiflac/ffmpeg"
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
companion object {
|
||||
@@ -901,37 +898,5 @@ class MainActivity: FlutterActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FFmpeg method channel
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, FFMPEG_CHANNEL).setMethodCallHandler { call, result ->
|
||||
scope.launch {
|
||||
try {
|
||||
when (call.method) {
|
||||
"execute" -> {
|
||||
val command = call.argument<String>("command") ?: ""
|
||||
val session = withContext(Dispatchers.IO) {
|
||||
FFmpegKit.execute(command)
|
||||
}
|
||||
val returnCode = session.returnCode
|
||||
val output = session.output ?: ""
|
||||
result.success(mapOf(
|
||||
"success" to ReturnCode.isSuccess(returnCode),
|
||||
"returnCode" to (returnCode?.value ?: -1),
|
||||
"output" to output
|
||||
))
|
||||
}
|
||||
"getVersion" -> {
|
||||
val session = withContext(Dispatchers.IO) {
|
||||
FFmpegKit.execute("-version")
|
||||
}
|
||||
result.success(session.output ?: "unknown")
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
result.error("FFMPEG_ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3394,35 +3394,65 @@ abstract class AppLocalizations {
|
||||
/// **'24-bit / up to 192kHz'**
|
||||
String get qualityHiResFlacMaxSubtitle;
|
||||
|
||||
/// Quality option - MP3 lossy format
|
||||
/// Quality option - lossy format (MP3/Opus)
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'MP3'**
|
||||
String get qualityMp3;
|
||||
/// **'Lossy'**
|
||||
String get qualityLossy;
|
||||
|
||||
/// Technical spec for MP3
|
||||
/// Technical spec for lossy MP3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'320kbps (converted from FLAC)'**
|
||||
String get qualityMp3Subtitle;
|
||||
/// **'MP3 320kbps (converted from FLAC)'**
|
||||
String get qualityLossyMp3Subtitle;
|
||||
|
||||
/// Setting - enable MP3 quality option
|
||||
/// Technical spec for lossy Opus
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable MP3 Option'**
|
||||
String get enableMp3Option;
|
||||
/// **'Opus 128kbps (converted from FLAC)'**
|
||||
String get qualityLossyOpusSubtitle;
|
||||
|
||||
/// Subtitle when MP3 is enabled
|
||||
/// Setting - enable lossy quality option
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'MP3 quality option is available'**
|
||||
String get enableMp3OptionSubtitleOn;
|
||||
/// **'Enable Lossy Option'**
|
||||
String get enableLossyOption;
|
||||
|
||||
/// Subtitle when MP3 is disabled
|
||||
/// Subtitle when lossy is enabled
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Downloads FLAC then converts to 320kbps MP3'**
|
||||
String get enableMp3OptionSubtitleOff;
|
||||
/// **'Lossy quality option is available'**
|
||||
String get enableLossyOptionSubtitleOn;
|
||||
|
||||
/// Subtitle when lossy is disabled
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Downloads FLAC then converts to lossy format'**
|
||||
String get enableLossyOptionSubtitleOff;
|
||||
|
||||
/// Setting - choose lossy format
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Lossy Format'**
|
||||
String get lossyFormat;
|
||||
|
||||
/// Description for lossy format picker
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose the lossy format for conversion'**
|
||||
String get lossyFormatDescription;
|
||||
|
||||
/// MP3 format description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'320kbps, best compatibility'**
|
||||
String get lossyFormatMp3Subtitle;
|
||||
|
||||
/// Opus format description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'128kbps, better quality at smaller size'**
|
||||
String get lossyFormatOpusSubtitle;
|
||||
|
||||
/// Note about quality availability
|
||||
///
|
||||
|
||||
@@ -1869,20 +1869,36 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1868,20 +1868,36 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / hingga 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (konversi dari FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Aktifkan Opsi MP3';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'Opsi kualitas MP3 tersedia';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Unduh FLAC lalu konversi ke MP3 320kbps';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / 最大 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1894,20 +1894,36 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-бит / до 192кГц';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
@@ -1856,20 +1856,36 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
String get qualityHiResFlacMaxSubtitle => '24-bit / up to 192kHz';
|
||||
|
||||
@override
|
||||
String get qualityMp3 => 'MP3';
|
||||
String get qualityLossy => 'Lossy';
|
||||
|
||||
@override
|
||||
String get qualityMp3Subtitle => '320kbps (converted from FLAC)';
|
||||
String get qualityLossyMp3Subtitle => 'MP3 320kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3Option => 'Enable MP3 Option';
|
||||
String get qualityLossyOpusSubtitle => 'Opus 128kbps (converted from FLAC)';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOn => 'MP3 quality option is available';
|
||||
String get enableLossyOption => 'Enable Lossy Option';
|
||||
|
||||
@override
|
||||
String get enableMp3OptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to 320kbps MP3';
|
||||
String get enableLossyOptionSubtitleOn => 'Lossy quality option is available';
|
||||
|
||||
@override
|
||||
String get enableLossyOptionSubtitleOff =>
|
||||
'Downloads FLAC then converts to lossy format';
|
||||
|
||||
@override
|
||||
String get lossyFormat => 'Lossy Format';
|
||||
|
||||
@override
|
||||
String get lossyFormatDescription => 'Choose the lossy format for conversion';
|
||||
|
||||
@override
|
||||
String get lossyFormatMp3Subtitle => '320kbps, best compatibility';
|
||||
|
||||
@override
|
||||
String get lossyFormatOpusSubtitle =>
|
||||
'128kbps, better quality at smaller size';
|
||||
|
||||
@override
|
||||
String get qualityNote =>
|
||||
|
||||
+20
-10
@@ -1373,16 +1373,26 @@
|
||||
"@qualityHiResFlacMax": {"description": "Quality option - maximum resolution FLAC"},
|
||||
"qualityHiResFlacMaxSubtitle": "24-bit / up to 192kHz",
|
||||
"@qualityHiResFlacMaxSubtitle": {"description": "Technical spec for hi-res max"},
|
||||
"qualityMp3": "MP3",
|
||||
"@qualityMp3": {"description": "Quality option - MP3 lossy format"},
|
||||
"qualityMp3Subtitle": "320kbps (converted from FLAC)",
|
||||
"@qualityMp3Subtitle": {"description": "Technical spec for MP3"},
|
||||
"enableMp3Option": "Enable MP3 Option",
|
||||
"@enableMp3Option": {"description": "Setting - enable MP3 quality option"},
|
||||
"enableMp3OptionSubtitleOn": "MP3 quality option is available",
|
||||
"@enableMp3OptionSubtitleOn": {"description": "Subtitle when MP3 is enabled"},
|
||||
"enableMp3OptionSubtitleOff": "Downloads FLAC then converts to 320kbps MP3",
|
||||
"@enableMp3OptionSubtitleOff": {"description": "Subtitle when MP3 is disabled"},
|
||||
"qualityLossy": "Lossy",
|
||||
"@qualityLossy": {"description": "Quality option - lossy format (MP3/Opus)"},
|
||||
"qualityLossyMp3Subtitle": "MP3 320kbps (converted from FLAC)",
|
||||
"@qualityLossyMp3Subtitle": {"description": "Technical spec for lossy MP3"},
|
||||
"qualityLossyOpusSubtitle": "Opus 128kbps (converted from FLAC)",
|
||||
"@qualityLossyOpusSubtitle": {"description": "Technical spec for lossy Opus"},
|
||||
"enableLossyOption": "Enable Lossy Option",
|
||||
"@enableLossyOption": {"description": "Setting - enable lossy quality option"},
|
||||
"enableLossyOptionSubtitleOn": "Lossy quality option is available",
|
||||
"@enableLossyOptionSubtitleOn": {"description": "Subtitle when lossy is enabled"},
|
||||
"enableLossyOptionSubtitleOff": "Downloads FLAC then converts to lossy format",
|
||||
"@enableLossyOptionSubtitleOff": {"description": "Subtitle when lossy is disabled"},
|
||||
"lossyFormat": "Lossy Format",
|
||||
"@lossyFormat": {"description": "Setting - choose lossy format"},
|
||||
"lossyFormatDescription": "Choose the lossy format for conversion",
|
||||
"@lossyFormatDescription": {"description": "Description for lossy format picker"},
|
||||
"lossyFormatMp3Subtitle": "320kbps, best compatibility",
|
||||
"@lossyFormatMp3Subtitle": {"description": "MP3 format description"},
|
||||
"lossyFormatOpusSubtitle": "128kbps, better quality at smaller size",
|
||||
"@lossyFormatOpusSubtitle": {"description": "Opus format description"},
|
||||
"qualityNote": "Actual quality depends on track availability from the service",
|
||||
"@qualityNote": {"description": "Note about quality availability"},
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ class AppSettings {
|
||||
final String albumFolderStructure;
|
||||
final bool showExtensionStore;
|
||||
final String locale;
|
||||
final bool enableMp3Option;
|
||||
final bool enableLossyOption;
|
||||
final String lossyFormat;
|
||||
final String lyricsMode;
|
||||
|
||||
const AppSettings({
|
||||
@@ -62,7 +63,8 @@ class AppSettings {
|
||||
this.albumFolderStructure = 'artist_album',
|
||||
this.showExtensionStore = true,
|
||||
this.locale = 'system',
|
||||
this.enableMp3Option = false,
|
||||
this.enableLossyOption = false,
|
||||
this.lossyFormat = 'mp3',
|
||||
this.lyricsMode = 'embed',
|
||||
});
|
||||
|
||||
@@ -95,7 +97,8 @@ class AppSettings {
|
||||
String? albumFolderStructure,
|
||||
bool? showExtensionStore,
|
||||
String? locale,
|
||||
bool? enableMp3Option,
|
||||
bool? enableLossyOption,
|
||||
String? lossyFormat,
|
||||
String? lyricsMode,
|
||||
}) {
|
||||
return AppSettings(
|
||||
@@ -126,7 +129,8 @@ class AppSettings {
|
||||
albumFolderStructure: albumFolderStructure ?? this.albumFolderStructure,
|
||||
showExtensionStore: showExtensionStore ?? this.showExtensionStore,
|
||||
locale: locale ?? this.locale,
|
||||
enableMp3Option: enableMp3Option ?? this.enableMp3Option,
|
||||
enableLossyOption: enableLossyOption ?? this.enableLossyOption,
|
||||
lossyFormat: lossyFormat ?? this.lossyFormat,
|
||||
lyricsMode: lyricsMode ?? this.lyricsMode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
|
||||
json['albumFolderStructure'] as String? ?? 'artist_album',
|
||||
showExtensionStore: json['showExtensionStore'] as bool? ?? true,
|
||||
locale: json['locale'] as String? ?? 'system',
|
||||
enableMp3Option: json['enableMp3Option'] as bool? ?? false,
|
||||
enableLossyOption: json['enableLossyOption'] as bool? ?? false,
|
||||
lossyFormat: json['lossyFormat'] as String? ?? 'mp3',
|
||||
lyricsMode: json['lyricsMode'] as String? ?? 'embed',
|
||||
);
|
||||
|
||||
@@ -69,6 +70,7 @@ Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
|
||||
'albumFolderStructure': instance.albumFolderStructure,
|
||||
'showExtensionStore': instance.showExtensionStore,
|
||||
'locale': instance.locale,
|
||||
'enableMp3Option': instance.enableMp3Option,
|
||||
'enableLossyOption': instance.enableLossyOption,
|
||||
'lossyFormat': instance.lossyFormat,
|
||||
'lyricsMode': instance.lyricsMode,
|
||||
};
|
||||
|
||||
@@ -1668,9 +1668,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
|
||||
final quality = item.qualityOverride ?? state.audioQuality;
|
||||
|
||||
// For MP3, we need to download FLAC first then convert
|
||||
// Servers don't support MP3 quality directly
|
||||
final downloadQuality = quality == 'MP3' ? 'LOSSLESS' : quality;
|
||||
// For LOSSY, we need to download FLAC first then convert
|
||||
// Servers don't support lossy quality directly
|
||||
final downloadQuality = quality == 'LOSSY' ? 'LOSSLESS' : quality;
|
||||
|
||||
// Fetch extended metadata (genre, label) from Deezer if available
|
||||
String? genre;
|
||||
@@ -1721,7 +1721,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
if (useExtensions) {
|
||||
_log.d('Using extension providers for download');
|
||||
_log.d(
|
||||
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}${quality == 'MP3' ? ' (downloading as LOSSLESS for conversion)' : ''}',
|
||||
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}${quality == 'LOSSY' ? ' (downloading as LOSSLESS for conversion)' : ''}',
|
||||
);
|
||||
_log.d('Output dir: $outputDir');
|
||||
result = await PlatformBridge.downloadWithExtensions(
|
||||
@@ -1748,7 +1748,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
} else if (state.autoFallback) {
|
||||
_log.d('Using auto-fallback mode');
|
||||
_log.d(
|
||||
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}${quality == 'MP3' ? ' (downloading as LOSSLESS for conversion)' : ''}',
|
||||
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}${quality == 'LOSSY' ? ' (downloading as LOSSLESS for conversion)' : ''}',
|
||||
);
|
||||
_log.d('Output dir: $outputDir');
|
||||
result = await PlatformBridge.downloadWithFallback(
|
||||
@@ -1969,11 +1969,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (quality == 'MP3' && filePath != null && filePath.endsWith('.flac')) {
|
||||
if (quality == 'LOSSY' && filePath != null && filePath.endsWith('.flac')) {
|
||||
if (wasExisting) {
|
||||
_log.i('MP3 requested but existing FLAC found - skipping conversion to preserve original file');
|
||||
_log.i('Lossy requested but existing FLAC found - skipping conversion to preserve original file');
|
||||
} else {
|
||||
_log.i('MP3 quality selected, converting FLAC to MP3...');
|
||||
final lossyFormat = settings.lossyFormat;
|
||||
_log.i('Lossy quality selected, converting FLAC to $lossyFormat...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
@@ -1981,40 +1982,43 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
);
|
||||
|
||||
try {
|
||||
final mp3Path = await FFmpegService.convertFlacToMp3(
|
||||
final convertedPath = await FFmpegService.convertFlacToLossy(
|
||||
filePath,
|
||||
bitrate: '320k',
|
||||
format: lossyFormat,
|
||||
deleteOriginal: true,
|
||||
);
|
||||
|
||||
if (mp3Path != null) {
|
||||
filePath = mp3Path;
|
||||
actualQuality = 'MP3 320kbps';
|
||||
_log.i('Successfully converted to MP3: $mp3Path');
|
||||
if (convertedPath != null) {
|
||||
filePath = convertedPath;
|
||||
actualQuality = lossyFormat == 'opus' ? 'Opus 128kbps' : 'MP3 320kbps';
|
||||
_log.i('Successfully converted to $lossyFormat: $convertedPath');
|
||||
|
||||
_log.i('Embedding metadata to MP3...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
final mp3BackendGenre = result['genre'] as String?;
|
||||
final mp3BackendLabel = result['label'] as String?;
|
||||
final mp3BackendCopyright = result['copyright'] as String?;
|
||||
|
||||
await _embedMetadataToMp3(
|
||||
mp3Path,
|
||||
trackToDownload,
|
||||
genre: mp3BackendGenre ?? genre,
|
||||
label: mp3BackendLabel ?? label,
|
||||
copyright: mp3BackendCopyright,
|
||||
);
|
||||
// Only embed metadata for MP3 (Opus uses Vorbis comments which are preserved)
|
||||
if (lossyFormat == 'mp3') {
|
||||
_log.i('Embedding metadata to MP3...');
|
||||
updateItemStatus(
|
||||
item.id,
|
||||
DownloadStatus.downloading,
|
||||
progress: 0.99,
|
||||
);
|
||||
|
||||
final mp3BackendGenre = result['genre'] as String?;
|
||||
final mp3BackendLabel = result['label'] as String?;
|
||||
final mp3BackendCopyright = result['copyright'] as String?;
|
||||
|
||||
await _embedMetadataToMp3(
|
||||
convertedPath,
|
||||
trackToDownload,
|
||||
genre: mp3BackendGenre ?? genre,
|
||||
label: mp3BackendLabel ?? label,
|
||||
copyright: mp3BackendCopyright,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_log.w('MP3 conversion failed, keeping FLAC file');
|
||||
_log.w('$lossyFormat conversion failed, keeping FLAC file');
|
||||
}
|
||||
} catch (e) {
|
||||
_log.e('MP3 conversion error: $e, keeping FLAC file');
|
||||
_log.e('Lossy conversion error: $e, keeping FLAC file');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,14 +231,19 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setEnableMp3Option(bool enabled) {
|
||||
state = state.copyWith(enableMp3Option: enabled);
|
||||
// If MP3 is disabled and current quality is MP3, reset to LOSSLESS
|
||||
if (!enabled && state.audioQuality == 'MP3') {
|
||||
void setEnableLossyOption(bool enabled) {
|
||||
state = state.copyWith(enableLossyOption: enabled);
|
||||
// If Lossy is disabled and current quality is LOSSY, reset to LOSSLESS
|
||||
if (!enabled && state.audioQuality == 'LOSSY') {
|
||||
state = state.copyWith(audioQuality: 'LOSSLESS');
|
||||
}
|
||||
_saveSettings();
|
||||
}
|
||||
|
||||
void setLossyFormat(String format) {
|
||||
state = state.copyWith(lossyFormat: format);
|
||||
_saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
final settingsProvider = NotifierProvider<SettingsNotifier, AppSettings>(
|
||||
|
||||
@@ -101,15 +101,24 @@ class DownloadSettingsPage extends ConsumerWidget {
|
||||
),
|
||||
SettingsSwitchItem(
|
||||
icon: Icons.audiotrack,
|
||||
title: context.l10n.enableMp3Option,
|
||||
subtitle: settings.enableMp3Option
|
||||
? context.l10n.enableMp3OptionSubtitleOn
|
||||
: context.l10n.enableMp3OptionSubtitleOff,
|
||||
value: settings.enableMp3Option,
|
||||
title: context.l10n.enableLossyOption,
|
||||
subtitle: settings.enableLossyOption
|
||||
? context.l10n.enableLossyOptionSubtitleOn
|
||||
: context.l10n.enableLossyOptionSubtitleOff,
|
||||
value: settings.enableLossyOption,
|
||||
onChanged: (value) => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setEnableMp3Option(value),
|
||||
.setEnableLossyOption(value),
|
||||
),
|
||||
if (settings.enableLossyOption)
|
||||
SettingsItem(
|
||||
icon: Icons.tune,
|
||||
title: context.l10n.lossyFormat,
|
||||
subtitle: settings.lossyFormat == 'opus'
|
||||
? 'Opus 128kbps'
|
||||
: 'MP3 320kbps',
|
||||
onTap: () => _showLossyFormatPicker(context, ref, settings.lossyFormat),
|
||||
),
|
||||
if (!settings.askQualityBeforeDownload && isBuiltInService) ...[
|
||||
_QualityOption(
|
||||
title: context.l10n.qualityFlacLossless,
|
||||
@@ -134,16 +143,18 @@ class DownloadSettingsPage extends ConsumerWidget {
|
||||
onTap: () => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setAudioQuality('HI_RES_LOSSLESS'),
|
||||
showDivider: settings.enableMp3Option,
|
||||
showDivider: settings.enableLossyOption,
|
||||
),
|
||||
if (settings.enableMp3Option)
|
||||
if (settings.enableLossyOption)
|
||||
_QualityOption(
|
||||
title: context.l10n.qualityMp3,
|
||||
subtitle: context.l10n.qualityMp3Subtitle,
|
||||
isSelected: settings.audioQuality == 'MP3',
|
||||
title: context.l10n.qualityLossy,
|
||||
subtitle: settings.lossyFormat == 'opus'
|
||||
? context.l10n.qualityLossyOpusSubtitle
|
||||
: context.l10n.qualityLossyMp3Subtitle,
|
||||
isSelected: settings.audioQuality == 'LOSSY',
|
||||
onTap: () => ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setAudioQuality('MP3'),
|
||||
.setAudioQuality('LOSSY'),
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
@@ -722,6 +733,68 @@ class DownloadSettingsPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showLossyFormatPicker(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
String current,
|
||||
) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: colorScheme.surfaceContainerHigh,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
|
||||
),
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
|
||||
child: Text(
|
||||
context.l10n.lossyFormat,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),
|
||||
child: Text(
|
||||
context.l10n.lossyFormatDescription,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.audiotrack),
|
||||
title: const Text('MP3'),
|
||||
subtitle: Text(context.l10n.lossyFormatMp3Subtitle),
|
||||
trailing: current == 'mp3' ? const Icon(Icons.check) : null,
|
||||
onTap: () {
|
||||
ref.read(settingsProvider.notifier).setLossyFormat('mp3');
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.graphic_eq),
|
||||
title: const Text('Opus'),
|
||||
subtitle: Text(context.l10n.lossyFormatOpusSubtitle),
|
||||
trailing: current == 'opus' ? const Icon(Icons.check) : null,
|
||||
onTap: () {
|
||||
ref.read(settingsProvider.notifier).setLossyFormat('opus');
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showFolderOrganizationPicker(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit.dart';
|
||||
import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit_config.dart';
|
||||
import 'package:ffmpeg_kit_flutter_new_audio/return_code.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
final _log = AppLogger('FFmpeg');
|
||||
|
||||
/// FFmpeg service for audio conversion and remuxing
|
||||
/// Uses native MethodChannel to call FFmpegKit from local AAR
|
||||
/// Uses ffmpeg_kit_flutter_new_audio plugin
|
||||
class FFmpegService {
|
||||
static const _channel = MethodChannel('com.zarz.spotiflac/ffmpeg');
|
||||
|
||||
static Future<FFmpegResult> _execute(String command) async {
|
||||
try {
|
||||
final result = await _channel.invokeMethod('execute', {'command': command});
|
||||
final map = Map<String, dynamic>.from(result);
|
||||
final session = await FFmpegKit.execute(command);
|
||||
final returnCode = await session.getReturnCode();
|
||||
final output = await session.getOutput() ?? '';
|
||||
|
||||
return FFmpegResult(
|
||||
success: map['success'] as bool,
|
||||
returnCode: map['returnCode'] as int,
|
||||
output: map['output'] as String,
|
||||
success: ReturnCode.isSuccess(returnCode),
|
||||
returnCode: returnCode?.getValue() ?? -1,
|
||||
output: output,
|
||||
);
|
||||
} catch (e) {
|
||||
_log.e('FFmpeg execute error: $e');
|
||||
@@ -69,6 +71,48 @@ class FFmpegService {
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<String?> convertFlacToOpus(
|
||||
String inputPath, {
|
||||
String bitrate = '128k',
|
||||
bool deleteOriginal = true,
|
||||
}) async {
|
||||
final outputPath = inputPath.replaceAll('.flac', '.opus');
|
||||
|
||||
// Opus in OGG container with VBR
|
||||
final command =
|
||||
'-i "$inputPath" -codec:a libopus -b:a $bitrate -vbr on -compression_level 10 -map 0:a -map_metadata 0 "$outputPath" -y';
|
||||
|
||||
final result = await _execute(command);
|
||||
|
||||
if (result.success) {
|
||||
if (deleteOriginal) {
|
||||
try {
|
||||
await File(inputPath).delete();
|
||||
} catch (_) {}
|
||||
}
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
_log.e('FLAC to Opus conversion failed: ${result.output}');
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Convert FLAC to lossy format based on format parameter
|
||||
/// format: 'mp3' or 'opus'
|
||||
static Future<String?> convertFlacToLossy(
|
||||
String inputPath, {
|
||||
required String format,
|
||||
bool deleteOriginal = true,
|
||||
}) async {
|
||||
switch (format.toLowerCase()) {
|
||||
case 'opus':
|
||||
return convertFlacToOpus(inputPath, deleteOriginal: deleteOriginal);
|
||||
case 'mp3':
|
||||
default:
|
||||
return convertFlacToMp3(inputPath, deleteOriginal: deleteOriginal);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> convertFlacToM4a(
|
||||
String inputPath, {
|
||||
String codec = 'aac',
|
||||
@@ -104,8 +148,8 @@ class FFmpegService {
|
||||
|
||||
static Future<bool> isAvailable() async {
|
||||
try {
|
||||
final version = await _channel.invokeMethod('getVersion');
|
||||
return version != null && version.toString().isNotEmpty;
|
||||
final version = await FFmpegKitConfig.getFFmpegVersion();
|
||||
return version?.isNotEmpty ?? false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
@@ -113,8 +157,7 @@ class FFmpegService {
|
||||
|
||||
static Future<String?> getVersion() async {
|
||||
try {
|
||||
final version = await _channel.invokeMethod('getVersion');
|
||||
return version as String?;
|
||||
return await FFmpegKitConfig.getFFmpegVersion();
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ const _builtInServices = [
|
||||
),
|
||||
];
|
||||
|
||||
/// MP3 quality option (shown when enabled in settings)
|
||||
const _mp3QualityOption = QualityOption(
|
||||
id: 'MP3',
|
||||
label: 'MP3',
|
||||
description: '320kbps (converted from FLAC)',
|
||||
/// Lossy quality option (shown when enabled in settings)
|
||||
const _lossyQualityOption = QualityOption(
|
||||
id: 'LOSSY',
|
||||
label: 'Lossy',
|
||||
description: 'MP3 320kbps or Opus 128kbps',
|
||||
);
|
||||
|
||||
/// A reusable widget for selecting download service (built-in + extensions)
|
||||
@@ -115,9 +115,9 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
|
||||
final settings = ref.read(settingsProvider);
|
||||
final builtIn = _builtInServices.where((s) => s.id == _selectedService).firstOrNull;
|
||||
if (builtIn != null) {
|
||||
// Add MP3 option if enabled in settings
|
||||
if (settings.enableMp3Option) {
|
||||
return [...builtIn.qualityOptions, _mp3QualityOption];
|
||||
// Add Lossy option if enabled in settings
|
||||
if (settings.enableLossyOption) {
|
||||
return [...builtIn.qualityOptions, _lossyQualityOption];
|
||||
}
|
||||
return builtIn.qualityOptions;
|
||||
}
|
||||
@@ -125,9 +125,9 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
|
||||
final extensionState = ref.read(extensionProvider);
|
||||
final ext = extensionState.extensions.where((e) => e.id == _selectedService).firstOrNull;
|
||||
if (ext != null && ext.qualityOptions.isNotEmpty) {
|
||||
// Add MP3 option for extensions too if enabled
|
||||
if (settings.enableMp3Option) {
|
||||
return [...ext.qualityOptions, _mp3QualityOption];
|
||||
// Add Lossy option for extensions too if enabled
|
||||
if (settings.enableLossyOption) {
|
||||
return [...ext.qualityOptions, _lossyQualityOption];
|
||||
}
|
||||
return ext.qualityOptions;
|
||||
}
|
||||
@@ -136,8 +136,8 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
|
||||
final defaultOptions = [
|
||||
const QualityOption(id: 'DEFAULT', label: 'Default Quality', description: 'Best available'),
|
||||
];
|
||||
if (settings.enableMp3Option) {
|
||||
return [...defaultOptions, _mp3QualityOption];
|
||||
if (settings.enableLossyOption) {
|
||||
return [...defaultOptions, _lossyQualityOption];
|
||||
}
|
||||
return defaultOptions;
|
||||
}
|
||||
@@ -259,6 +259,7 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
|
||||
return Icons.music_note;
|
||||
case 'MP3_320':
|
||||
case 'MP3':
|
||||
case 'LOSSY':
|
||||
return Icons.audiotrack;
|
||||
case 'OPUS':
|
||||
case 'OPUS_128':
|
||||
|
||||
@@ -297,6 +297,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
ffmpeg_kit_flutter_new_audio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ffmpeg_kit_flutter_new_audio
|
||||
sha256: "0a698b46cd163c8e9917af75325c84d27871a2a8b2c37de3b40486cd0ab662ae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
ffmpeg_kit_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffmpeg_kit_flutter_platform_interface
|
||||
sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
+2
-2
@@ -59,8 +59,8 @@ dependencies:
|
||||
receive_sharing_intent: ^1.8.1
|
||||
logger: ^2.5.0
|
||||
|
||||
# FFmpeg - using local custom AAR (arm64-v8a + armeabi-v7a only)
|
||||
# ffmpeg_kit_flutter_new_audio: ^2.0.0 # Replaced with local AAR
|
||||
# FFmpeg for audio conversion
|
||||
ffmpeg_kit_flutter_new_audio: ^2.0.0
|
||||
open_filex: ^4.7.0
|
||||
|
||||
# Notifications
|
||||
|
||||
Reference in New Issue
Block a user