feat(metadata): show embedded cover resolution (#510)

This commit is contained in:
zarzet
2026-08-11 18:55:40 +07:00
parent 8333069b95
commit 4693e2f1d2
3 changed files with 49 additions and 22 deletions
+5
View File
@@ -440,6 +440,11 @@ extension _TrackMetadataCards on _TrackMetadataScreenState {
_MetadataItem(context.l10n.trackDuration, formatClock(duration!)),
if (audioQualityStr != null)
_MetadataItem(context.l10n.trackAudioQuality, audioQualityStr),
if (_embeddedCoverDimensions case final dimensions?)
_MetadataItem(
context.l10n.trackCoverResolution,
'${dimensions.width} × ${dimensions.height} px',
),
if (releaseDate != null && releaseDate!.isNotEmpty)
_MetadataItem(context.l10n.trackReleaseDate, releaseDate!),
if (genre != null && genre!.isNotEmpty)
+15 -13
View File
@@ -67,10 +67,12 @@ final _log = AppLogger('TrackMetadata');
class _EmbeddedCoverPreviewCacheEntry {
final String previewPath;
final String? sourceValidationToken;
final ({int width, int height})? dimensions;
const _EmbeddedCoverPreviewCacheEntry({
required this.previewPath,
this.sourceValidationToken,
this.dimensions,
});
}
@@ -144,6 +146,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
Map<String, dynamic>? _editedMetadata;
String? _resolvedAudioFormat;
String? _embeddedCoverPreviewPath;
({int width, int height})? _embeddedCoverDimensions;
static final RegExp _invalidFileNameChars = RegExp(r'[<>:"/\\|?*\x00-\x1f]');
static final RegExp _multiUnderscore = RegExp(r'_+');
static final RegExp _leadingOrTrailingDots = RegExp(r'^\.+|\.+$');
@@ -235,28 +238,26 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
filePath == cleanFilePath &&
exists &&
!_hasPath(_embeddedCoverPreviewPath)) {
final cachedPath = await _getCachedEmbeddedCoverPreviewPathIfValid(
final cachedCover = await _getCachedEmbeddedCoverPreviewIfValid(
_coverCacheKey,
filePath,
);
if (mounted &&
generation == _metadataLoadGeneration &&
filePath == cleanFilePath &&
_hasPath(cachedPath)) {
setState(() => _embeddedCoverPreviewPath = cachedPath);
cachedCover != null) {
setState(() {
_embeddedCoverPreviewPath = cachedCover.previewPath;
_embeddedCoverDimensions = cachedCover.dimensions;
});
} else if (mounted &&
generation == _metadataLoadGeneration &&
filePath == cleanFilePath) {
final localCoverExists =
_hasPath(_localCoverPath) && await fileExists(_localCoverPath!);
if (!mounted ||
generation != _metadataLoadGeneration ||
filePath != cleanFilePath) {
return;
}
if (!localCoverExists && !_hasPath(_coverUrl)) {
unawaited(_refreshEmbeddedCoverPreview());
}
// The information card reports the artwork embedded in the audio
// file, not a potentially resized Library thumbnail or remote cover.
// Extraction is cached, so revisiting the same track does not repeat
// the work.
unawaited(_refreshEmbeddedCoverPreview());
}
}
}
@@ -589,6 +590,7 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen>
_editedMetadata = null;
_resolvedAudioFormat = null;
_embeddedCoverPreviewPath = null;
_embeddedCoverDimensions = null;
});
if (scrollController.hasClients) {
+29 -9
View File
@@ -35,6 +35,7 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
String cacheKey,
String sourcePath,
String previewPath,
({int width, int height})? dimensions,
) async {
final sourceValidationToken = await _readLocalFileValidationToken(
sourcePath,
@@ -45,6 +46,7 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
_EmbeddedCoverPreviewCacheEntry(
previewPath: previewPath,
sourceValidationToken: sourceValidationToken,
dimensions: dimensions,
);
if (existing != null && existing.previewPath != previewPath) {
await _cleanupTempFileAndParentIfNotCached(existing.previewPath);
@@ -74,7 +76,8 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
}
}
Future<String?> _getCachedEmbeddedCoverPreviewPathIfValid(
Future<_EmbeddedCoverPreviewCacheEntry?>
_getCachedEmbeddedCoverPreviewIfValid(
String cacheKey,
String sourcePath,
) async {
@@ -99,7 +102,7 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
}
}
return cached.previewPath;
return cached;
}
Future<void> _refreshEmbeddedCoverPreview({bool force = false}) async {
@@ -107,22 +110,27 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
final cacheKey = _coverCacheKey;
final sourcePath = cleanFilePath;
if (!force) {
final cachedPath = await _getCachedEmbeddedCoverPreviewPathIfValid(
final cachedCover = await _getCachedEmbeddedCoverPreviewIfValid(
cacheKey,
sourcePath,
);
if (_hasPath(cachedPath)) {
if (cachedCover != null) {
if (mounted &&
generation == _metadataLoadGeneration &&
sourcePath == cleanFilePath &&
_embeddedCoverPreviewPath != cachedPath) {
setState(() => _embeddedCoverPreviewPath = cachedPath);
(_embeddedCoverPreviewPath != cachedCover.previewPath ||
_embeddedCoverDimensions != cachedCover.dimensions)) {
setState(() {
_embeddedCoverPreviewPath = cachedCover.previewPath;
_embeddedCoverDimensions = cachedCover.dimensions;
});
}
return;
}
}
String? newPreviewPath;
({int width, int height})? newDimensions;
try {
if (!_fileExists) {
await _invalidateEmbeddedCoverPreviewCacheForPath(cacheKey);
@@ -130,7 +138,10 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
if (mounted &&
generation == _metadataLoadGeneration &&
sourcePath == cleanFilePath) {
setState(() => _embeddedCoverPreviewPath = null);
setState(() {
_embeddedCoverPreviewPath = null;
_embeddedCoverDimensions = null;
});
}
return;
}
@@ -148,7 +159,13 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
);
if (result['error'] == null && await File(outputPath).exists()) {
newPreviewPath = outputPath;
await _cacheEmbeddedCoverPreview(cacheKey, sourcePath, outputPath);
newDimensions = await FFmpegService.probeImageDimensions(outputPath);
await _cacheEmbeddedCoverPreview(
cacheKey,
sourcePath,
outputPath,
newDimensions,
);
} else {
try {
await tempDir.delete(recursive: true);
@@ -166,7 +183,10 @@ extension _TrackMetadataCover on _TrackMetadataScreenState {
return;
}
setState(() => _embeddedCoverPreviewPath = newPreviewPath);
setState(() {
_embeddedCoverPreviewPath = newPreviewPath;
_embeddedCoverDimensions = newDimensions;
});
if (oldPreviewPath != null && oldPreviewPath != newPreviewPath) {
await _cleanupTempFileAndParentIfNotCached(oldPreviewPath);
}