mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 13:19:44 +02:00
feat(metadata): remove ReplayGain from individual and selected tracks
This commit is contained in:
1 parent
c24132cb9b
commit
9d530cea9e
19 files changed
+835
-171
No files matched your search
@@ -536,7 +536,7 @@ Future<void> _performBatchConversion(
|
||||
}
|
||||
}
|
||||
|
||||
/// Batch-scans loudness and writes ReplayGain tags to [selectedItems].
|
||||
/// Adds or removes ReplayGain tags for [selectedItems].
|
||||
///
|
||||
/// [onConfirmOpen] / [onConfirmClosed] let the caller hide and restore any
|
||||
/// selection UI around the confirmation dialog; [onConfirmClosed] receives
|
||||
@@ -545,6 +545,7 @@ Future<void> runBatchReplayGain(
|
||||
BuildContext context,
|
||||
List<UnifiedLibraryItem> selectedItems, {
|
||||
required VoidCallback onExitSelectionMode,
|
||||
bool remove = false,
|
||||
VoidCallback? onConfirmOpen,
|
||||
Future<void> Function(bool confirmed)? onConfirmClosed,
|
||||
}) async {
|
||||
@@ -555,9 +556,15 @@ Future<void> runBatchReplayGain(
|
||||
final confirmed = await showAppDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AppAlertDialog(
|
||||
title: Text(ctx.l10n.replayGainBatchConfirmTitle),
|
||||
title: Text(
|
||||
remove
|
||||
? ctx.l10n.trackRemoveReplayGain
|
||||
: ctx.l10n.replayGainBatchConfirmTitle,
|
||||
),
|
||||
content: Text(
|
||||
ctx.l10n.replayGainBatchConfirmMessage(selectedItems.length),
|
||||
remove
|
||||
? ctx.l10n.replayGainRemoveConfirmMessage(selectedItems.length)
|
||||
: ctx.l10n.replayGainBatchConfirmMessage(selectedItems.length),
|
||||
),
|
||||
actions: [
|
||||
AppDialogAction(
|
||||
@@ -568,7 +575,11 @@ Future<void> runBatchReplayGain(
|
||||
filled: true,
|
||||
isDefault: true,
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: Text(ctx.l10n.replayGainBatchConfirmTitle),
|
||||
child: Text(
|
||||
remove
|
||||
? ctx.l10n.trackRemoveReplayGain
|
||||
: ctx.l10n.replayGainBatchConfirmTitle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -585,7 +596,9 @@ Future<void> runBatchReplayGain(
|
||||
|
||||
BatchProgressDialog.show(
|
||||
context: context,
|
||||
title: context.l10n.replayGainBatchAnalyzing,
|
||||
title: remove
|
||||
? context.l10n.replayGainRemoving
|
||||
: context.l10n.replayGainBatchAnalyzing,
|
||||
total: total,
|
||||
icon: Icons.graphic_eq,
|
||||
onCancel: () {
|
||||
@@ -599,10 +612,12 @@ Future<void> runBatchReplayGain(
|
||||
final item = selectedItems[i];
|
||||
BatchProgressDialog.update(current: i + 1, detail: item.trackName);
|
||||
try {
|
||||
final ok = await ReplayGainService.applyToFile(
|
||||
item.filePath,
|
||||
onUnsupportedDecoder: () => unsupportedDecoder = true,
|
||||
);
|
||||
final ok = remove
|
||||
? await ReplayGainService.removeFromFile(item.filePath)
|
||||
: await ReplayGainService.applyToFile(
|
||||
item.filePath,
|
||||
onUnsupportedDecoder: () => unsupportedDecoder = true,
|
||||
);
|
||||
if (ok) successCount++;
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -618,7 +633,9 @@ Future<void> runBatchReplayGain(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
[
|
||||
context.l10n.replayGainBatchSuccess(successCount, total),
|
||||
remove
|
||||
? context.l10n.replayGainRemoveBatchSuccess(successCount, total)
|
||||
: context.l10n.replayGainBatchSuccess(successCount, total),
|
||||
if (unsupportedDecoder) context.l10n.replayGainUnsupportedDecoder,
|
||||
].join('\n'),
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/logger.dart';
|
||||
|
||||
/// Standalone ReplayGain (re)scanning for existing audio files.
|
||||
/// ReplayGain scanning and tag removal for existing audio files.
|
||||
///
|
||||
/// Computes EBU R128 loudness via FFmpeg and writes gain tags using the native
|
||||
/// metadata editors where supported. Opus uses R128_* rather than legacy tags.
|
||||
@@ -104,6 +104,33 @@ class ReplayGainService {
|
||||
(path) => _writeLocalTags(path, gain, peak, album: true),
|
||||
);
|
||||
|
||||
/// Removes track/album tags, including Opus R128 and M4A Sound Check tags.
|
||||
/// Native editors copy the audio payload unchanged and publish atomically.
|
||||
static Future<bool> removeFromFile(String filePath) =>
|
||||
_updateFile(filePath, (path) async {
|
||||
if (!_isNativeWritableFormat(path)) return false;
|
||||
const fields = {
|
||||
'replaygain_track_gain': '',
|
||||
'replaygain_track_peak': '',
|
||||
'replaygain_album_gain': '',
|
||||
'replaygain_album_peak': '',
|
||||
};
|
||||
final result = await PlatformBridge.editFileMetadata(path, fields);
|
||||
final method = result['method'];
|
||||
if (result['success'] != true ||
|
||||
result['error'] != null ||
|
||||
method is! String ||
|
||||
!(method == 'native' || method.startsWith('native_'))) {
|
||||
return false;
|
||||
}
|
||||
final metadata = await PlatformBridge.readFileMetadata(path);
|
||||
return metadata['error'] == null &&
|
||||
metadata['audio_codec'] != null &&
|
||||
fields.keys.every(
|
||||
(key) => (metadata[key]?.toString() ?? '').trim().isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
static Future<bool> _writeLocalTags(
|
||||
String path,
|
||||
String gain,
|
||||
@@ -182,7 +209,7 @@ class ReplayGainService {
|
||||
if (isSaf) {
|
||||
safTempPath = await PlatformBridge.copyContentUriToTemp(filePath);
|
||||
if (safTempPath == null || safTempPath.isEmpty) {
|
||||
_log.w('Failed to copy SAF file to temp for ReplayGain scan');
|
||||
_log.w('Failed to copy SAF file to temp for ReplayGain update');
|
||||
return false;
|
||||
}
|
||||
workingPath = safTempPath;
|
||||
@@ -199,10 +226,10 @@ class ReplayGainService {
|
||||
}
|
||||
|
||||
refreshPlaybackNormalization(filePath);
|
||||
_log.i('ReplayGain tags written and verified: $filePath');
|
||||
_log.i('ReplayGain tags updated and verified: $filePath');
|
||||
return true;
|
||||
} catch (e) {
|
||||
_log.e('Failed to apply ReplayGain', e);
|
||||
_log.e('Failed to update ReplayGain', e);
|
||||
return false;
|
||||
} finally {
|
||||
if (safTempPath != null) {
|
||||
|
||||
Reference in new issue
Block a user