feat: native M4A ReplayGain tag writing and SAF picker error handling

This commit is contained in:
zarzet
2026-04-13 05:01:02 +07:00
parent 5d160f71f1
commit bc4b5a5b17
6 changed files with 367 additions and 17 deletions
+18 -1
View File
@@ -3910,11 +3910,14 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
// ── ReplayGain (MP3/Opus: scan before FFmpeg, add to metadata) ─
ReplayGainResult? scannedReplayGain;
// ── ReplayGain (MP3/Opus/M4A: scan before FFmpeg, add to metadata) ─
if (settings.embedReplayGain && !isFlac) {
try {
final rgResult = await FFmpegService.scanReplayGain(filePath);
if (rgResult != null) {
scannedReplayGain = rgResult;
metadata['REPLAYGAIN_TRACK_GAIN'] = rgResult.trackGain;
metadata['REPLAYGAIN_TRACK_PEAK'] = rgResult.trackPeak;
_log.d(
@@ -3967,6 +3970,20 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
_log.w('FFmpeg $format metadata embed failed');
}
if (isM4a && settings.embedReplayGain && scannedReplayGain != null) {
try {
await PlatformBridge.editFileMetadata(filePath, {
'replaygain_track_gain': scannedReplayGain.trackGain,
'replaygain_track_peak': scannedReplayGain.trackPeak,
});
_log.d(
'ReplayGain compatibility tags written for $format: gain=${scannedReplayGain.trackGain}, peak=${scannedReplayGain.trackPeak}',
);
} catch (e) {
_log.w('Failed to write native ReplayGain tags for $format: $e');
}
}
// ── FLAC post-processing ────────────────────────────────────────
if (isFlac) {
if (settings.artistTagMode == artistTagModeSplitVorbis) {
+18 -1
View File
@@ -10,6 +10,9 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/utils/file_access.dart';
import 'package:spotiflac_android/utils/logger.dart';
final _log = AppLogger('SetupScreen');
class SetupScreen extends ConsumerStatefulWidget {
const SetupScreen({super.key});
@@ -233,7 +236,21 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
if (Platform.isIOS) {
await _showIOSDirectoryOptions();
} else if (Platform.isAndroid) {
final result = await PlatformBridge.pickSafTree();
Map<String, dynamic>? result;
try {
result = await PlatformBridge.pickSafTree();
} catch (e) {
_log.w('Failed to open Android SAF picker: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
context.l10n.snackbarCannotOpenFile(e.toString()),
),
),
);
}
}
if (result != null) {
final treeUri = result['tree_uri'] as String? ?? '';
final displayName = result['display_name'] as String? ?? '';
-13
View File
@@ -2169,19 +2169,6 @@ class FFmpegService {
case 'UNSYNCEDLYRICS':
m4aMap['lyrics'] = value;
break;
// ReplayGain as iTunes freeform atoms (com.apple.iTunes:replaygain_*)
case 'REPLAYGAINTRACKGAIN':
m4aMap['REPLAYGAIN_TRACK_GAIN'] = value;
break;
case 'REPLAYGAINTRACKPEAK':
m4aMap['REPLAYGAIN_TRACK_PEAK'] = value;
break;
case 'REPLAYGAINALBUMGAIN':
m4aMap['REPLAYGAIN_ALBUM_GAIN'] = value;
break;
case 'REPLAYGAINALBUMPEAK':
m4aMap['REPLAYGAIN_ALBUM_PEAK'] = value;
break;
}
}