Pick up location from C2PA or Exif
This commit is contained in:
parent
d6c8146096
commit
64b14cef40
2 changed files with 197 additions and 113 deletions
|
|
@ -88,43 +88,18 @@ const updateDetails = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const exif = props.proof?.integrity?.exif;
|
if (props.metadata?.location) {
|
||||||
if (exif) {
|
const lat = props.metadata.location.latitude;
|
||||||
const getSimpleValue = (key: string): string | undefined => {
|
const lon = props.metadata.location.longitude;
|
||||||
return exif ? (exif[key] as string)?.replace(/^"(.+(?="$))"$/, "$1") : undefined;
|
const location = lat + " " + lon;
|
||||||
};
|
const link = "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(lat) + "," + encodeURIComponent(lon);
|
||||||
|
d.push({
|
||||||
const toDegrees = (dms: string | undefined, direction: string) => {
|
icon: "$vuetify.icons.ic_exif_location",
|
||||||
if (!dms || dms.length == 0) return undefined;
|
title: t("file_mode.cc_location"),
|
||||||
var parts = dms.split(/deg|min|sec/);
|
value: location,
|
||||||
var d = parts[0];
|
link: link,
|
||||||
var m = parts[1];
|
});
|
||||||
var s = parts[2];
|
|
||||||
var deg = (Number(d) + Number(m) / 60 + Number(s) / 3600).toFixed(6);
|
|
||||||
if (direction == "S" || direction == "W") {
|
|
||||||
deg = "-" + deg;
|
|
||||||
}
|
|
||||||
return deg;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Location
|
|
||||||
try {
|
|
||||||
const lat = toDegrees(getSimpleValue("GPSLatitude"), getSimpleValue("GPSLatitudeRef") ?? "");
|
|
||||||
const lon = toDegrees(getSimpleValue("GPSLongitude"), getSimpleValue("GPSLongitudeRef") ?? "");
|
|
||||||
if (lat && lon && lat.length > 0 && lon.length > 0) {
|
|
||||||
const location = lat + " " + lon;
|
|
||||||
const link =
|
|
||||||
"https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(lat) + "," + encodeURIComponent(lon);
|
|
||||||
d.push({
|
|
||||||
icon: "$vuetify.icons.ic_exif_location",
|
|
||||||
title: t("file_mode.cc_location"),
|
|
||||||
value: location,
|
|
||||||
link: link,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
details.value = d;
|
details.value = d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,13 +62,19 @@ export type Proof = {
|
||||||
ai?: { inferenceResult?: AIInferenceResult };
|
ai?: { inferenceResult?: AIInferenceResult };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MediaMetadataGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
export type MediaMetadataGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
||||||
export type MediaMetadataPropertySource = "c2pa" | "exif" | "metadata";
|
export type MediaMetadataPropertySource = "c2pa" | "exif" | "metadata";
|
||||||
|
|
||||||
export type MediaMetadataEdit = {
|
export type MediaMetadataEdit = {
|
||||||
editor: string;
|
editor: string;
|
||||||
date?: Date;
|
date?: Date;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type MediaMetadataLocation = {
|
||||||
|
latitude: string;
|
||||||
|
longitude: string;
|
||||||
|
source: MediaMetadataPropertySource;
|
||||||
|
};
|
||||||
|
|
||||||
export type MediaInterventionFlags = {
|
export type MediaInterventionFlags = {
|
||||||
creationDate?: Date;
|
creationDate?: Date;
|
||||||
|
|
@ -76,7 +82,7 @@ export type MediaInterventionFlags = {
|
||||||
modified?: boolean;
|
modified?: boolean;
|
||||||
containsC2PA?: boolean;
|
containsC2PA?: boolean;
|
||||||
containsEXIF?: boolean;
|
containsEXIF?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type MediaMetadata = {
|
export type MediaMetadata = {
|
||||||
device?: string;
|
device?: string;
|
||||||
|
|
@ -87,6 +93,7 @@ export type MediaMetadata = {
|
||||||
edits?: MediaMetadataEdit[];
|
edits?: MediaMetadataEdit[];
|
||||||
containsC2PA?: boolean;
|
containsC2PA?: boolean;
|
||||||
containsEXIF?: boolean;
|
containsEXIF?: boolean;
|
||||||
|
location?: MediaMetadataLocation;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FlagMatchRule = {
|
type FlagMatchRule = {
|
||||||
|
|
@ -98,7 +105,7 @@ type FlagMatchRule = {
|
||||||
type FlagMatchRulePathSegment = {
|
type FlagMatchRulePathSegment = {
|
||||||
object: any;
|
object: any;
|
||||||
path: string;
|
path: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type FlagMatchRuleValue = {
|
type FlagMatchRuleValue = {
|
||||||
path: FlagMatchRulePathSegment[];
|
path: FlagMatchRulePathSegment[];
|
||||||
|
|
@ -114,51 +121,92 @@ type FlagMatchInfo = {
|
||||||
type FlagValue = {
|
type FlagValue = {
|
||||||
path: string;
|
path: string;
|
||||||
transform?: (value: any, match: FlagMatchRuleValue) => any;
|
transform?: (value: any, match: FlagMatchRuleValue) => any;
|
||||||
}
|
};
|
||||||
|
|
||||||
const pathsC2PASource = (): FlagValue[] => {
|
const pathsC2PASource = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Make", transform: getExifMakeModelPrefixed },
|
{
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Model", transform: getExifMakeModelPrefixed },
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Make",
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/softwareAgent/name"},
|
transform: getExifMakeModelPrefixed,
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/softwareAgent"},
|
},
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../../claim_generator"}
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Model",
|
||||||
|
transform: getExifMakeModelPrefixed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/softwareAgent/name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/softwareAgent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../../claim_generator",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const pathsExifSource = (): FlagValue[] => {
|
const pathsExifSource = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/exif/Make", transform: getExifMakeModelNoPrefix },
|
{ path: "integrity/exif/Make", transform: getExifMakeModelNoPrefix },
|
||||||
{path: "integrity/exif/Model", transform: getExifMakeModelNoPrefix }
|
{ path: "integrity/exif/Model", transform: getExifMakeModelNoPrefix },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const pathsC2PACreationDate = (): FlagValue[] => {
|
const pathsC2PACreationDate = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/when"},
|
{
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../metadata/dateTime"},
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/when",
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../../signature_info/time"},
|
},
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:DateTimeOriginal", transform: (value, match) => {
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../metadata/dateTime",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/../../../signature_info/time",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:DateTimeOriginal",
|
||||||
|
transform: (value, match) => {
|
||||||
// Check if we have a timestamp offset
|
// Check if we have a timestamp offset
|
||||||
let offset = extractFlagValues("../exif:OffsetTimeOriginal", match.path)?.at(0)?.value;
|
let offset = extractFlagValues("../exif:OffsetTimeOriginal", match.path)?.at(0)?.value;
|
||||||
return dayjs(Date.parse(value + (offset ? offset : ""))).format("lll");
|
return dayjs(Date.parse(value + (offset ? offset : ""))).format("lll");
|
||||||
}}
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
const pathsC2PACamera = (): FlagValue[] => {
|
const pathsC2PACamera = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data{exif:SceneType=1|directly photographed image}"},
|
{
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:LensMake"},
|
path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data{exif:SceneType=1|directly photographed image}",
|
||||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:LensModel"},
|
},
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:LensMake" },
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:LensModel" },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const pathsExifCamera = (): FlagValue[] => {
|
const pathsExifCamera = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/exif{SceneType=1|directly photographed image}"},
|
{ path: "integrity/exif{SceneType=1|directly photographed image}" },
|
||||||
{path: "integrity/exif/LensMake"},
|
{ path: "integrity/exif/LensMake" },
|
||||||
{path: "integrity/exif/LensModel"},
|
{ path: "integrity/exif/LensModel" },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const pathsC2PALocation = (): FlagValue[] => {
|
||||||
|
return [
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/GPSLatitude" },
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/GPSLongitude" },
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/GPSLatitudeRef" },
|
||||||
|
{ path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/GPSLongitudeRef" },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const pathsExifLocation = (): FlagValue[] => {
|
||||||
|
return [
|
||||||
|
{ path: "integrity/exif/GPSLatitude" },
|
||||||
|
{ path: "integrity/exif/GPSLongitude" },
|
||||||
|
{ path: "integrity/exif/GPSLatitudeRef" },
|
||||||
|
{ path: "integrity/exif/GPSLongitudeRef" },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -166,47 +214,62 @@ const getExifValue = (val: string): string => {
|
||||||
return val.replace(/^"(.+(?="$))"$/, "$1");
|
return val.replace(/^"(.+(?="$))"$/, "$1");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toDegrees = (dms: string | undefined, direction: string) => {
|
||||||
|
if (!dms || dms.length == 0) return undefined;
|
||||||
|
var parts = dms.split(/deg|min|sec/);
|
||||||
|
var d = parts[0];
|
||||||
|
var m = parts[1];
|
||||||
|
var s = parts[2];
|
||||||
|
var deg = (Number(d) + Number(m) / 60 + Number(s) / 3600).toFixed(6);
|
||||||
|
if (direction == "S" || direction == "W") {
|
||||||
|
deg = "-" + deg;
|
||||||
|
}
|
||||||
|
return deg;
|
||||||
|
};
|
||||||
|
|
||||||
const pathsExifCreationDate = (): FlagValue[] => {
|
const pathsExifCreationDate = (): FlagValue[] => {
|
||||||
return [
|
return [
|
||||||
{path: "integrity/exif/DateTimeOriginal", transform: (value, match) => {
|
{
|
||||||
|
path: "integrity/exif/DateTimeOriginal",
|
||||||
|
transform: (value, match) => {
|
||||||
let dateTimeOriginal = getExifValue(value);
|
let dateTimeOriginal = getExifValue(value);
|
||||||
// Check if we have a timestamp offset
|
// Check if we have a timestamp offset
|
||||||
let offset = extractFlagValues("../OffsetTimeOriginal", match.path)?.at(0)?.value as string;
|
let offset = extractFlagValues("../OffsetTimeOriginal", match.path)?.at(0)?.value as string;
|
||||||
return dayjs(Date.parse(dateTimeOriginal + (offset ? getExifValue(offset) : ""))).format("lll");
|
return dayjs(Date.parse(dateTimeOriginal + (offset ? getExifValue(offset) : ""))).format("lll");
|
||||||
}}
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
};
|
||||||
|
|
||||||
const getExifMakeModelNoPrefix = (ignoredvalue: any, match: FlagMatchRuleValue): string => {
|
const getExifMakeModelNoPrefix = (ignoredvalue: any, match: FlagMatchRuleValue): string => {
|
||||||
return getExifMakeModel(match, "");
|
return getExifMakeModel(match, "");
|
||||||
}
|
};
|
||||||
|
|
||||||
const getExifMakeModelPrefixed = (ignoredvalue: any, match: FlagMatchRuleValue): string => {
|
const getExifMakeModelPrefixed = (ignoredvalue: any, match: FlagMatchRuleValue): string => {
|
||||||
return getExifMakeModel(match, "exif:");
|
return getExifMakeModel(match, "exif:");
|
||||||
}
|
};
|
||||||
|
|
||||||
const getExifMakeModel = (match: FlagMatchRuleValue, prefix: string): string => {
|
const getExifMakeModel = (match: FlagMatchRuleValue, prefix: string): string => {
|
||||||
// Make and model
|
// Make and model
|
||||||
let makeAndModel = "";
|
let makeAndModel = "";
|
||||||
const make = extractFlagValues(`../${prefix}Make`, match.path)?.at(0)?.value as string;
|
const make = extractFlagValues(`../${prefix}Make`, match.path)?.at(0)?.value as string;
|
||||||
const model = extractFlagValues(`../${prefix}Model`, match.path)?.at(0)?.value as string;
|
const model = extractFlagValues(`../${prefix}Model`, match.path)?.at(0)?.value as string;
|
||||||
if (make) {
|
if (make) {
|
||||||
makeAndModel += getExifValue(make);
|
makeAndModel += getExifValue(make);
|
||||||
|
}
|
||||||
|
if (model) {
|
||||||
|
if (makeAndModel.length > 0) {
|
||||||
|
makeAndModel += ", ";
|
||||||
}
|
}
|
||||||
if (model) {
|
makeAndModel += getExifValue(model);
|
||||||
if (makeAndModel.length > 0) {
|
}
|
||||||
makeAndModel += ", ";
|
return makeAndModel;
|
||||||
}
|
};
|
||||||
makeAndModel += getExifValue(model);
|
|
||||||
}
|
|
||||||
return makeAndModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ruleScreenshotMeta = (): FlagMatchRule[] => {
|
const ruleScreenshotMeta = (): FlagMatchRule[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field:
|
field: "name",
|
||||||
"name",
|
|
||||||
match: ["screenshot"],
|
match: ["screenshot"],
|
||||||
description: "Screen capture",
|
description: "Screen capture",
|
||||||
},
|
},
|
||||||
|
|
@ -260,10 +323,7 @@ const matchFlag = (rules: FlagMatchRule[], path: FlagMatchRulePathSegment[]) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]): FlagMatchRuleValue[] => {
|
const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]): FlagMatchRuleValue[] => {
|
||||||
const getValues = (
|
const getValues = (keys: string[], path: FlagMatchRulePathSegment[]): FlagMatchRuleValue[] | undefined => {
|
||||||
keys: string[],
|
|
||||||
path: FlagMatchRulePathSegment[]
|
|
||||||
): FlagMatchRuleValue[] | undefined => {
|
|
||||||
if (keys.length == 0 || path.length == 0) return undefined;
|
if (keys.length == 0 || path.length == 0) return undefined;
|
||||||
|
|
||||||
const o = path[0].object;
|
const o = path[0].object;
|
||||||
|
|
@ -289,7 +349,13 @@ const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]):
|
||||||
}
|
}
|
||||||
|
|
||||||
let nextObject = o[key];
|
let nextObject = o[key];
|
||||||
let omatches: any[] = nextObject ? (Array.isArray(nextObject) ? nextObject : hasBracket ? Object.values(nextObject) : [nextObject]) : [];
|
let omatches: any[] = nextObject
|
||||||
|
? Array.isArray(nextObject)
|
||||||
|
? nextObject
|
||||||
|
: hasBracket
|
||||||
|
? Object.values(nextObject)
|
||||||
|
: [nextObject]
|
||||||
|
: [];
|
||||||
|
|
||||||
// Any constraints controlling what array object(s) to consider?
|
// Any constraints controlling what array object(s) to consider?
|
||||||
if (optionalConstraint) {
|
if (optionalConstraint) {
|
||||||
|
|
@ -321,7 +387,7 @@ const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]):
|
||||||
return { value: oin, path: [{ object: oin, path: key + (omatches.length > 1 ? `[${i}]` : "") }, ...path] };
|
return { value: oin, path: [{ object: oin, path: key + (omatches.length > 1 ? `[${i}]` : "") }, ...path] };
|
||||||
});
|
});
|
||||||
} else if (omatches.length == 1) {
|
} else if (omatches.length == 1) {
|
||||||
return getValues(keys.slice(1), [{object: omatches[0], path: key}, ...path]);
|
return getValues(keys.slice(1), [{ object: omatches[0], path: key }, ...path]);
|
||||||
} else {
|
} else {
|
||||||
return omatches.reduce((res: FlagMatchRuleValue[] | undefined, oin: any, i: number) => {
|
return omatches.reduce((res: FlagMatchRuleValue[] | undefined, oin: any, i: number) => {
|
||||||
let matches = getValues(keys.slice(1), [{ object: oin, path: key + "[" + i + "]" }, ...path]);
|
let matches = getValues(keys.slice(1), [{ object: oin, path: key + "[" + i + "]" }, ...path]);
|
||||||
|
|
@ -354,20 +420,19 @@ const getFirstWithData = (flagValues: FlagValue[], path: FlagMatchRulePathSegmen
|
||||||
for (let idx = 0; idx < flagValues.length; idx++) {
|
for (let idx = 0; idx < flagValues.length; idx++) {
|
||||||
const result = extractFlagValues(flagValues[idx].path, path);
|
const result = extractFlagValues(flagValues[idx].path, path);
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
first = result[0]
|
first = result[0];
|
||||||
transform = flagValues[idx].transform;
|
transform = flagValues[idx].transform;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (first && first.value as string) {
|
if (first && (first.value as string)) {
|
||||||
try {
|
try {
|
||||||
let val = first.value as string;
|
let val = first.value as string;
|
||||||
if (val && transform) {
|
if (val && transform) {
|
||||||
val = transform(val, first);
|
val = transform(val, first);
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
@ -382,21 +447,33 @@ const getFirstWithDataAsDate = (flagValues: FlagValue[], path: FlagMatchRulePath
|
||||||
date = utils.parseExifDate(val);
|
date = utils.parseExifDate(val);
|
||||||
}
|
}
|
||||||
return date;
|
return date;
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMultiple = (flagValues: FlagValue[], path: FlagMatchRulePathSegment[]): (string | undefined)[] => {
|
||||||
|
let results: (string | undefined)[] = new Array(flagValues.length);
|
||||||
|
for (let idx = 0; idx < flagValues.length; idx++) {
|
||||||
|
const result = extractFlagValues(flagValues[idx].path, path);
|
||||||
|
if (result.length > 0) {
|
||||||
|
results[idx] = result[0].value as string;
|
||||||
|
} else {
|
||||||
|
results[idx] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
export const mediaMetadataToMediaInterventionFlags = (mediaMetadata: MediaMetadata): MediaInterventionFlags => {
|
export const mediaMetadataToMediaInterventionFlags = (mediaMetadata: MediaMetadata): MediaInterventionFlags => {
|
||||||
return {
|
return {
|
||||||
creationDate: mediaMetadata.creationDate,
|
creationDate: mediaMetadata.creationDate,
|
||||||
generator: mediaMetadata.generator,
|
generator: mediaMetadata.generator,
|
||||||
modified: mediaMetadata.edits && mediaMetadata.edits.length > 0,
|
modified: mediaMetadata.edits && mediaMetadata.edits.length > 0,
|
||||||
containsC2PA: mediaMetadata.containsC2PA,
|
containsC2PA: mediaMetadata.containsC2PA,
|
||||||
containsEXIF: mediaMetadata.containsEXIF
|
containsEXIF: mediaMetadata.containsEXIF,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined => {
|
export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined => {
|
||||||
if (!proof) return undefined;
|
if (!proof) return undefined;
|
||||||
|
|
@ -410,7 +487,7 @@ export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined =
|
||||||
valid = results.failure.length == 0 && results.success.length > 0;
|
valid = results.failure.length == 0 && results.success.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootMatchPath = [{object: proof, path: ""}];
|
const rootMatchPath = [{ object: proof, path: "" }];
|
||||||
|
|
||||||
let source: string | undefined = undefined;
|
let source: string | undefined = undefined;
|
||||||
let dateCreated: Date | undefined = undefined;
|
let dateCreated: Date | undefined = undefined;
|
||||||
|
|
@ -431,18 +508,28 @@ export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined =
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let generator: MediaMetadataGenerator = "unknown";
|
let generator: MediaMetadataGenerator = "unknown";
|
||||||
let generatorSource: MediaMetadataPropertySource | undefined = undefined;
|
let generatorSource: MediaMetadataPropertySource | undefined = undefined;
|
||||||
|
|
||||||
let digitalSourceType = extractFlagValues("integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType", rootMatchPath)?.at(0)?.value as string;
|
let digitalSourceType = extractFlagValues(
|
||||||
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType",
|
||||||
|
rootMatchPath
|
||||||
|
)?.at(0)?.value as string;
|
||||||
if ([C2PASourceTypeScreenCapture].includes(digitalSourceType)) {
|
if ([C2PASourceTypeScreenCapture].includes(digitalSourceType)) {
|
||||||
generator = "screenshot";
|
generator = "screenshot";
|
||||||
generatorSource = "c2pa";
|
generatorSource = "c2pa";
|
||||||
} else if ([C2PASourceTypeDigitalCapture, C2PASourceTypeComputationalCapture, C2PASourceTypeCompositeCapture].includes(digitalSourceType)) {
|
} else if (
|
||||||
|
[C2PASourceTypeDigitalCapture, C2PASourceTypeComputationalCapture, C2PASourceTypeCompositeCapture].includes(
|
||||||
|
digitalSourceType
|
||||||
|
)
|
||||||
|
) {
|
||||||
generator = "camera";
|
generator = "camera";
|
||||||
generatorSource = "c2pa";
|
generatorSource = "c2pa";
|
||||||
} else if ([C2PASourceTypeTrainedAlgorithmicMedia, C2PASourceTypeCompositeWithTrainedAlgorithmicMedia].includes(digitalSourceType)) {
|
} else if (
|
||||||
|
[C2PASourceTypeTrainedAlgorithmicMedia, C2PASourceTypeCompositeWithTrainedAlgorithmicMedia].includes(
|
||||||
|
digitalSourceType
|
||||||
|
)
|
||||||
|
) {
|
||||||
generator = "ai";
|
generator = "ai";
|
||||||
generatorSource = "c2pa";
|
generatorSource = "c2pa";
|
||||||
} else if (getFirstWithData(pathsC2PACamera(), rootMatchPath)) {
|
} else if (getFirstWithData(pathsC2PACamera(), rootMatchPath)) {
|
||||||
|
|
@ -468,20 +555,41 @@ export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined =
|
||||||
if (c2paEdits.length > 0) {
|
if (c2paEdits.length > 0) {
|
||||||
edits = c2paEdits.map((edit) => {
|
edits = c2paEdits.map((edit) => {
|
||||||
return {
|
return {
|
||||||
editor: getFirstWithData([
|
editor:
|
||||||
{path: "softwareAgent/name"},
|
getFirstWithData(
|
||||||
{path: "softwareAgent"},
|
[{ path: "softwareAgent/name" }, { path: "softwareAgent" }, { path: "../../../claim_generator" }],
|
||||||
{path: "../../../claim_generator"}
|
edit.path
|
||||||
], edit.path) ?? "",
|
) ?? "",
|
||||||
date: getFirstWithDataAsDate([
|
date: getFirstWithDataAsDate(
|
||||||
{path: "when"},
|
[{ path: "when" }, { path: "../../metadata/dateTime" }, { path: "../../../signature_info/time" }],
|
||||||
{path: "../../metadata/dateTime"},
|
edit.path
|
||||||
{path: "../../../signature_info/time"}
|
),
|
||||||
], edit.path)
|
};
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Location
|
||||||
|
let location: MediaMetadataLocation | undefined = undefined;
|
||||||
|
let locationSource: MediaMetadataPropertySource = "c2pa"
|
||||||
|
let [lat, lon, latref, lonref] = getMultiple(pathsC2PALocation(), rootMatchPath);
|
||||||
|
if (!lat || !lon) {
|
||||||
|
[lat, lon, latref, lonref] = getMultiple(pathsExifLocation(), rootMatchPath);
|
||||||
|
locationSource = "exif"
|
||||||
|
}
|
||||||
|
if (lat && lon) {
|
||||||
|
try {
|
||||||
|
const latitude = toDegrees(getExifValue(lat), getExifValue(latref ?? ""));
|
||||||
|
const longitude = toDegrees(getExifValue(lon), getExifValue(lonref ?? ""));
|
||||||
|
if (latitude && longitude && latitude.length > 0 && longitude.length > 0) {
|
||||||
|
location = {
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
source: "exif",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
// Do we have any data? Else, return "undefined", we don't just want to send an object with all defaults.
|
// Do we have any data? Else, return "undefined", we don't just want to send an object with all defaults.
|
||||||
if (source === undefined && dateCreated === undefined && generator === "unknown" && (!edits || edits.length == 0)) {
|
if (source === undefined && dateCreated === undefined && generator === "unknown" && (!edits || edits.length == 0)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
@ -495,7 +603,8 @@ export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined =
|
||||||
generatorSource: generatorSource,
|
generatorSource: generatorSource,
|
||||||
edits: edits,
|
edits: edits,
|
||||||
containsC2PA: proof.integrity?.c2pa !== undefined,
|
containsC2PA: proof.integrity?.c2pa !== undefined,
|
||||||
containsEXIF: proof.integrity?.exif !== undefined
|
containsEXIF: proof.integrity?.exif !== undefined,
|
||||||
|
location: location,
|
||||||
};
|
};
|
||||||
return flags;
|
return flags;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue