mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-07-29 07:18:49 +02:00
feat(convert): optionally retain original files
This commit is contained in:
@@ -4238,6 +4238,18 @@ abstract class AppLocalizations {
|
||||
/// **'Bitrate'**
|
||||
String get trackConvertBitrate;
|
||||
|
||||
/// Toggle to preserve the source file during conversion
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Keep original file'**
|
||||
String get trackConvertKeepOriginal;
|
||||
|
||||
/// Description for preserving the source file during conversion
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add the converted file as a separate library entry'**
|
||||
String get trackConvertKeepOriginalDescription;
|
||||
|
||||
/// Confirmation dialog title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -4263,6 +4275,15 @@ abstract class AppLocalizations {
|
||||
String targetFormat,
|
||||
);
|
||||
|
||||
/// Confirmation message when the source file will be preserved
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Convert from {sourceFormat} to {targetFormat}?\n\nThe original file will be kept and the converted file will be added as a separate library entry.'**
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
);
|
||||
|
||||
/// Hint shown when converting between lossless formats
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -4651,6 +4672,12 @@ abstract class AppLocalizations {
|
||||
/// **'Convert {count} {count, plural, =1{track} other{tracks}} to {format}? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.'**
|
||||
String selectionBatchConvertConfirmMessageLossless(int count, String format);
|
||||
|
||||
/// Batch conversion confirmation when source files will be preserved
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Convert {count} {count, plural, =1{track} other{tracks}} to {format}?\n\nOriginal files will be kept and converted files will be added as separate library entries.'**
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format);
|
||||
|
||||
/// Snackbar after batch conversion completes
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2408,6 +2408,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Konvertierung bestätigen';
|
||||
|
||||
@@ -2428,6 +2435,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return 'Konvertieren von $sourceFormat in $targetFormat? (kein Qualitätsverlust)\n\nDie Originaldatei wird nach der Konvertierung gelöscht.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Verlustfreie Konvertierung kein Qualitätsverlust';
|
||||
@@ -2710,6 +2725,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return 'Konvertiere $count $_temp0 in $format? (kein Qualitätsverlust)\n\nOriginaldateien werden nach der Konvertierung gelöscht.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return '$success von $total Titeln in $format konvertiert';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2439,6 +2439,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Débit binaire';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirmer la conversion';
|
||||
|
||||
@@ -2459,6 +2466,14 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return 'Convertir de $sourceFormat vers $targetFormat ? (Sans perte — aucune perte de qualité)\n\nLe fichier d\'origine sera supprimé après la conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Conversion sans perte — aucune perte de qualité';
|
||||
@@ -2746,6 +2761,17 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return 'Convertir $count $_temp0 au format $format ? (Sans perte — aucune perte de qualité)\n\nLes fichiers d\'origine seront supprimés après la conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return '$success pistes sur $total ont été converties au format $format';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2388,6 +2388,13 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Pertahankan file asli';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Tambahkan file hasil konversi sebagai entri library terpisah';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2408,6 +2415,14 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Konversi dari $sourceFormat ke $targetFormat?\n\nFile asli akan dipertahankan dan file hasil konversi akan ditambahkan sebagai entri library terpisah.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2687,6 +2702,11 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
return 'Konversi $count lagu ke $format?\n\nFile asli akan dipertahankan dan file hasil konversi akan ditambahkan sebagai entri library terpisah.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2368,6 +2368,13 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'ビットレート';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => '変換を確認';
|
||||
|
||||
@@ -2388,6 +2395,14 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2667,6 +2682,17 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2315,6 +2315,13 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => '비트레이트';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => '변환 확인';
|
||||
|
||||
@@ -2335,6 +2342,14 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
return '$sourceFormat에서 $targetFormat으로 변환하시겠습니까? (무손실 — 음질 손실 없음)\n\n변환 후에 원본 파일이 삭제됩니다';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint => '무손실 변환 — 음질 손실 없음';
|
||||
|
||||
@@ -2611,6 +2626,17 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
return '$count 개의 $_temp0을 $format으로 변환하시겠습니까? (무손실 — 음질 손실 없음)\n\n변환 후 원본 파일이 삭제됩니다';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return '$total 개 중 $success 개를 $format로 변환 완료';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2409,6 +2409,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Битрейт';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Подтвердить конвертацию';
|
||||
|
||||
@@ -2429,6 +2436,14 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint => 'Конвертация без потери качества';
|
||||
|
||||
@@ -2708,6 +2723,17 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
return 'Конвертировать $count $_temp0 в $format? (Без потери качества)\n\nОригинальные файлы будут удалены после конвертации.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Конвертировано $success треков $total в $format';
|
||||
|
||||
@@ -2405,6 +2405,13 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Dönüştürmeyi Onayla';
|
||||
|
||||
@@ -2425,6 +2432,14 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
return '$sourceFormat formatından $targetFormat formatına dönüştürmek ister misiniz? (Kayıpsız — kalite kaybı yok)\n\nDönüştürme işleminden sonra orijinal dosya silinecektir.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Kayıpsız dönüştürme — kalite kaybı yok';
|
||||
@@ -2705,6 +2720,17 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -2413,6 +2413,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Бітрейт';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Підтвердити конверсію';
|
||||
|
||||
@@ -2433,6 +2440,14 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
return 'Конвертувати з $sourceFormat у $targetFormat? (Lossless — без втрати якості)\n\nОригінальний файл буде видалено після конвертації.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless конвертація — без втрати якості';
|
||||
@@ -2716,6 +2731,17 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
return 'Конвертувати $count $_temp0 у $format? (Lossless — без втрати якості)\n\nОригінальні файли будуть видалені після конвертації.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Конвертовано $success з $total треків у $format';
|
||||
|
||||
@@ -2379,6 +2379,13 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get trackConvertBitrate => 'Bitrate';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginal => 'Keep original file';
|
||||
|
||||
@override
|
||||
String get trackConvertKeepOriginalDescription =>
|
||||
'Add the converted file as a separate library entry';
|
||||
|
||||
@override
|
||||
String get trackConvertConfirmTitle => 'Confirm Conversion';
|
||||
|
||||
@@ -2399,6 +2406,14 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return 'Convert from $sourceFormat to $targetFormat? (Lossless — no quality loss)\n\nThe original file will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String trackConvertConfirmKeepOriginal(
|
||||
String sourceFormat,
|
||||
String targetFormat,
|
||||
) {
|
||||
return 'Convert from $sourceFormat to $targetFormat?\n\nThe original file will be kept and the converted file will be added as a separate library entry.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackConvertLosslessHint =>
|
||||
'Lossless conversion — no quality loss';
|
||||
@@ -2678,6 +2693,17 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return 'Convert $count $_temp0 to $format? (Lossless — no quality loss)\n\nOriginal files will be deleted after conversion.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertConfirmKeepOriginal(int count, String format) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'tracks',
|
||||
one: 'track',
|
||||
);
|
||||
return 'Convert $count $_temp0 to $format?\n\nOriginal files will be kept and converted files will be added as separate library entries.';
|
||||
}
|
||||
|
||||
@override
|
||||
String selectionBatchConvertSuccess(int success, int total, String format) {
|
||||
return 'Converted $success of $total tracks to $format';
|
||||
|
||||
@@ -3140,6 +3140,14 @@
|
||||
"@trackConvertBitrate": {
|
||||
"description": "Label for bitrate selection"
|
||||
},
|
||||
"trackConvertKeepOriginal": "Keep original file",
|
||||
"@trackConvertKeepOriginal": {
|
||||
"description": "Toggle to preserve the source file during conversion"
|
||||
},
|
||||
"trackConvertKeepOriginalDescription": "Add the converted file as a separate library entry",
|
||||
"@trackConvertKeepOriginalDescription": {
|
||||
"description": "Description for preserving the source file during conversion"
|
||||
},
|
||||
"trackConvertConfirmTitle": "Confirm Conversion",
|
||||
"@trackConvertConfirmTitle": {
|
||||
"description": "Confirmation dialog title"
|
||||
@@ -3171,6 +3179,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"trackConvertConfirmKeepOriginal": "Convert from {sourceFormat} to {targetFormat}?\n\nThe original file will be kept and the converted file will be added as a separate library entry.",
|
||||
"@trackConvertConfirmKeepOriginal": {
|
||||
"description": "Confirmation message when the source file will be preserved",
|
||||
"placeholders": {
|
||||
"sourceFormat": {
|
||||
"type": "String"
|
||||
},
|
||||
"targetFormat": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"trackConvertLosslessHint": "Lossless conversion — no quality loss",
|
||||
"@trackConvertLosslessHint": {
|
||||
"description": "Hint shown when converting between lossless formats"
|
||||
@@ -3547,6 +3567,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectionBatchConvertConfirmKeepOriginal": "Convert {count} {count, plural, =1{track} other{tracks}} to {format}?\n\nOriginal files will be kept and converted files will be added as separate library entries.",
|
||||
"@selectionBatchConvertConfirmKeepOriginal": {
|
||||
"description": "Batch conversion confirmation when source files will be preserved",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
},
|
||||
"format": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectionBatchConvertSuccess": "Converted {success} of {total} tracks to {format}",
|
||||
"@selectionBatchConvertSuccess": {
|
||||
"description": "Snackbar after batch conversion completes",
|
||||
|
||||
+33
-1
@@ -5883,5 +5883,37 @@
|
||||
"extensionsSearchProviderDescription": "Pilih layanan yang digunakan untuk mencari lagu",
|
||||
"albumFolderAlbumOnly": "Album Saja",
|
||||
"nowPlayingOpenInExternalPlayer": "Open in external player",
|
||||
"cueSplitButton": "Split into Tracks"
|
||||
"cueSplitButton": "Split into Tracks",
|
||||
"trackConvertKeepOriginal": "Pertahankan file asli",
|
||||
"@trackConvertKeepOriginal": {
|
||||
"description": "Toggle untuk mempertahankan file sumber saat konversi"
|
||||
},
|
||||
"trackConvertKeepOriginalDescription": "Tambahkan file hasil konversi sebagai entri library terpisah",
|
||||
"@trackConvertKeepOriginalDescription": {
|
||||
"description": "Deskripsi opsi mempertahankan file sumber saat konversi"
|
||||
},
|
||||
"trackConvertConfirmKeepOriginal": "Konversi dari {sourceFormat} ke {targetFormat}?\n\nFile asli akan dipertahankan dan file hasil konversi akan ditambahkan sebagai entri library terpisah.",
|
||||
"@trackConvertConfirmKeepOriginal": {
|
||||
"description": "Konfirmasi konversi ketika file sumber dipertahankan",
|
||||
"placeholders": {
|
||||
"sourceFormat": {
|
||||
"type": "String"
|
||||
},
|
||||
"targetFormat": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectionBatchConvertConfirmKeepOriginal": "Konversi {count} lagu ke {format}?\n\nFile asli akan dipertahankan dan file hasil konversi akan ditambahkan sebagai entri library terpisah.",
|
||||
"@selectionBatchConvertConfirmKeepOriginal": {
|
||||
"description": "Konfirmasi konversi batch ketika file sumber dipertahankan",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
},
|
||||
"format": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,17 +263,25 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
bitrate,
|
||||
);
|
||||
},
|
||||
onConvert: (format, bitrate, losslessQuality, losslessProcessing) {
|
||||
Navigator.pop(sheetContext);
|
||||
_confirmAndConvert(
|
||||
context: this.context,
|
||||
sourceFormat: currentFormat,
|
||||
targetFormat: format,
|
||||
bitrate: bitrate,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
);
|
||||
},
|
||||
onConvert:
|
||||
(
|
||||
format,
|
||||
bitrate,
|
||||
losslessQuality,
|
||||
losslessProcessing,
|
||||
keepOriginal,
|
||||
) {
|
||||
Navigator.pop(sheetContext);
|
||||
_confirmAndConvert(
|
||||
context: this.context,
|
||||
sourceFormat: currentFormat,
|
||||
targetFormat: format,
|
||||
bitrate: bitrate,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -738,6 +746,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
const LosslessConversionQuality(),
|
||||
LosslessConversionProcessing losslessProcessing =
|
||||
const LosslessConversionProcessing(),
|
||||
bool keepOriginal = false,
|
||||
}) {
|
||||
final isLossless = isLosslessConversionTarget(targetFormat);
|
||||
showDialog<void>(
|
||||
@@ -746,7 +755,12 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
return AlertDialog(
|
||||
title: Text(dialogContext.l10n.trackConvertConfirmTitle),
|
||||
content: Text(
|
||||
isLossless && losslessQuality.hasCaps
|
||||
keepOriginal
|
||||
? dialogContext.l10n.trackConvertConfirmKeepOriginal(
|
||||
sourceFormat,
|
||||
targetFormat,
|
||||
)
|
||||
: isLossless && losslessQuality.hasCaps
|
||||
? dialogContext.l10n.trackConvertConfirmMessageLosslessCapped(
|
||||
sourceFormat,
|
||||
targetFormat,
|
||||
@@ -784,6 +798,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
bitrate: bitrate,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
},
|
||||
child: Text(dialogContext.l10n.trackConvertFormat),
|
||||
@@ -801,6 +816,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
const LosslessConversionQuality(),
|
||||
LosslessConversionProcessing losslessProcessing =
|
||||
const LosslessConversionProcessing(),
|
||||
bool keepOriginal = false,
|
||||
}) async {
|
||||
if (_isConverting) return;
|
||||
_setState(() => _isConverting = true);
|
||||
@@ -874,7 +890,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
metadata: metadata,
|
||||
coverPath: coverPath,
|
||||
artistTagMode: ref.read(settingsProvider).artistTagMode,
|
||||
deleteOriginal: !isSaf,
|
||||
deleteOriginal: !isSaf && !keepOriginal,
|
||||
sourceBitDepth: bitDepth,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
@@ -991,14 +1007,13 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
return;
|
||||
}
|
||||
|
||||
final dotIdx = oldFileName.lastIndexOf('.');
|
||||
final baseName = dotIdx > 0
|
||||
? oldFileName.substring(0, dotIdx)
|
||||
: oldFileName;
|
||||
final convTarget = convertTargetExtAndMime(targetFormat);
|
||||
final newExt = convTarget.ext;
|
||||
final mimeType = convTarget.mime;
|
||||
final newFileName = '$baseName$newExt';
|
||||
final newFileName = convertedOutputFileName(
|
||||
originalFileName: oldFileName,
|
||||
targetFormat: targetFormat,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
|
||||
final safUri = await PlatformBridge.createSafFileFromPath(
|
||||
treeUri: treeUri,
|
||||
@@ -1026,7 +1041,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSameContentUri(cleanFilePath, safUri)) {
|
||||
if (!keepOriginal && !isSameContentUri(cleanFilePath, safUri)) {
|
||||
final deletedOriginal = await PlatformBridge.safDelete(
|
||||
cleanFilePath,
|
||||
).catchError((_) => false);
|
||||
@@ -1038,19 +1053,16 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
}
|
||||
|
||||
if (!_isLocalItem) {
|
||||
await HistoryDatabase.instance.updateFilePath(
|
||||
_downloadItem!.id,
|
||||
safUri,
|
||||
newSafFileName: newFileName,
|
||||
await ConversionLibraryService.persistHistoryConversion(
|
||||
source: _downloadItem!,
|
||||
newFilePath: safUri,
|
||||
newQuality: newQuality,
|
||||
newFormat: normalizedConvertedAudioFormat(targetFormat),
|
||||
newBitrate: convertedAudioBitrateKbps(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
),
|
||||
newBitDepth: convertedBitDepth,
|
||||
newSampleRate: convertedSampleRate,
|
||||
clearAudioSpecs: !isLosslessOutput,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
newSafFileName: newFileName,
|
||||
);
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
} else {
|
||||
@@ -1061,6 +1073,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
await ref.read(localLibraryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
@@ -1075,18 +1088,15 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
}
|
||||
} else {
|
||||
if (!_isLocalItem) {
|
||||
await HistoryDatabase.instance.updateFilePath(
|
||||
_downloadItem!.id,
|
||||
newPath,
|
||||
await ConversionLibraryService.persistHistoryConversion(
|
||||
source: _downloadItem!,
|
||||
newFilePath: newPath,
|
||||
newQuality: newQuality,
|
||||
newFormat: normalizedConvertedAudioFormat(targetFormat),
|
||||
newBitrate: convertedAudioBitrateKbps(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
),
|
||||
newBitDepth: convertedBitDepth,
|
||||
newSampleRate: convertedSampleRate,
|
||||
clearAudioSpecs: !isLosslessOutput,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
await ref.read(downloadHistoryProvider.notifier).reloadFromStorage();
|
||||
} else {
|
||||
@@ -1097,6 +1107,7 @@ extension _TrackMetadataConvertAndCueSplit on _TrackMetadataScreenState {
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
await ref.read(localLibraryProvider.notifier).reloadFromStorage();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:spotiflac_android/services/history_database.dart';
|
||||
import 'package:spotiflac_android/services/conversion_library_service.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
@@ -8,8 +8,8 @@ import 'package:spotiflac_android/models/unified_library_item.dart';
|
||||
import 'package:spotiflac_android/providers/download_history_provider.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
import 'package:spotiflac_android/services/conversion_library_service.dart';
|
||||
import 'package:spotiflac_android/services/ffmpeg_service.dart';
|
||||
import 'package:spotiflac_android/services/history_database.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/services/replaygain_service.dart';
|
||||
@@ -86,20 +86,22 @@ Future<void> showBatchConvertSheet(
|
||||
confirmLabel: sheetConfirmLabel,
|
||||
sourceBitDepth: lowestKnownPositiveInt(sourceBitDepths),
|
||||
sourceSampleRate: lowestKnownPositiveInt(sourceSampleRates),
|
||||
onConvert: (format, bitrate, losslessQuality, losslessProcessing) {
|
||||
didStartConversion = true;
|
||||
Navigator.pop(sheetContext);
|
||||
_performBatchConversion(
|
||||
context,
|
||||
ref,
|
||||
selectedItems: selectedItems,
|
||||
targetFormat: format,
|
||||
bitrate: bitrate,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
onExitSelectionMode: onExitSelectionMode,
|
||||
);
|
||||
},
|
||||
onConvert:
|
||||
(format, bitrate, losslessQuality, losslessProcessing, keepOriginal) {
|
||||
didStartConversion = true;
|
||||
Navigator.pop(sheetContext);
|
||||
_performBatchConversion(
|
||||
context,
|
||||
ref,
|
||||
selectedItems: selectedItems,
|
||||
targetFormat: format,
|
||||
bitrate: bitrate,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
keepOriginal: keepOriginal,
|
||||
onExitSelectionMode: onExitSelectionMode,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -115,6 +117,7 @@ Future<void> _performBatchConversion(
|
||||
LosslessConversionQuality losslessQuality = const LosslessConversionQuality(),
|
||||
LosslessConversionProcessing losslessProcessing =
|
||||
const LosslessConversionProcessing(),
|
||||
bool keepOriginal = false,
|
||||
required VoidCallback onExitSelectionMode,
|
||||
}) async {
|
||||
final convertibleItems = <UnifiedLibraryItem>[];
|
||||
@@ -150,7 +153,12 @@ Future<void> _performBatchConversion(
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(context.l10n.selectionBatchConvertConfirmTitle),
|
||||
content: Text(
|
||||
isLossless && losslessQuality.hasCaps
|
||||
keepOriginal
|
||||
? context.l10n.selectionBatchConvertConfirmKeepOriginal(
|
||||
convertibleItems.length,
|
||||
targetFormat,
|
||||
)
|
||||
: isLossless && losslessQuality.hasCaps
|
||||
? context.l10n.selectionBatchConvertConfirmMessageLosslessCapped(
|
||||
convertibleItems.length,
|
||||
targetFormat,
|
||||
@@ -188,7 +196,6 @@ Future<void> _performBatchConversion(
|
||||
|
||||
int successCount = 0;
|
||||
final total = convertibleItems.length;
|
||||
final historyDb = HistoryDatabase.instance;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final shouldEmbedLyrics =
|
||||
settings.embedLyrics && settings.lyricsMode != 'external';
|
||||
@@ -266,7 +273,7 @@ Future<void> _performBatchConversion(
|
||||
metadata: metadata,
|
||||
coverPath: coverPath,
|
||||
artistTagMode: settings.artistTagMode,
|
||||
deleteOriginal: !isSaf,
|
||||
deleteOriginal: !isSaf && !keepOriginal,
|
||||
sourceBitDepth: item.historyItem?.bitDepth ?? item.localItem?.bitDepth,
|
||||
losslessQuality: losslessQuality,
|
||||
losslessProcessing: losslessProcessing,
|
||||
@@ -326,14 +333,13 @@ Future<void> _performBatchConversion(
|
||||
final relativeDir = hi.safRelativeDir ?? '';
|
||||
if (treeUri != null && treeUri.isNotEmpty) {
|
||||
final oldFileName = hi.safFileName ?? '';
|
||||
final dotIdx = oldFileName.lastIndexOf('.');
|
||||
final baseName = dotIdx > 0
|
||||
? oldFileName.substring(0, dotIdx)
|
||||
: oldFileName;
|
||||
final convTarget = convertTargetExtAndMime(targetFormat);
|
||||
final newExt = convTarget.ext;
|
||||
final mimeType = convTarget.mime;
|
||||
final newFileName = '$baseName$newExt';
|
||||
final newFileName = convertedOutputFileName(
|
||||
originalFileName: oldFileName,
|
||||
targetFormat: targetFormat,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
|
||||
final safUri = await PlatformBridge.createSafFileFromPath(
|
||||
treeUri: treeUri,
|
||||
@@ -355,25 +361,22 @@ Future<void> _performBatchConversion(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isSameContentUri(item.filePath, safUri)) {
|
||||
if (!keepOriginal && !isSameContentUri(item.filePath, safUri)) {
|
||||
try {
|
||||
await PlatformBridge.safDelete(item.filePath);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
await historyDb.updateFilePath(
|
||||
hi.id,
|
||||
safUri,
|
||||
newSafFileName: newFileName,
|
||||
await ConversionLibraryService.persistHistoryConversion(
|
||||
source: hi,
|
||||
newFilePath: safUri,
|
||||
newQuality: newQuality,
|
||||
newFormat: normalizedConvertedAudioFormat(targetFormat),
|
||||
newBitrate: convertedAudioBitrateKbps(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
),
|
||||
newBitDepth: convertedBitDepth,
|
||||
newSampleRate: convertedSampleRate,
|
||||
clearAudioSpecs: !isLosslessOutput,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
newSafFileName: newFileName,
|
||||
);
|
||||
}
|
||||
try {
|
||||
@@ -423,14 +426,13 @@ Future<void> _performBatchConversion(
|
||||
}
|
||||
|
||||
if (treeUri != null && oldFileName.isNotEmpty) {
|
||||
final dotIdx = oldFileName.lastIndexOf('.');
|
||||
final baseName = dotIdx > 0
|
||||
? oldFileName.substring(0, dotIdx)
|
||||
: oldFileName;
|
||||
final convTarget = convertTargetExtAndMime(targetFormat);
|
||||
final newExt = convTarget.ext;
|
||||
final mimeType = convTarget.mime;
|
||||
final newFileName = '$baseName$newExt';
|
||||
final newFileName = convertedOutputFileName(
|
||||
originalFileName: oldFileName,
|
||||
targetFormat: targetFormat,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
|
||||
final safUri = await PlatformBridge.createSafFileFromPath(
|
||||
treeUri: treeUri,
|
||||
@@ -452,7 +454,7 @@ Future<void> _performBatchConversion(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isSameContentUri(item.filePath, safUri)) {
|
||||
if (!keepOriginal && !isSameContentUri(item.filePath, safUri)) {
|
||||
try {
|
||||
await PlatformBridge.safDelete(item.filePath);
|
||||
} catch (_) {}
|
||||
@@ -464,6 +466,7 @@ Future<void> _performBatchConversion(
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -476,18 +479,15 @@ Future<void> _performBatchConversion(
|
||||
} catch (_) {}
|
||||
}
|
||||
} else if (item.historyItem != null) {
|
||||
await historyDb.updateFilePath(
|
||||
item.historyItem!.id,
|
||||
newPath,
|
||||
await ConversionLibraryService.persistHistoryConversion(
|
||||
source: item.historyItem!,
|
||||
newFilePath: newPath,
|
||||
newQuality: newQuality,
|
||||
newFormat: normalizedConvertedAudioFormat(targetFormat),
|
||||
newBitrate: convertedAudioBitrateKbps(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
),
|
||||
newBitDepth: convertedBitDepth,
|
||||
newSampleRate: convertedSampleRate,
|
||||
clearAudioSpecs: !isLosslessOutput,
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
} else if (item.localItem != null) {
|
||||
await LibraryDatabase.instance.replaceWithConvertedItem(
|
||||
@@ -497,6 +497,7 @@ Future<void> _performBatchConversion(
|
||||
bitrate: bitrate,
|
||||
bitDepth: convertedBitDepth,
|
||||
sampleRate: convertedSampleRate,
|
||||
keepOriginal: keepOriginal,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:spotiflac_android/providers/download_history_provider.dart';
|
||||
import 'package:spotiflac_android/services/history_database.dart';
|
||||
import 'package:spotiflac_android/utils/audio_conversion_utils.dart';
|
||||
|
||||
class ConversionLibraryService {
|
||||
const ConversionLibraryService._();
|
||||
|
||||
static Future<void> persistHistoryConversion({
|
||||
required DownloadHistoryItem source,
|
||||
required String newFilePath,
|
||||
required String newQuality,
|
||||
required String targetFormat,
|
||||
required String bitrate,
|
||||
required int? bitDepth,
|
||||
required int? sampleRate,
|
||||
required bool keepOriginal,
|
||||
String? newSafFileName,
|
||||
}) async {
|
||||
final convertedBitrate = convertedAudioBitrateKbps(
|
||||
targetFormat: targetFormat,
|
||||
bitrate: bitrate,
|
||||
);
|
||||
final isLossless = isLosslessConversionTarget(targetFormat);
|
||||
|
||||
if (!keepOriginal) {
|
||||
await HistoryDatabase.instance.updateFilePath(
|
||||
source.id,
|
||||
newFilePath,
|
||||
newSafFileName: newSafFileName,
|
||||
newQuality: newQuality,
|
||||
newFormat: normalizedConvertedAudioFormat(targetFormat),
|
||||
newBitrate: convertedBitrate,
|
||||
newBitDepth: bitDepth,
|
||||
newSampleRate: sampleRate,
|
||||
clearAudioSpecs: !isLossless,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final converted = source.toJson()
|
||||
..['id'] = convertedLibraryItemId(source.id, newFilePath)
|
||||
..['filePath'] = newFilePath
|
||||
..['downloadedAt'] = DateTime.now().toIso8601String()
|
||||
..['quality'] = newQuality
|
||||
..['format'] = normalizedConvertedAudioFormat(targetFormat)
|
||||
..['bitrate'] = convertedBitrate
|
||||
..['bitDepth'] = isLossless ? bitDepth : null
|
||||
..['sampleRate'] = isLossless ? sampleRate : null;
|
||||
if (newSafFileName != null) {
|
||||
converted['safFileName'] = newSafFileName;
|
||||
}
|
||||
await HistoryDatabase.instance.upsert(converted);
|
||||
}
|
||||
}
|
||||
@@ -1783,6 +1783,7 @@ class LibraryDatabase {
|
||||
required String bitrate,
|
||||
int? bitDepth,
|
||||
int? sampleRate,
|
||||
bool keepOriginal = false,
|
||||
}) async {
|
||||
final db = await database;
|
||||
final stat = await fileStat(newFilePath);
|
||||
@@ -1810,16 +1811,18 @@ class LibraryDatabase {
|
||||
}
|
||||
|
||||
await db.transaction((txn) async {
|
||||
await txn.delete(
|
||||
'library_path_keys',
|
||||
where: 'item_id = ?',
|
||||
whereArgs: [item.id],
|
||||
);
|
||||
await txn.delete(
|
||||
'library',
|
||||
where: 'id = ? OR file_path = ?',
|
||||
whereArgs: [item.id, item.filePath],
|
||||
);
|
||||
if (!keepOriginal) {
|
||||
await txn.delete(
|
||||
'library_path_keys',
|
||||
where: 'item_id = ?',
|
||||
whereArgs: [item.id],
|
||||
);
|
||||
await txn.delete(
|
||||
'library',
|
||||
where: 'id = ? OR file_path = ?',
|
||||
whereArgs: [item.id, item.filePath],
|
||||
);
|
||||
}
|
||||
await txn.insert(
|
||||
'library',
|
||||
_jsonToDbRow(updated),
|
||||
|
||||
@@ -332,6 +332,27 @@ String normalizedConvertedAudioFormat(String targetFormat) {
|
||||
}
|
||||
}
|
||||
|
||||
String convertedOutputFileName({
|
||||
required String originalFileName,
|
||||
required String targetFormat,
|
||||
required bool keepOriginal,
|
||||
}) {
|
||||
final dotIndex = originalFileName.lastIndexOf('.');
|
||||
final baseName = dotIndex > 0
|
||||
? originalFileName.substring(0, dotIndex)
|
||||
: originalFileName;
|
||||
final suffix = keepOriginal ? '_converted' : '';
|
||||
return '$baseName$suffix${convertTargetExtAndMime(targetFormat).ext}';
|
||||
}
|
||||
|
||||
String convertedLibraryItemId(String sourceId, String filePath) {
|
||||
var hash = 5381;
|
||||
for (final codeUnit in filePath.codeUnits) {
|
||||
hash = (((hash << 5) + hash) ^ codeUnit) & 0x7fffffff;
|
||||
}
|
||||
return '$sourceId:converted:${hash.toRadixString(16)}';
|
||||
}
|
||||
|
||||
int? convertedAudioBitrateKbps({
|
||||
required String targetFormat,
|
||||
required String bitrate,
|
||||
|
||||
@@ -25,6 +25,7 @@ class BatchConvertSheet extends StatefulWidget {
|
||||
String bitrate,
|
||||
LosslessConversionQuality losslessQuality,
|
||||
LosslessConversionProcessing losslessProcessing,
|
||||
bool keepOriginal,
|
||||
)
|
||||
onConvert;
|
||||
|
||||
@@ -55,6 +56,7 @@ class _BatchConvertSheetState extends State<BatchConvertSheet> {
|
||||
int? _selectedMaxSampleRate;
|
||||
String _selectedDither = 'none';
|
||||
String _selectedResampler = 'swr';
|
||||
bool _keepOriginal = false;
|
||||
|
||||
String _defaultBitrateForFormat(String format) {
|
||||
if (format == 'Opus') return '128k';
|
||||
@@ -84,9 +86,7 @@ class _BatchConvertSheetState extends State<BatchConvertSheet> {
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.viewInsetsOf(context).bottom,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
|
||||
child: DraggableScrollableSheet(
|
||||
initialChildSize: 0.85,
|
||||
minChildSize: 0.5,
|
||||
@@ -370,6 +370,19 @@ class _BatchConvertSheetState extends State<BatchConvertSheet> {
|
||||
),
|
||||
),
|
||||
|
||||
_card(
|
||||
cs,
|
||||
child: SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(context.l10n.trackConvertKeepOriginal),
|
||||
subtitle: Text(
|
||||
context.l10n.trackConvertKeepOriginalDescription,
|
||||
),
|
||||
value: _keepOriginal,
|
||||
onChanged: (value) => setState(() => _keepOriginal = value),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
@@ -385,6 +398,7 @@ class _BatchConvertSheetState extends State<BatchConvertSheet> {
|
||||
dither: _selectedDither,
|
||||
resampler: _selectedResampler,
|
||||
),
|
||||
_keepOriginal,
|
||||
),
|
||||
icon: const Icon(Icons.swap_horiz),
|
||||
style: FilledButton.styleFrom(
|
||||
|
||||
Reference in New Issue
Block a user