diff --git a/lib/screens/track_metadata_lyrics.dart b/lib/screens/track_metadata_lyrics.dart index 5db3d1c1..6a7063aa 100644 --- a/lib/screens/track_metadata_lyrics.dart +++ b/lib/screens/track_metadata_lyrics.dart @@ -597,9 +597,17 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { } String _sanitizeFileNameSegment(String value) { - var sanitized = value.replaceAll(_TrackMetadataScreenState._invalidFileNameChars, '_').trim(); - sanitized = sanitized.replaceAll(_TrackMetadataScreenState._leadingOrTrailingDots, ''); - sanitized = sanitized.replaceAll(_TrackMetadataScreenState._multiUnderscore, '_'); + var sanitized = value + .replaceAll(_TrackMetadataScreenState._invalidFileNameChars, '_') + .trim(); + sanitized = sanitized.replaceAll( + _TrackMetadataScreenState._leadingOrTrailingDots, + '', + ); + sanitized = sanitized.replaceAll( + _TrackMetadataScreenState._multiUnderscore, + '_', + ); if (sanitized.isEmpty) { return 'untitled'; } @@ -1015,121 +1023,108 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { final downloadedCoverPath = result['cover_path'] as String?; String? effectiveCoverPath = downloadedCoverPath; String? extractedCoverPath; - if (!_hasPath(effectiveCoverPath)) { - try { - final tempDir = await Directory.systemTemp.createTemp( - 'reenrich_cover_', - ); - final coverOutput = - '${tempDir.path}${Platform.pathSeparator}cover.jpg'; - final extracted = await PlatformBridge.extractCoverToFile( - ffmpegTarget, - coverOutput, - ); - if (extracted['error'] == null) { - effectiveCoverPath = coverOutput; - extractedCoverPath = coverOutput; - } else { - try { - await tempDir.delete(recursive: true); - } catch (_) {} - } - } catch (_) {} - } - final metadata = (result['metadata'] as Map?)?.map( - (k, v) => MapEntry(k, v.toString()), - ); - final lower = cleanFilePath.toLowerCase(); + try { + if (!_hasPath(effectiveCoverPath)) { + try { + final tempDir = await Directory.systemTemp.createTemp( + 'reenrich_cover_', + ); + final coverOutput = + '${tempDir.path}${Platform.pathSeparator}cover.jpg'; + final extracted = await PlatformBridge.extractCoverToFile( + ffmpegTarget, + coverOutput, + ); + if (extracted['error'] == null) { + effectiveCoverPath = coverOutput; + extractedCoverPath = coverOutput; + } else { + try { + await tempDir.delete(recursive: true); + } catch (_) {} + } + } catch (_) {} + } + final metadata = (result['metadata'] as Map?)?.map( + (k, v) => MapEntry(k, v.toString()), + ); + final lower = cleanFilePath.toLowerCase(); - String? ffmpegResult; - if (lower.endsWith('.mp3')) { - ffmpegResult = await FFmpegService.embedMetadataToMp3( - mp3Path: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - ); - } else if (lower.endsWith('.m4a') || lower.endsWith('.aac')) { - ffmpegResult = await FFmpegService.embedMetadataToM4a( - m4aPath: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - ); - } else if (lower.endsWith('.opus') || lower.endsWith('.ogg')) { - ffmpegResult = await FFmpegService.embedMetadataToOpus( - opusPath: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - artistTagMode: artistTagMode, - ); - } + String? ffmpegResult; + if (lower.endsWith('.mp3')) { + ffmpegResult = await FFmpegService.embedMetadataToMp3( + mp3Path: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + ); + } else if (lower.endsWith('.m4a') || lower.endsWith('.aac')) { + ffmpegResult = await FFmpegService.embedMetadataToM4a( + m4aPath: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + ); + } else if (lower.endsWith('.opus') || lower.endsWith('.ogg')) { + ffmpegResult = await FFmpegService.embedMetadataToOpus( + opusPath: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + artistTagMode: artistTagMode, + ); + } - if (ffmpegResult != null && tempPath != null && safUri != null) { - final ok = await PlatformBridge.writeTempToSaf(ffmpegResult, safUri); - if (!ok && mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.trackSaveFailed( - context.l10n.snackbarFailedToWriteStorage, + if (ffmpegResult != null && tempPath != null && safUri != null) { + final ok = await PlatformBridge.writeTempToSaf( + ffmpegResult, + safUri, + ); + if (!ok && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.trackSaveFailed( + context.l10n.snackbarFailedToWriteStorage, + ), ), ), - ), + ); + return; + } + await writeReEnrichSafSidecarLrc( + safUri: safUri, + reEnrichResult: result, ); - if (_hasPath(downloadedCoverPath)) { - try { - await File(downloadedCoverPath!).delete(); - } catch (_) {} - } - if (_hasPath(extractedCoverPath)) { - await _cleanupTempFileAndParent(extractedCoverPath); - } - if (tempPath.isNotEmpty) { - try { - await File(tempPath).delete(); - } catch (_) {} - } - return; } - await writeReEnrichSafSidecarLrc( - safUri: safUri, - reEnrichResult: result, - ); - } - if (tempPath != null && tempPath.isNotEmpty) { - try { - await File(tempPath).delete(); - } catch (_) {} - } - - if (ffmpegResult != null) { - // Filesystem .lrc sidecar. SAF sidecar is written only after - // writeTempToSaf succeeds. - await writeReEnrichSidecarLrc( - audioFilePath: cleanFilePath, - reEnrichResult: result, - ); - await _refreshEmbeddedCoverPreview(force: true); - _markMetadataChanged(); - await _syncDownloadHistoryMetadata(); - if (mounted) { + if (ffmpegResult != null) { + // Filesystem .lrc sidecar. SAF sidecar is written only after + // writeTempToSaf succeeds. + await writeReEnrichSidecarLrc( + audioFilePath: cleanFilePath, + reEnrichResult: result, + ); + await _refreshEmbeddedCoverPreview(force: true); + _markMetadataChanged(); + await _syncDownloadHistoryMetadata(); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.trackReEnrichSuccess)), + ); + } + } else if (mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackReEnrichSuccess)), + SnackBar(content: Text(context.l10n.trackReEnrichFfmpegFailed)), ); } - } else if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.trackReEnrichFfmpegFailed)), - ); - } - - if (_hasPath(downloadedCoverPath)) { - try { - await File(downloadedCoverPath!).delete(); - } catch (_) {} - } - if (_hasPath(extractedCoverPath)) { - await _cleanupTempFileAndParent(extractedCoverPath); + } finally { + for (final path in [downloadedCoverPath, tempPath]) { + if (!_hasPath(path)) continue; + try { + await File(path!).delete(); + } catch (_) {} + } + if (_hasPath(extractedCoverPath)) { + await _cleanupTempFileAndParent(extractedCoverPath); + } } } else { if (mounted) { @@ -1191,19 +1186,30 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { var cleaned = line.trim(); if (_TrackMetadataScreenState._lrcMetadataPattern.hasMatch(cleaned) && - !_TrackMetadataScreenState._lrcBackgroundLinePattern.hasMatch(cleaned)) { + !_TrackMetadataScreenState._lrcBackgroundLinePattern.hasMatch( + cleaned, + )) { continue; } // Convert [bg:...] wrapper to a plain secondary vocal line. - final bgMatch = _TrackMetadataScreenState._lrcBackgroundLinePattern.firstMatch(cleaned); + final bgMatch = _TrackMetadataScreenState._lrcBackgroundLinePattern + .firstMatch(cleaned); if (bgMatch != null) { cleaned = bgMatch.group(1)?.trim() ?? ''; } - cleaned = cleaned.replaceAll(_TrackMetadataScreenState._lrcTimestampPattern, '').trim(); - cleaned = cleaned.replaceAll(_TrackMetadataScreenState._lrcInlineTimestampPattern, ''); - cleaned = cleaned.replaceFirst(_TrackMetadataScreenState._lrcSpeakerPrefixPattern, ''); + cleaned = cleaned + .replaceAll(_TrackMetadataScreenState._lrcTimestampPattern, '') + .trim(); + cleaned = cleaned.replaceAll( + _TrackMetadataScreenState._lrcInlineTimestampPattern, + '', + ); + cleaned = cleaned.replaceFirst( + _TrackMetadataScreenState._lrcSpeakerPrefixPattern, + '', + ); cleaned = cleaned.replaceAll(RegExp(r'\s+'), ' ').trim(); if (cleaned.isNotEmpty) { @@ -1213,5 +1219,4 @@ extension _TrackMetadataLyricsAndSaving on _TrackMetadataScreenState { return cleanLines.join('\n'); } - } diff --git a/lib/utils/ffmpeg_reenrich.dart b/lib/utils/ffmpeg_reenrich.dart index 021cd521..dfa1257a 100644 --- a/lib/utils/ffmpeg_reenrich.dart +++ b/lib/utils/ffmpeg_reenrich.dart @@ -47,101 +47,92 @@ Future applyFfmpegReEnrichResult({ String? effectiveCoverPath = downloadedCoverPath; String? extractedCoverPath; - if (!_hasValue(effectiveCoverPath)) { - try { - final tempDir = await Directory.systemTemp.createTemp('reenrich_cover_'); - final coverOutput = '${tempDir.path}${Platform.pathSeparator}cover.jpg'; - final extracted = await PlatformBridge.extractCoverToFile( - ffmpegTarget, - coverOutput, - ); - if (extracted['error'] == null) { - effectiveCoverPath = coverOutput; - extractedCoverPath = coverOutput; - } else { - try { - await tempDir.delete(recursive: true); - } catch (_) {} - } - } catch (_) {} - } - - final metadata = (result['metadata'] as Map?)?.map( - (k, v) => MapEntry(k, v.toString()), - ); - - final format = item.format?.toLowerCase(); - final lowerPath = item.filePath.toLowerCase(); - final isMp3 = format == 'mp3' || lowerPath.endsWith('.mp3'); - final isM4A = - format == 'm4a' || - format == 'aac' || - lowerPath.endsWith('.m4a') || - lowerPath.endsWith('.aac'); - final isOpus = - format == 'opus' || - format == 'ogg' || - lowerPath.endsWith('.opus') || - lowerPath.endsWith('.ogg'); - - String? ffmpegResult; - if (isMp3) { - ffmpegResult = await FFmpegService.embedMetadataToMp3( - mp3Path: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - preserveMetadata: true, - ); - } else if (isM4A) { - ffmpegResult = await FFmpegService.embedMetadataToM4a( - m4aPath: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - preserveMetadata: true, - ); - } else if (isOpus) { - ffmpegResult = await FFmpegService.embedMetadataToOpus( - opusPath: ffmpegTarget, - coverPath: effectiveCoverPath, - metadata: metadata, - artistTagMode: artistTagMode, - preserveMetadata: true, - ); - } - - if (ffmpegResult != null && _hasValue(tempPath) && _hasValue(safUri)) { - final ok = await PlatformBridge.writeTempToSaf(ffmpegResult, safUri!); - if (!ok) { - if (_hasValue(downloadedCoverPath)) { - await _safeDeleteFile(downloadedCoverPath!); - } - if (_hasValue(extractedCoverPath)) { - await _cleanupTempFileAndParent(extractedCoverPath!); - } - await _safeDeleteFile(tempPath!); - return false; + try { + if (!_hasValue(effectiveCoverPath)) { + try { + final tempDir = await Directory.systemTemp.createTemp( + 'reenrich_cover_', + ); + final coverOutput = '${tempDir.path}${Platform.pathSeparator}cover.jpg'; + final extracted = await PlatformBridge.extractCoverToFile( + ffmpegTarget, + coverOutput, + ); + if (extracted['error'] == null) { + effectiveCoverPath = coverOutput; + extractedCoverPath = coverOutput; + } else { + try { + await tempDir.delete(recursive: true); + } catch (_) {} + } + } catch (_) {} } - await writeReEnrichSafSidecarLrc(safUri: safUri, reEnrichResult: result); - } - if (_hasValue(downloadedCoverPath)) { - await _safeDeleteFile(downloadedCoverPath!); - } - if (_hasValue(extractedCoverPath)) { - await _cleanupTempFileAndParent(extractedCoverPath!); - } - if (_hasValue(tempPath)) { - await _safeDeleteFile(tempPath!); - } - - if (ffmpegResult != null) { - // Filesystem .lrc sidecar. SAF sidecar is written only after - // writeTempToSaf succeeds. - await writeReEnrichSidecarLrc( - audioFilePath: item.filePath, - reEnrichResult: result, + final metadata = (result['metadata'] as Map?)?.map( + (k, v) => MapEntry(k, v.toString()), ); - } + final format = item.format?.toLowerCase(); + final lowerPath = item.filePath.toLowerCase(); + final isMp3 = format == 'mp3' || lowerPath.endsWith('.mp3'); + final isM4A = + format == 'm4a' || + format == 'aac' || + lowerPath.endsWith('.m4a') || + lowerPath.endsWith('.aac'); + final isOpus = + format == 'opus' || + format == 'ogg' || + lowerPath.endsWith('.opus') || + lowerPath.endsWith('.ogg'); - return ffmpegResult != null; + String? ffmpegResult; + if (isMp3) { + ffmpegResult = await FFmpegService.embedMetadataToMp3( + mp3Path: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + preserveMetadata: true, + ); + } else if (isM4A) { + ffmpegResult = await FFmpegService.embedMetadataToM4a( + m4aPath: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + preserveMetadata: true, + ); + } else if (isOpus) { + ffmpegResult = await FFmpegService.embedMetadataToOpus( + opusPath: ffmpegTarget, + coverPath: effectiveCoverPath, + metadata: metadata, + artistTagMode: artistTagMode, + preserveMetadata: true, + ); + } + + if (ffmpegResult != null && _hasValue(tempPath) && _hasValue(safUri)) { + final ok = await PlatformBridge.writeTempToSaf(ffmpegResult, safUri!); + if (!ok) return false; + await writeReEnrichSafSidecarLrc(safUri: safUri, reEnrichResult: result); + } + + if (ffmpegResult != null) { + await writeReEnrichSidecarLrc( + audioFilePath: item.filePath, + reEnrichResult: result, + ); + } + return ffmpegResult != null; + } finally { + if (_hasValue(downloadedCoverPath)) { + await _safeDeleteFile(downloadedCoverPath!); + } + if (_hasValue(extractedCoverPath)) { + await _cleanupTempFileAndParent(extractedCoverPath!); + } + if (_hasValue(tempPath)) { + await _safeDeleteFile(tempPath!); + } + } }