mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 21:29:36 +02:00
fix: force native FLAC muxer when decrypting to .flac output
Downloads from providers that stream FLAC inside an fMP4 container (e.g. Amazon Music) were being written to disk with a .flac extension while the payload still carried ISO-BMFF atoms. The container-conversion guard then saw codec=flac and skipped the remux, leaving native FLAC tag writers to fail with 'fLaC head incorrect'. Force '-f flac' on the decryption command whenever the target extension is .flac so FFmpeg emits a real FLAC stream, and add an 'fLaC' magic-byte probe on both the Dart and Kotlin container-conversion guards so a FLAC-in-MP4 source is remuxed rather than silently passed through as a tag-writer hazard.
This commit is contained in:
1 parent
4b7146afe4
commit
8f5c59683a
3 files changed
+80
-8
No files matched your search
@@ -6160,9 +6160,13 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
String? flacPath;
|
||||
try {
|
||||
final codec = await FFmpegService.probePrimaryAudioCodec(tempPath);
|
||||
if (!FFmpegService.isLosslessAudioCodec(codec) || codec == 'flac') {
|
||||
final isAlreadyNativeFlac =
|
||||
codec == 'flac' && await FFmpegService.isNativeFlacFile(tempPath);
|
||||
if (!FFmpegService.isLosslessAudioCodec(codec) || isAlreadyNativeFlac) {
|
||||
_log.d(
|
||||
'Preserving native container; audio codec is ${codec ?? 'unknown'}, not a lossless source needing FLAC conversion.',
|
||||
'Preserving native container; audio codec is ${codec ?? 'unknown'}'
|
||||
'${isAlreadyNativeFlac ? ' (native FLAC)' : ''}, '
|
||||
'no FLAC container conversion needed.',
|
||||
);
|
||||
return filePath;
|
||||
}
|
||||
@@ -6203,9 +6207,13 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
|
||||
final codec = await FFmpegService.probePrimaryAudioCodec(filePath);
|
||||
if (!FFmpegService.isLosslessAudioCodec(codec) || codec == 'flac') {
|
||||
final isAlreadyNativeFlac =
|
||||
codec == 'flac' && await FFmpegService.isNativeFlacFile(filePath);
|
||||
if (!FFmpegService.isLosslessAudioCodec(codec) || isAlreadyNativeFlac) {
|
||||
_log.d(
|
||||
'Preserving native container; audio codec is ${codec ?? 'unknown'}, not a lossless source needing FLAC conversion.',
|
||||
'Preserving native container; audio codec is ${codec ?? 'unknown'}'
|
||||
'${isAlreadyNativeFlac ? ' (native FLAC)' : ''}, '
|
||||
'no FLAC container conversion needed.',
|
||||
);
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -283,6 +283,28 @@ class FFmpegService {
|
||||
}.contains(normalized);
|
||||
}
|
||||
|
||||
/// Returns `true` when [filePath] starts with the native FLAC magic bytes
|
||||
/// (`fLaC`). Useful to distinguish a real FLAC file from a FLAC-in-MP4
|
||||
/// container that carries a `.flac` extension or claims codec=flac.
|
||||
static Future<bool> isNativeFlacFile(String filePath) async {
|
||||
try {
|
||||
final raf = await File(filePath).open();
|
||||
try {
|
||||
final header = await raf.read(4);
|
||||
return header.length == 4 &&
|
||||
header[0] == 0x66 && // 'f'
|
||||
header[1] == 0x4C && // 'L'
|
||||
header[2] == 0x61 && // 'a'
|
||||
header[3] == 0x43; // 'C'
|
||||
} finally {
|
||||
await raf.close();
|
||||
}
|
||||
} catch (e) {
|
||||
_log.w('Native FLAC magic probe failed for $filePath: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> convertM4aToFlac(String inputPath) async {
|
||||
final outputPath = _buildOutputPath(inputPath, '.flac');
|
||||
|
||||
@@ -435,7 +457,16 @@ class FFmpegService {
|
||||
// Force MOV demuxer: -decryption_key is only supported by the MOV/MP4
|
||||
// demuxer. The input may carry a .flac extension (SAF mode) while actually
|
||||
// containing an encrypted M4A stream, so we must override auto-detection.
|
||||
return '-v error -decryption_key "$key" -f $demuxerFormat -i "$inputPath" $audioMap-c copy "$outputPath" -y';
|
||||
//
|
||||
// When the requested output is a native .flac we also force the flac
|
||||
// muxer (-f flac). Without it, FFmpeg infers the muxer from the output
|
||||
// extension AND keeps the input container's stream layout, which for
|
||||
// FLAC-in-MP4 sources would still emit an ISO-BMFF payload under a
|
||||
// .flac filename. That file fails native FLAC tag writers later on.
|
||||
final muxerOverride = outputPath.toLowerCase().endsWith('.flac')
|
||||
? '-f flac '
|
||||
: '';
|
||||
return '-v error -decryption_key "$key" -f $demuxerFormat -i "$inputPath" $audioMap-c copy $muxerOverride"$outputPath" -y';
|
||||
}
|
||||
|
||||
final keyCandidates = _buildDecryptionKeyCandidates(decryptionKey);
|
||||
|
||||
Reference in new issue
Block a user