#443 Info: improved NS prefix handling

This commit is contained in:
Thibault Deckers 2023-01-16 14:24:54 +01:00
parent a6b5398962
commit 86c6e7ce93
14 changed files with 132 additions and 98 deletions

View file

@ -12,7 +12,7 @@ import com.drew.metadata.xmp.XmpDirectory
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.metadata.* import deckers.thibault.aves.metadata.*
import deckers.thibault.aves.metadata.XMP.doesPropExist import deckers.thibault.aves.metadata.XMP.doesPropPathExist
import deckers.thibault.aves.metadata.XMP.getSafeStructField import deckers.thibault.aves.metadata.XMP.getSafeStructField
import deckers.thibault.aves.metadata.metadataextractor.Helper import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.FieldMap
@ -104,7 +104,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
try { try {
container = xmpDirs.firstNotNullOfOrNull { container = xmpDirs.firstNotNullOfOrNull {
val xmpMeta = it.xmpMeta val xmpMeta = it.xmpMeta
if (xmpMeta.doesPropExist(XMP.GDEVICE_DIRECTORY_PROP_NAME)) { if (xmpMeta.doesPropPathExist(listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME))) {
GoogleDeviceContainer().apply { findItems(xmpMeta) } GoogleDeviceContainer().apply { findItems(xmpMeta) }
} else { } else {
null null

View file

@ -3,7 +3,7 @@ package deckers.thibault.aves.metadata
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import com.adobe.internal.xmp.XMPMeta import com.adobe.internal.xmp.XMPMeta
import deckers.thibault.aves.metadata.XMP.countPropArrayItems import deckers.thibault.aves.metadata.XMP.countPropPathArrayItems
import deckers.thibault.aves.metadata.XMP.getSafeStructField import deckers.thibault.aves.metadata.XMP.getSafeStructField
import deckers.thibault.aves.utils.indexOfBytes import deckers.thibault.aves.utils.indexOfBytes
import java.io.DataInputStream import java.io.DataInputStream
@ -15,11 +15,12 @@ class GoogleDeviceContainer {
private val offsets: MutableList<Int> = ArrayList() private val offsets: MutableList<Int> = ArrayList()
fun findItems(xmpMeta: XMPMeta) { fun findItems(xmpMeta: XMPMeta) {
val count = xmpMeta.countPropArrayItems(XMP.GDEVICE_DIRECTORY_PROP_NAME) val containerDirectoryPath = listOf(XMP.GDEVICE_CONTAINER_PROP_NAME, XMP.GDEVICE_CONTAINER_DIRECTORY_PROP_NAME)
val count = xmpMeta.countPropPathArrayItems(containerDirectoryPath)
for (i in 1 until count + 1) { for (i in 1 until count + 1) {
val mimeType = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value val mimeType = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME))?.value
val length = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull() val length = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME))?.value?.toLongOrNull()
val dataUri = xmpMeta.getSafeStructField(listOf(XMP.GDEVICE_DIRECTORY_PROP_NAME, i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value val dataUri = xmpMeta.getSafeStructField(containerDirectoryPath + listOf(i, XMP.GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME))?.value
if (mimeType != null && length != null && dataUri != null) { if (mimeType != null && length != null && dataUri != null) {
items.add( items.add(
GoogleDeviceContainerItem( GoogleDeviceContainerItem(

View file

@ -49,6 +49,7 @@ object XMP {
private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/" private const val GCONTAINER_ITEM_NS_URI = "http://ns.google.com/photos/1.0/container/item/"
private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/" private const val GDEPTH_NS_URI = "http://ns.google.com/photos/1.0/depthmap/"
private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/" private const val GDEVICE_NS_URI = "http://ns.google.com/photos/dd/1.0/device/"
private const val GDEVICE_CONTAINER_NS_URI = "http://ns.google.com/photos/dd/1.0/container/"
private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/" private const val GDEVICE_ITEM_NS_URI = "http://ns.google.com/photos/dd/1.0/item/"
private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/" private const val GIMAGE_NS_URI = "http://ns.google.com/photos/1.0/image/"
private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/" private const val GPANO_NS_URI = "http://ns.google.com/photos/1.0/panorama/"
@ -79,7 +80,8 @@ object XMP {
// google portrait // google portrait
val GDEVICE_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container/Container:Directory") val GDEVICE_CONTAINER_PROP_NAME = XMPPropName(GDEVICE_NS_URI, "Container")
val GDEVICE_CONTAINER_DIRECTORY_PROP_NAME = XMPPropName(GDEVICE_CONTAINER_NS_URI, "Directory")
val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI") val GDEVICE_CONTAINER_ITEM_DATA_URI_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "DataURI")
val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length") val GDEVICE_CONTAINER_ITEM_LENGTH_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Length")
val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime") val GDEVICE_CONTAINER_ITEM_MIME_PROP_NAME = XMPPropName(GDEVICE_ITEM_NS_URI, "Mime")
@ -254,10 +256,18 @@ object XMP {
return doesPropertyExist(prop.nsUri, prop.toString()) return doesPropertyExist(prop.nsUri, prop.toString())
} }
fun XMPMeta.doesPropPathExist(props: List<XMPPropName>): Boolean {
return doesPropertyExist(props.first().nsUri, props.joinToString("/"))
}
fun XMPMeta.countPropArrayItems(prop: XMPPropName): Int { fun XMPMeta.countPropArrayItems(prop: XMPPropName): Int {
return countArrayItems(prop.nsUri, prop.toString()) return countArrayItems(prop.nsUri, prop.toString())
} }
fun XMPMeta.countPropPathArrayItems(props: List<XMPPropName>): Int {
return countArrayItems(props.first().nsUri, props.joinToString("/"))
}
fun XMPMeta.getPropArrayItemValues(prop: XMPPropName): List<String> { fun XMPMeta.getPropArrayItemValues(prop: XMPPropName): List<String> {
val schema = prop.nsUri val schema = prop.nsUri
val propName = prop.toString() val propName = prop.toString()

View file

@ -463,7 +463,7 @@ extension ExtraAvesEntryMetadataEdition on AvesEntry {
modified |= XMP.removeElements( modified |= XMP.removeElements(
descriptions, descriptions,
XMP.containerDirectory, XMP.containerDirectory,
Namespaces.container, Namespaces.gContainer,
); );
modified |= [ modified |= [

View file

@ -7,7 +7,6 @@ class Namespaces {
static const avm = 'http://www.communicatingastronomy.org/avm/1.0/'; static const avm = 'http://www.communicatingastronomy.org/avm/1.0/';
static const camera = 'http://pix4d.com/camera/1.0/'; static const camera = 'http://pix4d.com/camera/1.0/';
static const cc = 'http://creativecommons.org/ns#'; static const cc = 'http://creativecommons.org/ns#';
static const container = 'http://ns.google.com/photos/1.0/container/';
static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/'; static const creatorAtom = 'http://ns.adobe.com/creatorAtom/1.0/';
static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/'; static const crd = 'http://ns.adobe.com/camera-raw-defaults/1.0/';
static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/'; static const crlcp = 'http://ns.adobe.com/camera-raw-embedded-lens-profile/1.0/';
@ -26,9 +25,13 @@ class Namespaces {
static const exifEx = 'http://cipa.jp/exif/1.0/'; static const exifEx = 'http://cipa.jp/exif/1.0/';
static const gAudio = 'http://ns.google.com/photos/1.0/audio/'; static const gAudio = 'http://ns.google.com/photos/1.0/audio/';
static const gCamera = 'http://ns.google.com/photos/1.0/camera/'; static const gCamera = 'http://ns.google.com/photos/1.0/camera/';
static const gContainer = 'http://ns.google.com/photos/1.0/container/';
static const gCreations = 'http://ns.google.com/photos/1.0/creations/'; static const gCreations = 'http://ns.google.com/photos/1.0/creations/';
static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/'; static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/';
static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/'; static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/';
static const gDeviceCamera = 'http://ns.google.com/photos/dd/1.0/camera/';
static const gDeviceContainer = 'http://ns.google.com/photos/dd/1.0/container/';
static const gDeviceItem = 'http://ns.google.com/photos/dd/1.0/item/';
static const gFocus = 'http://ns.google.com/photos/1.0/focus/'; static const gFocus = 'http://ns.google.com/photos/1.0/focus/';
static const gImage = 'http://ns.google.com/photos/1.0/image/'; static const gImage = 'http://ns.google.com/photos/1.0/image/';
static const gPano = 'http://ns.google.com/photos/1.0/panorama/'; static const gPano = 'http://ns.google.com/photos/1.0/panorama/';
@ -83,7 +86,6 @@ class Namespaces {
avm: 'Astronomy Visualization', avm: 'Astronomy Visualization',
camera: 'Pix4D Camera', camera: 'Pix4D Camera',
cc: 'Creative Commons', cc: 'Creative Commons',
container: 'Container',
crd: 'Camera Raw Defaults', crd: 'Camera Raw Defaults',
creatorAtom: 'After Effects', creatorAtom: 'After Effects',
crs: 'Camera Raw Settings', crs: 'Camera Raw Settings',
@ -97,6 +99,7 @@ class Namespaces {
exifEx: 'Exif Ex', exifEx: 'Exif Ex',
gAudio: 'Google Audio', gAudio: 'Google Audio',
gCamera: 'Google Camera', gCamera: 'Google Camera',
gContainer: 'Google Container',
gCreations: 'Google Creations', gCreations: 'Google Creations',
gDepth: 'Google Depth', gDepth: 'Google Depth',
gDevice: 'Google Device', gDevice: 'Google Device',
@ -138,7 +141,7 @@ class Namespaces {
}; };
static final defaultPrefixes = { static final defaultPrefixes = {
container: 'Container', gContainer: 'Container',
dc: 'dc', dc: 'dc',
gCamera: 'GCamera', gCamera: 'GCamera',
microsoftPhoto: 'MicrosoftPhoto', microsoftPhoto: 'MicrosoftPhoto',

View file

@ -22,56 +22,62 @@ import 'package:tuple/tuple.dart';
@immutable @immutable
class XmpNamespace extends Equatable { class XmpNamespace extends Equatable {
final Map<String, String> schemaRegistryPrefixes;
final String nsUri, nsPrefix; final String nsUri, nsPrefix;
final Map<String, String> rawProps; final Map<String, String> rawProps;
@override @override
List<Object?> get props => [nsUri, nsPrefix]; List<Object?> get props => [nsUri, nsPrefix];
const XmpNamespace(this.nsUri, this.nsPrefix, this.rawProps); XmpNamespace({
required this.nsUri,
required this.schemaRegistryPrefixes,
required this.rawProps,
}) : nsPrefix = prefixForUri(schemaRegistryPrefixes, nsUri);
factory XmpNamespace.create(String nsUri, String nsPrefix, Map<String, String> rawProps) { factory XmpNamespace.create(Map<String, String> schemaRegistryPrefixes, String nsPrefix, Map<String, String> rawProps) {
final nsUri = schemaRegistryPrefixes[nsPrefix] ?? '';
switch (nsUri) { switch (nsUri) {
case Namespaces.container:
return XmpContainer(nsPrefix, rawProps);
case Namespaces.creatorAtom: case Namespaces.creatorAtom:
return XmpCreatorAtom(nsPrefix, rawProps); return XmpCreatorAtom(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.crs: case Namespaces.crs:
return XmpCrsNamespace(nsPrefix, rawProps); return XmpCrsNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.darktable: case Namespaces.darktable:
return XmpDarktableNamespace(nsPrefix, rawProps); return XmpDarktableNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.dwc: case Namespaces.dwc:
return XmpDwcNamespace(nsPrefix, rawProps); return XmpDwcNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.exif: case Namespaces.exif:
return XmpExifNamespace(nsPrefix, rawProps); return XmpExifNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.gAudio: case Namespaces.gAudio:
return XmpGAudioNamespace(nsPrefix, rawProps); return XmpGAudioNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.gContainer:
return XmpGContainer(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.gDepth: case Namespaces.gDepth:
return XmpGDepthNamespace(nsPrefix, rawProps); return XmpGDepthNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.gDevice: case Namespaces.gDevice:
return XmpGDeviceNamespace(nsPrefix, rawProps); return XmpGDeviceNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.gImage: case Namespaces.gImage:
return XmpGImageNamespace(nsPrefix, rawProps); return XmpGImageNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.iptc4xmpCore: case Namespaces.iptc4xmpCore:
return XmpIptcCoreNamespace(nsPrefix, rawProps); return XmpIptcCoreNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.iptc4xmpExt: case Namespaces.iptc4xmpExt:
return XmpIptc4xmpExtNamespace(nsPrefix, rawProps); return XmpIptc4xmpExtNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.mwgrs: case Namespaces.mwgrs:
return XmpMgwRegionsNamespace(nsPrefix, rawProps); return XmpMgwRegionsNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.mp: case Namespaces.mp:
return XmpMPNamespace(nsPrefix, rawProps); return XmpMPNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.photoshop: case Namespaces.photoshop:
return XmpPhotoshopNamespace(nsPrefix, rawProps); return XmpPhotoshopNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.plus: case Namespaces.plus:
return XmpPlusNamespace(nsPrefix, rawProps); return XmpPlusNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.tiff: case Namespaces.tiff:
return XmpTiffNamespace(nsPrefix, rawProps); return XmpTiffNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.xmp: case Namespaces.xmp:
return XmpBasicNamespace(nsPrefix, rawProps); return XmpBasicNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
case Namespaces.xmpMM: case Namespaces.xmpMM:
return XmpMMNamespace(nsPrefix, rawProps); return XmpMMNamespace(schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
default: default:
return XmpNamespace(nsUri, nsPrefix, rawProps); return XmpNamespace(nsUri: nsUri, schemaRegistryPrefixes: schemaRegistryPrefixes, rawProps: rawProps);
} }
} }
@ -130,6 +136,8 @@ class XmpNamespace extends Equatable {
String formatValue(XmpProp prop) => prop.value; String formatValue(XmpProp prop) => prop.value;
Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {}; Map<String, InfoValueSpanBuilder> linkifyValues(List<XmpProp> props) => {};
static String prefixForUri(Map<String, String> schemaRegistryPrefixes, String nsUri) => schemaRegistryPrefixes.entries.firstWhereOrNull((kv) => kv.value == nsUri)?.key ?? '';
} }
class XmpProp implements Comparable<XmpProp> { class XmpProp implements Comparable<XmpProp> {

View file

@ -2,7 +2,7 @@ import 'package:aves/utils/xmp_utils.dart';
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
class XmpCrsNamespace extends XmpNamespace { class XmpCrsNamespace extends XmpNamespace {
XmpCrsNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.crs, nsPrefix, rawProps); XmpCrsNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.crs);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [

View file

@ -4,68 +4,69 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/exif.md // cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/exif.md
class XmpExifNamespace extends XmpNamespace { class XmpExifNamespace extends XmpNamespace {
const XmpExifNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.exif, nsPrefix, rawProps); XmpExifNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.exif);
@override @override
String formatValue(XmpProp prop) { String formatValue(XmpProp prop) {
final v = prop.value; final v = prop.value;
switch (prop.path) { final field = prop.path.replaceAll(nsPrefix, '');
case 'exif:ColorSpace': switch (field) {
case 'ColorSpace':
return Exif.getColorSpaceDescription(v); return Exif.getColorSpaceDescription(v);
case 'exif:Contrast': case 'Contrast':
return Exif.getContrastDescription(v); return Exif.getContrastDescription(v);
case 'exif:CustomRendered': case 'CustomRendered':
return Exif.getCustomRenderedDescription(v); return Exif.getCustomRenderedDescription(v);
case 'exif:ExifVersion': case 'ExifVersion':
case 'exif:FlashpixVersion': case 'FlashpixVersion':
return Exif.getExifVersionDescription(v); return Exif.getExifVersionDescription(v);
case 'exif:ExposureMode': case 'ExposureMode':
return Exif.getExposureModeDescription(v); return Exif.getExposureModeDescription(v);
case 'exif:ExposureProgram': case 'ExposureProgram':
return Exif.getExposureProgramDescription(v); return Exif.getExposureProgramDescription(v);
case 'exif:FileSource': case 'FileSource':
return Exif.getFileSourceDescription(v); return Exif.getFileSourceDescription(v);
case 'exif:Flash/exif:Mode': case 'Flash/Mode':
return Exif.getFlashModeDescription(v); return Exif.getFlashModeDescription(v);
case 'exif:Flash/exif:Return': case 'Flash/Return':
return Exif.getFlashReturnDescription(v); return Exif.getFlashReturnDescription(v);
case 'exif:FocalPlaneResolutionUnit': case 'FocalPlaneResolutionUnit':
return Exif.getResolutionUnitDescription(v); return Exif.getResolutionUnitDescription(v);
case 'exif:GainControl': case 'GainControl':
return Exif.getGainControlDescription(v); return Exif.getGainControlDescription(v);
case 'exif:LightSource': case 'LightSource':
return Exif.getLightSourceDescription(v); return Exif.getLightSourceDescription(v);
case 'exif:MeteringMode': case 'MeteringMode':
return Exif.getMeteringModeDescription(v); return Exif.getMeteringModeDescription(v);
case 'exif:Saturation': case 'Saturation':
return Exif.getSaturationDescription(v); return Exif.getSaturationDescription(v);
case 'exif:SceneCaptureType': case 'SceneCaptureType':
return Exif.getSceneCaptureTypeDescription(v); return Exif.getSceneCaptureTypeDescription(v);
case 'exif:SceneType': case 'SceneType':
return Exif.getSceneTypeDescription(v); return Exif.getSceneTypeDescription(v);
case 'exif:SensingMethod': case 'SensingMethod':
return Exif.getSensingMethodDescription(v); return Exif.getSensingMethodDescription(v);
case 'exif:Sharpness': case 'Sharpness':
return Exif.getSharpnessDescription(v); return Exif.getSharpnessDescription(v);
case 'exif:SubjectDistanceRange': case 'SubjectDistanceRange':
return Exif.getSubjectDistanceRangeDescription(v); return Exif.getSubjectDistanceRangeDescription(v);
case 'exif:WhiteBalance': case 'WhiteBalance':
return Exif.getWhiteBalanceDescription(v); return Exif.getWhiteBalanceDescription(v);
case 'exif:GPSAltitudeRef': case 'GPSAltitudeRef':
return Exif.getGPSAltitudeRefDescription(v); return Exif.getGPSAltitudeRefDescription(v);
case 'exif:GPSDestBearingRef': case 'GPSDestBearingRef':
case 'exif:GPSImgDirectionRef': case 'GPSImgDirectionRef':
case 'exif:GPSTrackRef': case 'GPSTrackRef':
return Exif.getGPSDirectionRefDescription(v); return Exif.getGPSDirectionRefDescription(v);
case 'exif:GPSDestDistanceRef': case 'GPSDestDistanceRef':
return Exif.getGPSDestDistanceRefDescription(v); return Exif.getGPSDestDistanceRefDescription(v);
case 'exif:GPSDifferential': case 'GPSDifferential':
return Exif.getGPSDifferentialDescription(v); return Exif.getGPSDifferentialDescription(v);
case 'exif:GPSMeasureMode': case 'GPSMeasureMode':
return Exif.getGPSMeasureModeDescription(v); return Exif.getGPSMeasureModeDescription(v);
case 'exif:GPSSpeedRef': case 'GPSSpeedRef':
return Exif.getGPSSpeedRefDescription(v); return Exif.getGPSSpeedRefDescription(v);
case 'exif:GPSStatus': case 'GPSStatus':
return Exif.getGPSStatusDescription(v); return Exif.getGPSStatusDescription(v);
default: default:
return v; return v;

View file

@ -7,7 +7,11 @@ import 'package:collection/collection.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
abstract class XmpGoogleNamespace extends XmpNamespace { abstract class XmpGoogleNamespace extends XmpNamespace {
const XmpGoogleNamespace(String nsUri, String nsPrefix, Map<String, String> rawProps) : super(nsUri, nsPrefix, rawProps); XmpGoogleNamespace({
required super.nsUri,
required super.schemaRegistryPrefixes,
required super.rawProps,
});
List<Tuple2<String, String>> get dataProps; List<Tuple2<String, String>> get dataProps;
@ -53,14 +57,14 @@ abstract class XmpGoogleNamespace extends XmpNamespace {
} }
class XmpGAudioNamespace extends XmpGoogleNamespace { class XmpGAudioNamespace extends XmpGoogleNamespace {
const XmpGAudioNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gAudio, nsPrefix, rawProps); XmpGAudioNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gAudio);
@override @override
List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')]; List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')];
} }
class XmpGDepthNamespace extends XmpGoogleNamespace { class XmpGDepthNamespace extends XmpGoogleNamespace {
const XmpGDepthNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gDepth, nsPrefix, rawProps); XmpGDepthNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gDepth);
@override @override
List<Tuple2<String, String>> get dataProps => [ List<Tuple2<String, String>> get dataProps => [
@ -70,8 +74,16 @@ class XmpGDepthNamespace extends XmpGoogleNamespace {
} }
class XmpGDeviceNamespace extends XmpNamespace { class XmpGDeviceNamespace extends XmpNamespace {
XmpGDeviceNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gDevice, nsPrefix, rawProps) { late final String _cameraNsPrefix;
final mimePattern = RegExp(nsPrefix + r'Container/Container:Directory\[(\d+)\]/Item:Mime'); late final String _containerNsPrefix;
late final String _itemNsPrefix;
XmpGDeviceNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gDevice) {
_cameraNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceCamera);
_containerNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceContainer);
_itemNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, Namespaces.gDeviceItem);
final mimePattern = RegExp(nsPrefix + r'Container/' + _containerNsPrefix + r'Directory\[(\d+)\]/' + _itemNsPrefix + r'Mime');
final originalProps = rawProps.entries.toList(); final originalProps = rawProps.entries.toList();
originalProps.forEach((kv) { originalProps.forEach((kv) {
final path = kv.key; final path = kv.key;
@ -81,7 +93,7 @@ class XmpGDeviceNamespace extends XmpNamespace {
if (indexString != null) { if (indexString != null) {
final index = int.tryParse(indexString); final index = int.tryParse(indexString);
if (index != null) { if (index != null) {
final dataPath = '${nsPrefix}Container/Container:Directory[$index]/Item:Data'; final dataPath = '${nsPrefix}Container/${_containerNsPrefix}Directory[$index]/${_itemNsPrefix}Data';
rawProps[dataPath] = '[skipped]'; rawProps[dataPath] = '[skipped]';
} }
} }
@ -94,16 +106,16 @@ class XmpGDeviceNamespace extends XmpNamespace {
XmpCardData( XmpCardData(
RegExp(nsPrefix + r'Cameras\[(\d+)\]/(.*)'), RegExp(nsPrefix + r'Cameras\[(\d+)\]/(.*)'),
cards: [ cards: [
XmpCardData(RegExp(r'Camera:DepthMap/(.*)')), XmpCardData(RegExp(_cameraNsPrefix + r'DepthMap/(.*)')),
XmpCardData(RegExp(r'Camera:Image/(.*)')), XmpCardData(RegExp(_cameraNsPrefix + r'Image/(.*)')),
XmpCardData(RegExp(r'Camera:ImagingModel/(.*)')), XmpCardData(RegExp(_cameraNsPrefix + r'ImagingModel/(.*)')),
], ],
), ),
XmpCardData( XmpCardData(
RegExp(nsPrefix + r'Container/Container:Directory\[(\d+)\]/(.*)'), RegExp(nsPrefix + r'Container/' + _containerNsPrefix + r'Directory\[(\d+)\]/(.*)'),
spanBuilders: (index, struct) { spanBuilders: (index, struct) {
if (struct.containsKey('Item:Data') && struct.containsKey('Item:DataURI')) { if (struct.containsKey('${_itemNsPrefix}Data') && struct.containsKey('${_itemNsPrefix}DataURI')) {
final dataUriProp = struct['Item:DataURI']; final dataUriProp = struct['${_itemNsPrefix}DataURI'];
if (dataUriProp != null) { if (dataUriProp != null) {
return { return {
'Data': InfoRowGroup.linkSpanBuilder( 'Data': InfoRowGroup.linkSpanBuilder(
@ -121,17 +133,17 @@ class XmpGDeviceNamespace extends XmpNamespace {
} }
class XmpGImageNamespace extends XmpGoogleNamespace { class XmpGImageNamespace extends XmpGoogleNamespace {
const XmpGImageNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.gImage, nsPrefix, rawProps); XmpGImageNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gImage);
@override @override
List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')]; List<Tuple2<String, String>> get dataProps => [Tuple2('${nsPrefix}Data', '${nsPrefix}Mime')];
} }
class XmpContainer extends XmpNamespace { class XmpGContainer extends XmpNamespace {
XmpContainer(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.container, nsPrefix, rawProps); XmpGContainer({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.gContainer);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
XmpCardData(RegExp('${nsPrefix}Directory\\[(\\d+)\\]/${nsPrefix}Item/(.*)'), title: 'Directory Item'), XmpCardData(RegExp(nsPrefix + r'Directory\[(\d+)\]/' + nsPrefix + r'Item/(.*)'), title: 'Directory Item'),
]; ];
} }

View file

@ -2,7 +2,7 @@ import 'package:aves/utils/xmp_utils.dart';
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
class XmpCreatorAtom extends XmpNamespace { class XmpCreatorAtom extends XmpNamespace {
XmpCreatorAtom(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.creatorAtom, nsPrefix, rawProps); XmpCreatorAtom({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.creatorAtom);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -11,7 +11,7 @@ class XmpCreatorAtom extends XmpNamespace {
} }
class XmpDarktableNamespace extends XmpNamespace { class XmpDarktableNamespace extends XmpNamespace {
XmpDarktableNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.darktable, nsPrefix, rawProps); XmpDarktableNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.darktable);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -20,7 +20,7 @@ class XmpDarktableNamespace extends XmpNamespace {
} }
class XmpDwcNamespace extends XmpNamespace { class XmpDwcNamespace extends XmpNamespace {
XmpDwcNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.dwc, nsPrefix, rawProps); XmpDwcNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.dwc);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -37,7 +37,7 @@ class XmpDwcNamespace extends XmpNamespace {
} }
class XmpIptcCoreNamespace extends XmpNamespace { class XmpIptcCoreNamespace extends XmpNamespace {
XmpIptcCoreNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpCore, nsPrefix, rawProps); XmpIptcCoreNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.iptc4xmpCore);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -46,7 +46,7 @@ class XmpIptcCoreNamespace extends XmpNamespace {
} }
class XmpIptc4xmpExtNamespace extends XmpNamespace { class XmpIptc4xmpExtNamespace extends XmpNamespace {
XmpIptc4xmpExtNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.iptc4xmpExt, nsPrefix, rawProps); XmpIptc4xmpExtNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.iptc4xmpExt);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -55,7 +55,7 @@ class XmpIptc4xmpExtNamespace extends XmpNamespace {
} }
class XmpMPNamespace extends XmpNamespace { class XmpMPNamespace extends XmpNamespace {
XmpMPNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mp, nsPrefix, rawProps); XmpMPNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.mp);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -65,7 +65,7 @@ class XmpMPNamespace extends XmpNamespace {
// cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15) // cf www.metadataworkinggroup.org/pdf/mwg_guidance.pdf (down, as of 2021/02/15)
class XmpMgwRegionsNamespace extends XmpNamespace { class XmpMgwRegionsNamespace extends XmpNamespace {
XmpMgwRegionsNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.mwgrs, nsPrefix, rawProps); XmpMgwRegionsNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.mwgrs);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -75,7 +75,7 @@ class XmpMgwRegionsNamespace extends XmpNamespace {
} }
class XmpPlusNamespace extends XmpNamespace { class XmpPlusNamespace extends XmpNamespace {
XmpPlusNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.plus, nsPrefix, rawProps); XmpPlusNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.plus);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [
@ -86,7 +86,7 @@ class XmpPlusNamespace extends XmpNamespace {
} }
class XmpMMNamespace extends XmpNamespace { class XmpMMNamespace extends XmpNamespace {
XmpMMNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmpMM, nsPrefix, rawProps); XmpMMNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.xmpMM);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [

View file

@ -3,7 +3,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/photoshop.md // cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/photoshop.md
class XmpPhotoshopNamespace extends XmpNamespace { class XmpPhotoshopNamespace extends XmpNamespace {
XmpPhotoshopNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.photoshop, nsPrefix, rawProps); XmpPhotoshopNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.photoshop);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [

View file

@ -4,7 +4,7 @@ import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
// cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/tiff.md // cf https://github.com/adobe/xmp-docs/blob/master/XMPNamespaces/tiff.md
class XmpTiffNamespace extends XmpNamespace { class XmpTiffNamespace extends XmpNamespace {
const XmpTiffNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.tiff, nsPrefix, rawProps); XmpTiffNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.tiff);
@override @override
String formatValue(XmpProp prop) { String formatValue(XmpProp prop) {

View file

@ -6,7 +6,7 @@ import 'package:aves/widgets/viewer/info/common.dart';
import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart'; import 'package:aves/widgets/viewer/info/metadata/xmp_namespaces.dart';
class XmpBasicNamespace extends XmpNamespace { class XmpBasicNamespace extends XmpNamespace {
XmpBasicNamespace(String nsPrefix, Map<String, String> rawProps) : super(Namespaces.xmp, nsPrefix, rawProps); XmpBasicNamespace({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: Namespaces.xmp);
@override @override
late final List<XmpCardData> cards = [ late final List<XmpCardData> cards = [

View file

@ -56,9 +56,8 @@ class _XmpDirTileState extends State<XmpDirTile> {
return nsPrefix; return nsPrefix;
}).entries.map((kv) { }).entries.map((kv) {
final nsPrefix = kv.key; final nsPrefix = kv.key;
final nsUri = _schemaRegistryPrefixes[nsPrefix] ?? '';
final rawProps = Map.fromEntries(kv.value); final rawProps = Map.fromEntries(kv.value);
return XmpNamespace.create(nsUri, nsPrefix, rawProps); return XmpNamespace.create(_schemaRegistryPrefixes, nsPrefix, rawProps);
}).toList() }).toList()
..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle)); ..sort((a, b) => compareAsciiUpperCase(a.displayTitle, b.displayTitle));
return AvesExpansionTile( return AvesExpansionTile(