#1507 mime type normalization
This commit is contained in:
parent
e8eae7e9db
commit
4df4738dd3
11 changed files with 45 additions and 19 deletions
|
@ -179,7 +179,7 @@ class SqfliteLocalMediaDb implements LocalMediaDb {
|
||||||
void _batchInsertEntry(Batch batch, AvesEntry entry) {
|
void _batchInsertEntry(Batch batch, AvesEntry entry) {
|
||||||
batch.insert(
|
batch.insert(
|
||||||
entryTable,
|
entryTable,
|
||||||
entry.toMap(),
|
entry.toDatabaseMap(),
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
conflictAlgorithm: ConflictAlgorithm.replace,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ class AvesEntry with AvesEntryBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
// for DB only
|
// for DB only
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toDatabaseMap() {
|
||||||
return {
|
return {
|
||||||
EntryFields.id: id,
|
EntryFields.id: id,
|
||||||
EntryFields.uri: uri,
|
EntryFields.uri: uri,
|
||||||
|
@ -397,6 +397,17 @@ class AvesEntry with AvesEntryBase {
|
||||||
}.nonNulls.where((v) => v.isNotEmpty).join(', ');
|
}.nonNulls.where((v) => v.isNotEmpty).join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void normalizeMimeTypeFields(Map fields) {
|
||||||
|
final mimeType = fields[EntryFields.mimeType] as String?;
|
||||||
|
if (mimeType != null) {
|
||||||
|
fields[EntryFields.mimeType] = MimeTypes.normalize(mimeType);
|
||||||
|
}
|
||||||
|
final sourceMimeType = fields[EntryFields.sourceMimeType] as String?;
|
||||||
|
if (sourceMimeType != null) {
|
||||||
|
fields[EntryFields.sourceMimeType] = MimeTypes.normalize(sourceMimeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> applyNewFields(Map newFields, {required bool persist}) async {
|
Future<void> applyNewFields(Map newFields, {required bool persist}) async {
|
||||||
final oldMimeType = mimeType;
|
final oldMimeType = mimeType;
|
||||||
final oldDateModifiedMillis = this.dateModifiedMillis;
|
final oldDateModifiedMillis = this.dateModifiedMillis;
|
||||||
|
@ -458,7 +469,7 @@ class AvesEntry with AvesEntryBase {
|
||||||
|
|
||||||
final updatedEntry = await mediaFetchService.getEntry(uri, mimeType);
|
final updatedEntry = await mediaFetchService.getEntry(uri, mimeType);
|
||||||
if (updatedEntry != null) {
|
if (updatedEntry != null) {
|
||||||
await applyNewFields(updatedEntry.toMap(), persist: persist);
|
await applyNewFields(updatedEntry.toDatabaseMap(), persist: persist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,9 +114,7 @@ class VideoMetadataFormatter {
|
||||||
|
|
||||||
// exclude date if it is suspiciously close to epoch
|
// exclude date if it is suspiciously close to epoch
|
||||||
if (dateMillis != null && !DateTime.fromMillisecondsSinceEpoch(dateMillis).isAtSameDayAs(epoch)) {
|
if (dateMillis != null && !DateTime.fromMillisecondsSinceEpoch(dateMillis).isAtSameDayAs(epoch)) {
|
||||||
catalogMetadata = catalogMetadata.copyWith(
|
catalogMetadata = catalogMetadata.copyWith(dateMillis: dateMillis);
|
||||||
dateMillis: dateMillis,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return catalogMetadata;
|
return catalogMetadata;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/keys.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
@ -81,17 +82,17 @@ class MultiPageInfo {
|
||||||
final videoPage = _pages.firstWhereOrNull((page) => page.isVideo);
|
final videoPage = _pages.firstWhereOrNull((page) => page.isVideo);
|
||||||
if (videoPage != null && videoPage.uri == null) {
|
if (videoPage != null && videoPage.uri == null) {
|
||||||
final fields = await embeddedDataService.extractMotionPhotoVideo(mainEntry);
|
final fields = await embeddedDataService.extractMotionPhotoVideo(mainEntry);
|
||||||
if (fields.containsKey('uri')) {
|
if (fields.containsKey(EntryFields.uri)) {
|
||||||
final pageIndex = _pages.indexOf(videoPage);
|
final pageIndex = _pages.indexOf(videoPage);
|
||||||
_pages.removeAt(pageIndex);
|
_pages.removeAt(pageIndex);
|
||||||
_pages.insert(
|
_pages.insert(
|
||||||
pageIndex,
|
pageIndex,
|
||||||
videoPage.copyWith(
|
videoPage.copyWith(
|
||||||
uri: fields['uri'] as String?,
|
uri: fields[EntryFields.uri] as String?,
|
||||||
// the initial fake page may contain inaccurate values for the following fields
|
// the initial fake page may contain inaccurate values for the following fields
|
||||||
// so we override them with values from the extracted standalone video
|
// so we override them with values from the extracted standalone video
|
||||||
rotationDegrees: fields['sourceRotationDegrees'] as int?,
|
rotationDegrees: fields[EntryFields.sourceRotationDegrees] as int?,
|
||||||
durationMillis: fields['durationMillis'] as int?,
|
durationMillis: fields[EntryFields.durationMillis] as int?,
|
||||||
));
|
));
|
||||||
_pageEntries.remove(videoPage);
|
_pageEntries.remove(videoPage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ class MimeTypes {
|
||||||
|
|
||||||
static bool isVisual(String mimeType) => isImage(mimeType) || isVideo(mimeType);
|
static bool isVisual(String mimeType) => isImage(mimeType) || isVideo(mimeType);
|
||||||
|
|
||||||
static String _collapsedType(String mimeType) {
|
static String normalize(String mimeType) {
|
||||||
switch (mimeType) {
|
switch (mimeType) {
|
||||||
case avi:
|
case avi:
|
||||||
case aviMSVideo:
|
case aviMSVideo:
|
||||||
|
@ -127,7 +127,7 @@ class MimeTypes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool refersToSameType(String a, b) => _collapsedType(a) == _collapsedType(b);
|
static bool refersToSameType(String a, b) => normalize(a) == normalize(b);
|
||||||
|
|
||||||
static String? forExtension(String extension) {
|
static String? forExtension(String extension) {
|
||||||
switch (extension) {
|
switch (extension) {
|
||||||
|
|
|
@ -81,6 +81,7 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
'allowUnsized': allowUnsized,
|
'allowUnsized': allowUnsized,
|
||||||
}) as Map;
|
}) as Map;
|
||||||
|
AvesEntry.normalizeMimeTypeFields(result);
|
||||||
return AvesEntry.fromMap(result);
|
return AvesEntry.fromMap(result);
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
// do not report issues with media content as it is likely an obsolete Media Store entry
|
// do not report issues with media content as it is likely an obsolete Media Store entry
|
||||||
|
|
|
@ -85,7 +85,11 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
'directory': directory,
|
'directory': directory,
|
||||||
})
|
})
|
||||||
.where((event) => event is Map)
|
.where((event) => event is Map)
|
||||||
.map((event) => AvesEntry.fromMap(event as Map));
|
.map((event) {
|
||||||
|
final fields = event as Map;
|
||||||
|
AvesEntry.normalizeMimeTypeFields(fields);
|
||||||
|
return AvesEntry.fromMap(fields);
|
||||||
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
reportService.recordError(e, stack);
|
reportService.recordError(e, stack);
|
||||||
return Stream.error(e);
|
return Stream.error(e);
|
||||||
|
|
|
@ -3,10 +3,10 @@ import 'package:aves/model/entry/entry.dart';
|
||||||
import 'package:aves/model/entry/extensions/multipage.dart';
|
import 'package:aves/model/entry/extensions/multipage.dart';
|
||||||
import 'package:aves/model/entry/extensions/props.dart';
|
import 'package:aves/model/entry/extensions/props.dart';
|
||||||
import 'package:aves/model/media/geotiff.dart';
|
import 'package:aves/model/media/geotiff.dart';
|
||||||
|
import 'package:aves/model/media/panorama.dart';
|
||||||
import 'package:aves/model/metadata/catalog.dart';
|
import 'package:aves/model/metadata/catalog.dart';
|
||||||
import 'package:aves/model/metadata/overlay.dart';
|
import 'package:aves/model/metadata/overlay.dart';
|
||||||
import 'package:aves/model/multipage.dart';
|
import 'package:aves/model/multipage.dart';
|
||||||
import 'package:aves/model/media/panorama.dart';
|
|
||||||
import 'package:aves/services/common/service_policy.dart';
|
import 'package:aves/services/common/service_policy.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/metadata/xmp.dart';
|
import 'package:aves/services/metadata/xmp.dart';
|
||||||
|
@ -88,6 +88,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
}) as Map;
|
}) as Map;
|
||||||
result['id'] = entry.id;
|
result['id'] = entry.id;
|
||||||
|
AvesEntry.normalizeMimeTypeFields(result);
|
||||||
return CatalogMetadata.fromMap(result);
|
return CatalogMetadata.fromMap(result);
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (entry.isValid) {
|
if (entry.isValid) {
|
||||||
|
@ -164,6 +165,7 @@ class PlatformMetadataFetchService implements MetadataFetchService {
|
||||||
imagePage['height'] = entry.height;
|
imagePage['height'] = entry.height;
|
||||||
imagePage['rotationDegrees'] = entry.rotationDegrees;
|
imagePage['rotationDegrees'] = entry.rotationDegrees;
|
||||||
}
|
}
|
||||||
|
pageMaps.forEach(AvesEntry.normalizeMimeTypeFields);
|
||||||
return MultiPageInfo.fromPageMaps(entry, pageMaps);
|
return MultiPageInfo.fromPageMaps(entry, pageMaps);
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
if (entry.isValid) {
|
if (entry.isValid) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:math';
|
||||||
import 'package:aves/app_mode.dart';
|
import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/covers.dart';
|
import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -106,12 +107,18 @@ class AvesFilterChip extends StatefulWidget {
|
||||||
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
|
const touchArea = Size(kMinInteractiveDimension, kMinInteractiveDimension);
|
||||||
final actionDelegate = ChipActionDelegate();
|
final actionDelegate = ChipActionDelegate();
|
||||||
final animations = context.read<Settings>().accessibilityAnimations;
|
final animations = context.read<Settings>().accessibilityAnimations;
|
||||||
|
|
||||||
|
var title = filter.getLabel(context);
|
||||||
|
if (filter is MimeFilter) {
|
||||||
|
title += ' (${filter.mime})';
|
||||||
|
}
|
||||||
|
|
||||||
final selectedAction = await showMenu<ChipAction>(
|
final selectedAction = await showMenu<ChipAction>(
|
||||||
context: context,
|
context: context,
|
||||||
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
|
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
|
||||||
items: [
|
items: [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: Text(filter.getLabel(context)),
|
child: Text(title),
|
||||||
),
|
),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
...ChipAction.values.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) {
|
...ChipAction.values.where((action) => actionDelegate.isVisible(action, filter: filter)).map((action) {
|
||||||
|
|
|
@ -103,7 +103,7 @@ class _DbTabState extends State<DbTab> {
|
||||||
child: const Text('Duplicate entry'),
|
child: const Text('Duplicate entry'),
|
||||||
),
|
),
|
||||||
InfoRowGroup(
|
InfoRowGroup(
|
||||||
info: Map.fromEntries(data.toMap().entries.map((kv) => MapEntry(kv.key, kv.value?.toString() ?? ''))),
|
info: Map.fromEntries(data.toDatabaseMap().entries.map((kv) => MapEntry(kv.key, kv.value?.toString() ?? ''))),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/entry/entry.dart';
|
import 'package:aves/model/entry/entry.dart';
|
||||||
|
import 'package:aves/model/entry/extensions/keys.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
@ -51,13 +52,14 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
|
||||||
case EmbeddedDataSource.xmp:
|
case EmbeddedDataSource.xmp:
|
||||||
fields = await embeddedDataService.extractXmpDataProp(entry, notification.props, notification.mimeType);
|
fields = await embeddedDataService.extractXmpDataProp(entry, notification.props, notification.mimeType);
|
||||||
}
|
}
|
||||||
if (!fields.containsKey('mimeType') || !fields.containsKey('uri')) {
|
AvesEntry.normalizeMimeTypeFields(fields);
|
||||||
|
final mimeType = fields[EntryFields.mimeType] as String?;
|
||||||
|
final uri = fields[EntryFields.uri] as String?;
|
||||||
|
if (mimeType == null || uri == null) {
|
||||||
showFeedback(context, FeedbackType.warn, context.l10n.viewerInfoOpenEmbeddedFailureFeedback);
|
showFeedback(context, FeedbackType.warn, context.l10n.viewerInfoOpenEmbeddedFailureFeedback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final mimeType = fields['mimeType']!;
|
|
||||||
final uri = fields['uri']!;
|
|
||||||
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
|
if (!MimeTypes.isImage(mimeType) && !MimeTypes.isVideo(mimeType)) {
|
||||||
// open with another app
|
// open with another app
|
||||||
unawaited(appService.open(uri, mimeType, forceChooser: true).then((success) {
|
unawaited(appService.open(uri, mimeType, forceChooser: true).then((success) {
|
||||||
|
|
Loading…
Reference in a new issue