mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 13:22:49 +02:00
feat(metadata): show embedded cover resolution (#510)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user