Handle stds:exif C2PA assertions
This commit is contained in:
parent
ed79b3186a
commit
5c559a98ad
3 changed files with 128 additions and 66 deletions
|
|
@ -97,38 +97,6 @@ const updateDetails = () => {
|
|||
return deg;
|
||||
};
|
||||
|
||||
// Make and model
|
||||
let makeAndModel = "";
|
||||
const make = getSimpleValue("Make");
|
||||
const model = getSimpleValue("Model");
|
||||
if (make) {
|
||||
makeAndModel += make;
|
||||
}
|
||||
if (model) {
|
||||
if (makeAndModel.length > 0) {
|
||||
makeAndModel += ", ";
|
||||
}
|
||||
makeAndModel += model;
|
||||
}
|
||||
if (makeAndModel.length > 0) {
|
||||
d.push({
|
||||
icon: "$vuetify.icons.ic_exif_device_camera",
|
||||
title: t("file_mode.cc_source"),
|
||||
value: makeAndModel,
|
||||
});
|
||||
}
|
||||
|
||||
// Creation date
|
||||
const date = getSimpleValue("DateTimeOriginal");
|
||||
const dateOffset = getSimpleValue("OffsetTimeOriginal");
|
||||
if (date) {
|
||||
d.push({
|
||||
icon: "$vuetify.icons.ic_exif_time",
|
||||
title: t("file_mode.cc_capture_timestamp"),
|
||||
value: dayjs(Date.parse(date + (dateOffset ? dateOffset : ""))).format("lll"),
|
||||
});
|
||||
}
|
||||
|
||||
// Location
|
||||
try {
|
||||
const lat = toDegrees(getSimpleValue("GPSLatitude"), getSimpleValue("GPSLatitudeRef") ?? "");
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import utils from "@/plugins/utils";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export type AIInferenceResult = {
|
||||
aiGenerated: boolean;
|
||||
aiProbability: number;
|
||||
|
|
@ -61,7 +64,6 @@ export type Proof = {
|
|||
|
||||
export type ProofHintFlagsGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
||||
export type ProofHintFlagSource = "c2pa" | "exif" | "metadata";
|
||||
//export type ProofHintFlagsEditor = "unknown" | "manual" | "ai";
|
||||
|
||||
export type ProofHintFlagsEdit = {
|
||||
editor: string;
|
||||
|
|
@ -101,6 +103,66 @@ type FlagMatchInfo = {
|
|||
re: string;
|
||||
};
|
||||
|
||||
type FlagValue = {
|
||||
path: string;
|
||||
transform?: (value: any, match: FlagMatchRuleValue) => any;
|
||||
}
|
||||
|
||||
const pathsC2PASource = (): FlagValue[] => {
|
||||
return [
|
||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Make", transform: getExifMakeModel },
|
||||
{path: "integrity/c2pa/manifest_info/manifests[]/assertions[label=stds.exif]/data/exif:Model", transform: getExifMakeModel },
|
||||
{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 pathsC2PACreationDate = (): FlagValue[] => {
|
||||
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]/../../../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
|
||||
let offset = extractFlagValues("../exif:OffsetTimeOriginal", match.path)?.at(0)?.value;
|
||||
return dayjs(Date.parse(value + (offset ? offset : ""))).format("lll");
|
||||
}}
|
||||
];
|
||||
}
|
||||
|
||||
const getExifValue = (val: string): string => {
|
||||
return val.replace(/^"(.+(?="$))"$/, "$1");
|
||||
};
|
||||
|
||||
const pathsExifCreationDate = (): FlagValue[] => {
|
||||
return [
|
||||
{path: "integrity/exif/DateTimeOriginal", transform: (value, match) => {
|
||||
let dateTimeOriginal = getExifValue(value);
|
||||
// Check if we have a timestamp offset
|
||||
let offset = extractFlagValues("../OffsetTimeOriginal", match.path)?.at(0)?.value as string;
|
||||
return dayjs(Date.parse(dateTimeOriginal + (offset ? getExifValue(offset) : ""))).format("lll");
|
||||
}}
|
||||
];
|
||||
}
|
||||
|
||||
const getExifMakeModel = (ignoredvalue: any, match: FlagMatchRuleValue): string => {
|
||||
// Make and model
|
||||
let makeAndModel = "";
|
||||
const make = extractFlagValues("../exif:Make", match.path)?.at(0)?.value as string;
|
||||
const model = extractFlagValues("../exif:Model", match.path)?.at(0)?.value as string;
|
||||
if (make) {
|
||||
makeAndModel += make;
|
||||
}
|
||||
if (model) {
|
||||
if (makeAndModel.length > 0) {
|
||||
makeAndModel += ", ";
|
||||
}
|
||||
makeAndModel += model;
|
||||
}
|
||||
return makeAndModel;
|
||||
}
|
||||
|
||||
const ruleScreenshotC2PA = (): FlagMatchRule[] => {
|
||||
return [
|
||||
{
|
||||
|
|
@ -245,7 +307,8 @@ const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]):
|
|||
opart = opart.filter((item) => item[prop] !== val);
|
||||
} else {
|
||||
const [prop, val] = optionalConstraint.split("=");
|
||||
opart = opart.filter((item) => item[prop] === val);
|
||||
let valarray = val.split("|");
|
||||
opart = opart.filter((item) => valarray.includes(item[prop]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,35 +353,46 @@ const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]):
|
|||
return result;
|
||||
};
|
||||
|
||||
const getFirstWithData = (flagPaths: string[], path: FlagMatchRulePathSegment[]): string | undefined => {
|
||||
for (let idx = 0; idx < flagPaths.length; idx++) {
|
||||
const result = extractFlagValues(flagPaths[idx], path);
|
||||
const getFirstWithData = (flagValues: FlagValue[], path: FlagMatchRulePathSegment[]): string | undefined => {
|
||||
let first: FlagMatchRuleValue | undefined = undefined;
|
||||
let transform: ((value: any, match: FlagMatchRuleValue) => any) | undefined = undefined;
|
||||
for (let idx = 0; idx < flagValues.length; idx++) {
|
||||
const result = extractFlagValues(flagValues[idx].path, path);
|
||||
if (result.length > 0) {
|
||||
return result[0].value as string;
|
||||
first = result[0]
|
||||
transform = flagValues[idx].transform;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (first && first.value as string) {
|
||||
try {
|
||||
let val = first.value as string;
|
||||
if (val && transform) {
|
||||
val = transform(val, first);
|
||||
}
|
||||
return val;
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getFirstWithDataAsDate = (flagPaths: string[], path: FlagMatchRulePathSegment[]): Date | undefined => {
|
||||
const val = getFirstWithData(flagPaths, path);
|
||||
const getFirstWithDataAsDate = (flagValues: FlagValue[], path: FlagMatchRulePathSegment[]): Date | undefined => {
|
||||
let val = getFirstWithData(flagValues, path);
|
||||
if (val) {
|
||||
try {
|
||||
const date = new Date(Date.parse(val));
|
||||
let date = new Date(Date.parse(val));
|
||||
if (isNaN(date.valueOf())) {
|
||||
// Try EXIF format
|
||||
date = utils.parseExifDate(val);
|
||||
}
|
||||
return date;
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const softwareAgentFromAction = (action: FlagMatchRuleValue): string | undefined => {
|
||||
const agent = getFirstWithData([
|
||||
"softwareAgent/name", "softwareAgent", "../../../claim_generator"
|
||||
], action.path);
|
||||
return agent;
|
||||
};
|
||||
|
||||
export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined => {
|
||||
if (!proof) return undefined;
|
||||
|
||||
|
|
@ -337,22 +411,16 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
|
|||
let dateCreated: Date | undefined = undefined;
|
||||
let dateCreatedSource: ProofHintFlagSource | undefined = undefined;
|
||||
|
||||
let creationAction = extractFlagValues(
|
||||
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]",
|
||||
rootMatchPath
|
||||
);
|
||||
if (creationAction.length == 0) {
|
||||
creationAction = extractFlagValues(
|
||||
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions.v2]/data/actions[action=c2pa.created]",
|
||||
rootMatchPath
|
||||
);
|
||||
}
|
||||
if (creationAction.length > 0) {
|
||||
source = softwareAgentFromAction(creationAction[0]);
|
||||
dateCreated = getFirstWithDataAsDate(["when", "../../metadata/dateTime", "../../../signature_info/time"], creationAction[0].path);
|
||||
source = getFirstWithData(pathsC2PASource(), rootMatchPath);
|
||||
dateCreated = getFirstWithDataAsDate(pathsC2PACreationDate(), rootMatchPath);
|
||||
if (dateCreated) {
|
||||
dateCreatedSource = "c2pa";
|
||||
} else {
|
||||
dateCreated = getFirstWithDataAsDate(pathsExifCreationDate(), rootMatchPath);
|
||||
if (dateCreated) {
|
||||
dateCreatedSource = "exif";
|
||||
}
|
||||
}
|
||||
console.log("DATE CREATED", dateCreated);
|
||||
|
||||
let generator: ProofHintFlagsGenerator = matchFlag(ruleAiGenerated(), rootMatchPath).result ? "ai" : matchFlag(ruleScreenshotC2PA(), rootMatchPath).result ? "screenshot" : matchFlag(ruleCamera(), rootMatchPath).result ? "camera" : "unknown";
|
||||
let generatorSource: ProofHintFlagSource | undefined = undefined;
|
||||
|
|
@ -378,8 +446,16 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
|
|||
if (c2paEdits.length > 0) {
|
||||
edits = c2paEdits.map((edit) => {
|
||||
return {
|
||||
editor: softwareAgentFromAction(edit) ?? "",
|
||||
date: getFirstWithDataAsDate(["when", "../../metadata/dateTime", "../../../signature_info/time"], edit.path)
|
||||
editor: getFirstWithData([
|
||||
{path: "softwareAgent/name"},
|
||||
{path: "softwareAgent"},
|
||||
{path: "../../../claim_generator"}
|
||||
], edit.path) ?? "",
|
||||
date: getFirstWithDataAsDate([
|
||||
{path: "when"},
|
||||
{path: "../../metadata/dateTime"},
|
||||
{path: "../../../signature_info/time"}
|
||||
], edit.path)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1040,6 +1040,24 @@ class Util {
|
|||
return then.format("lll");
|
||||
}
|
||||
|
||||
parseExifDate(exifDateString) {
|
||||
// Use a regular expression to match and capture the date/time components.
|
||||
const regex = /(\d{4}):(\d{2}):(\d{2})\s(\d{2}):(\d{2}):(\d{2})/;
|
||||
const match = exifDateString.match(regex);
|
||||
|
||||
if (match) {
|
||||
// Extract components and convert to numbers. Note that months are 0-indexed.
|
||||
const year = parseInt(match[1], 10);
|
||||
const month = parseInt(match[2], 10) - 1;
|
||||
const day = parseInt(match[3], 10);
|
||||
const hour = parseInt(match[4], 10);
|
||||
const minute = parseInt(match[5], 10);
|
||||
const second = parseInt(match[6], 10);
|
||||
return new Date(year, month, day, hour, minute, second);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
browserCanRecordAudio() {
|
||||
return _browserCanRecordAudio;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue