mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-11 02:08:44 +02:00
refactor(download): simplify finalization and remove the retired worker
This commit is contained in:
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,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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user