refactor(download): simplify finalization and remove the retired worker

This commit is contained in:
zarzet committed 2026-09-30 21:44:31 +07:00
1 parent 3a63918e1f
commit b8e3a4c166
5 files changed
+32 -370

No files matched your search

@@ -1150,7 +1150,10 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
return null;
}
var preserve = false;
String? producedFileName;
final rawFileName =
(result['file_name'] as String?) ?? context.safFileName ?? 'track';
final newFileName =
'${rawFileName.replaceFirst(RegExp(r'\.[^.]+$'), '')}.flac';
final newUri = await _replaceSafFileVia(
uri: filePath,
treeUri: treeUri,
@@ -1176,13 +1179,6 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
'as FLAC and embedding metadata.',
);
await embedFlacMetadata(tempPath);
final rawFileName =
(result['file_name'] as String?) ??
context.safFileName ??
'track';
final baseName = rawFileName.replaceFirst(RegExp(r'\.[^.]+$'), '');
final newFileName = '$baseName.flac';
producedFileName = newFileName;
return (tempPath, newFileName);
}
final flacPath = await FFmpegService.convertM4aToFlac(tempPath);
@@ -1191,13 +1187,6 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
}
addCleanup(flacPath);
await embedFlacMetadata(flacPath);
final rawFileName =
(result['file_name'] as String?) ??
context.safFileName ??
'track';
final baseName = rawFileName.replaceFirst(RegExp(r'\.[^.]+$'), '');
final newFileName = '$baseName.flac';
producedFileName = newFileName;
return (flacPath, newFileName);
},
);
@@ -1207,7 +1196,7 @@ extension _DownloadQueueFinalization on DownloadQueueNotifier {
if (newUri == null) {
return null;
}
result['file_name'] = producedFileName;
result['file_name'] = newFileName;
markFinalOutputAsFlac();
return newUri;
}
@@ -5,13 +5,6 @@ part of 'download_queue_provider.dart';
/// the native download call, decrypt/convert/embed finalization, and the
/// history hand-off. Queue scheduling stays in the main file.
extension _SingleItemDownload on DownloadQueueNotifier {
bool _isStorageWriteFailure(Map<String, dynamic> result) {
return isStorageWriteFailure(
errorType: result['error_type']?.toString(),
errorMessage: (result['error'] ?? result['message'])?.toString(),
);
}
Future<String?> _runPostProcessingHooks(
String filePath,
Track track,
@@ -520,7 +513,11 @@ class _DownloadRun {
outputDir: effectiveOutputDir,
);
if (result['success'] != true && n._isStorageWriteFailure(result)) {
if (result['success'] != true &&
isStorageWriteFailure(
errorType: result['error_type']?.toString(),
errorMessage: (result['error'] ?? result['message'])?.toString(),
)) {
if (n._isLocallyCancelled(item.id)) {
_log.i('Download was cancelled before storage fallback, skipping');
return false;
@@ -931,7 +928,7 @@ class _DownloadRun {
if (shouldForceDashSafM4aHandling) {
_log.w(
'SAF file is labeled FLAC but backend returned DASH/M4A stream; converting it back to FLAC.',
'SAF file is labeled FLAC but backend returned an M4A stream; converting it back to FLAC.',
);
}
@@ -1268,10 +1265,8 @@ class _DownloadRun {
: renamedPath;
await file.rename(finalRenamedPath);
targetPath = finalRenamedPath;
filePath = finalRenamedPath;
} else {
filePath = targetPath;
}
filePath = targetPath;
if (metadataEmbeddingEnabled) {
n.updateItemStatus(
@@ -1288,9 +1283,7 @@ class _DownloadRun {
}
Future<void> _convertLocalM4aToFlac(String currentFilePath) async {
_log.d(
'M4A file detected (Hi-Res DASH stream), attempting conversion to FLAC...',
);
_log.d('M4A file detected, attempting conversion to FLAC...');
try {
final file = File(currentFilePath);
@@ -1348,23 +1341,7 @@ class _DownloadRun {
_log.d('Converted to FLAC: $flacPath');
_log.d('Embedding metadata and cover to converted FLAC...');
try {
final backendGenre = result['genre'] as String?;
final backendLabel = result['label'] as String?;
final backendCopyright = result['copyright'] as String?;
if (backendGenre != null ||
backendLabel != null ||
backendCopyright != null) {
_log.d(
'Extended metadata from backend - Genre: $backendGenre, Label: $backendLabel, Copyright: $backendCopyright',
);
}
await _embedFinalMetadata(flacPath, format: 'flac');
_log.d('Metadata and cover embedded successfully');
} catch (e) {
_log.w('Warning: Failed to embed metadata/cover: $e');
}
await _embedFinalMetadata(flacPath, format: 'flac');
} else {
_log.w('FFmpeg conversion returned null, keeping M4A file');
}
@@ -1384,16 +1361,17 @@ class _DownloadRun {
resultOutputExt == '.ogg';
final isMp3File =
currentFilePath.endsWith('.mp3') || resultOutputExt == '.mp3';
final ext = isOpusFile
? (resultOutputExt == '.ogg' ? '.ogg' : '.opus')
final (ext, formatName) = isOpusFile
? (resultOutputExt == '.ogg' ? '.ogg' : '.opus', 'Opus')
: isMp3File
? '.mp3'
: '.flac';
final formatName = isOpusFile
? 'Opus'
: isMp3File
? 'MP3'
: 'FLAC';
? ('.mp3', 'MP3')
: ('.flac', 'FLAC');
// MP3 takes precedence for the embed format when both checks match.
final embedFormat = isMp3File
? 'mp3'
: isOpusFile
? 'opus'
: 'flac';
_log.d(
'SAF $formatName detected, embedding metadata and cover via temp file...',
);
@@ -1416,11 +1394,6 @@ class _DownloadRun {
// so a sidecar .lrc written next to it would be orphaned;
// the SAF .lrc is written by _saveExternalLrc after publish,
// reusing the LRC fetched here via result['lyrics_lrc'].
final embedFormat = isMp3File
? 'mp3'
: isOpusFile
? 'opus'
: 'flac';
final fetchedLrc = await _embedFinalMetadata(
tempPath,
format: embedFormat,
+1 -1
View File
@@ -1,7 +1,7 @@
import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/services/library_database.dart';
/// Field group keys understood by the Go re-enrich backend.
/// Field group keys understood by the native re-enrich backend.
class ReEnrichFields {
static const String cover = 'cover';
static const String lyrics = 'lyrics';
+7 -16
View File
@@ -505,16 +505,6 @@ class FFmpegService {
}.contains(normalized);
}
/// Probes the source audio bit depth (bits_per_raw_sample, falling back to
/// bits_per_sample). Returns null when unknown.
static Future<int?> probeBitDepth(String filePath) async {
return (await _probePrimaryAudioProperties(filePath)).bitDepth;
}
static Future<int?> probeSampleRate(String filePath) async {
return (await _probePrimaryAudioProperties(filePath)).sampleRate;
}
/// 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.
@@ -2102,8 +2092,9 @@ class FFmpegService {
}
/// Convert to uncompressed PCM (WAV or AIFF), preserving bit depth when known.
/// Tags and cover are written natively into an embedded ID3 chunk by the Go
/// backend (RIFF "id3 " for WAV, "ID3 " for AIFF) for full-fidelity tagging.
/// Tags and cover are written natively into an embedded ID3 chunk by the
/// native backend (RIFF "id3 " for WAV, "ID3 " for AIFF) for full-fidelity
/// tagging.
static Future<String?> _convertToPcm({
required String inputPath,
required Map<String, String> metadata,
@@ -2125,7 +2116,7 @@ class FFmpegService {
final outputPath = outputPlan.workingPath;
var depth = targetBitDepth ?? sourceBitDepth;
if (depth == null || depth <= 0) {
depth = await probeBitDepth(inputPath);
depth = (await _probePrimaryAudioProperties(inputPath)).bitDepth;
}
final use24 = depth != null && depth >= 24;
final codec = isAiff
@@ -2198,9 +2189,9 @@ class FFmpegService {
);
}
/// Writes tags + cover into a WAV/AIFF file via the Go native ID3-chunk
/// Writes tags + cover into a WAV/AIFF file via the native backend ID3-chunk
/// writer (PlatformBridge.editFileMetadata). Maps Vorbis-style metadata keys
/// to the lowercase field names the Go editor expects.
/// to the lowercase field names the native editor expects.
static Future<bool> _embedChunkTagsNative(
String path,
Map<String, String> vorbisMetadata,
@@ -2284,7 +2275,7 @@ class FFmpegService {
/// Each track is extracted with `-c copy` (no re-encoding) and metadata is embedded.
/// [audioPath] is the source audio file (FLAC, WAV, etc.)
/// [outputDir] is where individual track files will be saved
/// [tracks] is the list of track split info from the Go CUE parser
/// [tracks] is the list of track split info from the native CUE parser
/// [albumMetadata] contains album-level metadata (artist, album, genre, date)
static Future<List<String>?> splitCueToTracks({
required String audioPath,