mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-13 13:29:09 +02:00
fix(download): publish converted SAF files with the final extension
This commit is contained in:
@@ -1002,13 +1002,14 @@ class _DownloadRun {
|
||||
(localFile.uri.pathSegments.isEmpty
|
||||
? 'track$safOutputExt'
|
||||
: localFile.uri.pathSegments.last);
|
||||
final localExt = n._downloadResultOutputExt(result, filePath: localPath);
|
||||
if (localExt != null && localExt.isNotEmpty) {
|
||||
finalName = finalName.replaceFirst(RegExp(r'\.[^.]+$'), localExt);
|
||||
if (!finalName.toLowerCase().endsWith(localExt.toLowerCase())) {
|
||||
finalName = '$finalName$localExt';
|
||||
}
|
||||
}
|
||||
finalName = finalizedAudioFileName(
|
||||
fileName: finalName,
|
||||
localPath: localPath,
|
||||
fallbackExtension:
|
||||
n._downloadResultOutputExt(result, filePath: localPath) ??
|
||||
safOutputExt,
|
||||
);
|
||||
final localExt = finalName.substring(finalName.lastIndexOf('.'));
|
||||
|
||||
final measured = probedFinalMetadata;
|
||||
final qualityLabel = buildQualityVariantFilenameLabel(
|
||||
@@ -1039,7 +1040,11 @@ class _DownloadRun {
|
||||
String? publishedName;
|
||||
var alreadyExists = false;
|
||||
if (item.preserveQualityVariant && qualityVariantCollisionOnly) {
|
||||
final logicalName = safFileName ?? finalName;
|
||||
final logicalName = finalizedAudioFileName(
|
||||
fileName: safFileName ?? finalName,
|
||||
localPath: localPath,
|
||||
fallbackExtension: localExt,
|
||||
);
|
||||
final stagingLabel = qualityVariantStagingLabel(item.id);
|
||||
final cleanName = removeQualityVariantStagingLabel(
|
||||
fileName: logicalName,
|
||||
@@ -1057,7 +1062,7 @@ class _DownloadRun {
|
||||
relativeDir: effectiveOutputDir,
|
||||
cleanFileName: cleanName,
|
||||
variantFileName: variantName,
|
||||
mimeType: n._mimeTypeForExt(localExt ?? safOutputExt),
|
||||
mimeType: n._mimeTypeForExt(localExt),
|
||||
srcPath: localPath,
|
||||
preservedSuffix: qualityLabel ?? '',
|
||||
);
|
||||
@@ -1068,7 +1073,7 @@ class _DownloadRun {
|
||||
treeUri: settings.downloadTreeUri,
|
||||
relativeDir: effectiveOutputDir,
|
||||
fileName: finalName,
|
||||
mimeType: n._mimeTypeForExt(localExt ?? safOutputExt),
|
||||
mimeType: n._mimeTypeForExt(localExt),
|
||||
srcPath: localPath,
|
||||
preservedSuffix: qualityLabel ?? '',
|
||||
);
|
||||
@@ -1079,7 +1084,7 @@ class _DownloadRun {
|
||||
treeUri: settings.downloadTreeUri,
|
||||
relativeDir: effectiveOutputDir,
|
||||
fileName: finalName,
|
||||
mimeType: n._mimeTypeForExt(localExt ?? safOutputExt),
|
||||
mimeType: n._mimeTypeForExt(localExt),
|
||||
srcPath: localPath,
|
||||
);
|
||||
publishedUri = published?.uri;
|
||||
|
||||
@@ -134,6 +134,23 @@ String isoBmffAudioExtensionForCodec(String? codec) {
|
||||
return normalizeAudioFormatValue(codec) == 'ac4' ? '.mp4' : '.m4a';
|
||||
}
|
||||
|
||||
/// Uses the finalized local container after conversion, before publishing to
|
||||
/// SAF. Provider output fields still describe the original download here.
|
||||
String finalizedAudioFileName({
|
||||
required String fileName,
|
||||
required String localPath,
|
||||
required String fallbackExtension,
|
||||
}) {
|
||||
final extension =
|
||||
RegExp(
|
||||
r'\.(flac|m4a|mp4|mp3|opus|ogg|aac|wav|aiff)$',
|
||||
caseSensitive: false,
|
||||
).firstMatch(localPath)?.group(0)?.toLowerCase() ??
|
||||
fallbackExtension;
|
||||
final stem = fileName.replaceFirst(RegExp(r'\.[^.]+$'), '');
|
||||
return '$stem$extension';
|
||||
}
|
||||
|
||||
/// Resolves the actual audio codec reported by native metadata probing, while
|
||||
/// falling back to the container format when the codec is absent or generic.
|
||||
///
|
||||
|
||||
@@ -23,6 +23,47 @@ import 'package:spotiflac_android/utils/path_match_keys.dart';
|
||||
import 'package:spotiflac_android/utils/string_utils.dart';
|
||||
|
||||
void main() {
|
||||
group('Finalized SAF audio names', () {
|
||||
for (final extension in ['mp3', 'opus', 'flac', 'ogg', 'm4a', 'mp4']) {
|
||||
test(
|
||||
'publishes the final $extension container instead of source M4A',
|
||||
() {
|
||||
expect(
|
||||
finalizedAudioFileName(
|
||||
fileName: 'Example song.m4a',
|
||||
localPath: '/cache/final_audio.$extension',
|
||||
fallbackExtension: '.m4a',
|
||||
),
|
||||
'Example song.$extension',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
test('preserves variant labels and normalizes uppercase suffixes', () {
|
||||
expect(
|
||||
finalizedAudioFileName(
|
||||
fileName: 'Song [OPUS 256kbps].m4a',
|
||||
localPath: '/cache/final.OPUS',
|
||||
fallbackExtension: '.m4a',
|
||||
),
|
||||
'Song [OPUS 256kbps].opus',
|
||||
);
|
||||
});
|
||||
test(
|
||||
'uses the reported container when the temporary path has no suffix',
|
||||
() {
|
||||
expect(
|
||||
finalizedAudioFileName(
|
||||
fileName: 'Song',
|
||||
localPath: '/cache/final_audio',
|
||||
fallbackExtension: '.m4a',
|
||||
),
|
||||
'Song.m4a',
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('file deletion', () {
|
||||
test('confirms a local file is absent before reporting success', () async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
|
||||
Reference in New Issue
Block a user