2025-07-04 12:51:57 +02:00
|
|
|
export type AIInferenceResult = {
|
|
|
|
|
aiGenerated: boolean;
|
|
|
|
|
aiProbability: number;
|
|
|
|
|
humanProbability: number;
|
2025-08-28 10:27:57 +02:00
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
2025-07-11 14:36:58 +02:00
|
|
|
export const C2PASourceTypeScreenCapture = "http://cv.iptc.org/newscodes/digitalsourcetype/screenCapture";
|
|
|
|
|
export const C2PASourceTypeDigitalCapture = "http://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture";
|
|
|
|
|
export const C2PASourceTypeComputationalCapture = "http://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture";
|
|
|
|
|
export const C2PASourceTypeCompositeCapture = "http://cv.iptc.org/newscodes/digitalsourcetype/compositeCapture";
|
2025-08-28 10:27:57 +02:00
|
|
|
export const C2PASourceTypeTrainedAlgorithmicMedia =
|
|
|
|
|
"http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia";
|
|
|
|
|
export const C2PASourceTypeCompositeWithTrainedAlgorithmicMedia =
|
|
|
|
|
"http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia";
|
2025-07-11 14:36:58 +02:00
|
|
|
|
2025-07-04 12:51:57 +02:00
|
|
|
export type C2PAActionsAssertion = {
|
2025-08-28 10:27:57 +02:00
|
|
|
actions: {
|
|
|
|
|
action: string;
|
|
|
|
|
softwareAgent?: string;
|
|
|
|
|
digitalSourceType?: string;
|
|
|
|
|
}[];
|
|
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
|
|
|
|
export type C2PAAssertion = {
|
2025-08-28 10:27:57 +02:00
|
|
|
label: string;
|
|
|
|
|
data: C2PAActionsAssertion | undefined;
|
|
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
|
|
|
|
export type C2PAManifest = {
|
2025-08-28 10:27:57 +02:00
|
|
|
assertions: C2PAAssertion[];
|
|
|
|
|
signature_info: {
|
|
|
|
|
time: string;
|
|
|
|
|
};
|
|
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
2025-07-11 14:36:58 +02:00
|
|
|
export type C2PAValidationResults = {
|
2025-08-28 10:27:57 +02:00
|
|
|
activeManifest?: {
|
|
|
|
|
failure: any[];
|
|
|
|
|
success: any[];
|
|
|
|
|
informational: any[];
|
|
|
|
|
};
|
|
|
|
|
};
|
2025-07-11 14:36:58 +02:00
|
|
|
|
2025-07-04 12:51:57 +02:00
|
|
|
export type C2PAManifestInfo = {
|
2025-08-28 10:27:57 +02:00
|
|
|
active_manifest: string;
|
|
|
|
|
manifests: { [key: string]: C2PAManifest };
|
|
|
|
|
validation_results?: C2PAValidationResults;
|
|
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
|
|
|
|
export type C2PAData = {
|
2025-08-28 10:27:57 +02:00
|
|
|
manifest_info: C2PAManifestInfo;
|
|
|
|
|
};
|
2025-07-04 12:51:57 +02:00
|
|
|
|
|
|
|
|
export type Proof = {
|
2025-08-28 10:27:57 +02:00
|
|
|
data?: any;
|
|
|
|
|
name?: string;
|
|
|
|
|
json?: string;
|
|
|
|
|
integrity?: { pgp?: any; c2pa?: C2PAData; exif?: { [key: string]: string | Object }; opentimestamps?: any };
|
|
|
|
|
ai?: { inferenceResult?: AIInferenceResult };
|
|
|
|
|
};
|
2025-08-20 15:12:04 +02:00
|
|
|
|
2025-09-09 10:56:15 +02:00
|
|
|
export type ProofHintFlagsGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
2025-09-23 17:17:26 +02:00
|
|
|
export type ProofHintFlagSource = "c2pa" | "exif" | "metadata";
|
2025-09-23 12:52:52 +02:00
|
|
|
//export type ProofHintFlagsEditor = "unknown" | "manual" | "ai";
|
2025-09-09 10:56:15 +02:00
|
|
|
|
|
|
|
|
export type ProofHintFlagsEdit = {
|
2025-09-23 12:52:52 +02:00
|
|
|
editor: string;
|
2025-09-09 10:56:15 +02:00
|
|
|
date?: Date;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 15:12:04 +02:00
|
|
|
export type ProofHintFlags = {
|
2025-09-09 10:56:15 +02:00
|
|
|
device?: string;
|
2025-09-05 11:16:50 +02:00
|
|
|
creationDate?: Date;
|
2025-09-23 17:17:26 +02:00
|
|
|
creationDateSource?: ProofHintFlagSource;
|
2025-09-09 10:56:15 +02:00
|
|
|
generator?: ProofHintFlagsGenerator;
|
2025-09-23 17:17:26 +02:00
|
|
|
generatorSource?: ProofHintFlagSource;
|
2025-09-09 10:56:15 +02:00
|
|
|
edits?: ProofHintFlagsEdit[];
|
2025-09-23 16:10:28 +02:00
|
|
|
containsC2PA?: boolean;
|
|
|
|
|
containsEXIF?: boolean;
|
2025-08-28 10:27:57 +02:00
|
|
|
};
|
2025-08-20 15:12:04 +02:00
|
|
|
|
2025-08-28 10:27:57 +02:00
|
|
|
type FlagMatchRule = {
|
|
|
|
|
field: string;
|
|
|
|
|
match: string[];
|
|
|
|
|
description: string;
|
|
|
|
|
};
|
2025-08-20 15:12:04 +02:00
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
type FlagMatchRulePathSegment = {
|
|
|
|
|
object: any;
|
2025-09-01 16:26:05 +02:00
|
|
|
path: string;
|
2025-09-23 12:52:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type FlagMatchRuleValue = {
|
|
|
|
|
path: FlagMatchRulePathSegment[];
|
|
|
|
|
value: string | object;
|
2025-09-01 16:26:05 +02:00
|
|
|
};
|
|
|
|
|
|
2025-08-28 10:27:57 +02:00
|
|
|
type FlagMatchInfo = {
|
|
|
|
|
field: string;
|
|
|
|
|
value: string;
|
|
|
|
|
re: string;
|
|
|
|
|
};
|
2025-08-25 14:47:12 +02:00
|
|
|
|
2025-09-09 10:56:15 +02:00
|
|
|
const ruleScreenshotC2PA = (): FlagMatchRule[] => {
|
2025-08-28 10:27:57 +02:00
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
field:
|
2025-09-01 16:26:05 +02:00
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType",
|
2025-08-28 10:27:57 +02:00
|
|
|
match: [C2PASourceTypeScreenCapture],
|
|
|
|
|
description: "Screen capture",
|
|
|
|
|
},
|
2025-09-29 11:24:06 +02:00
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType",
|
|
|
|
|
match: [C2PASourceTypeScreenCapture],
|
|
|
|
|
description: "Screen capture",
|
|
|
|
|
},
|
2025-08-28 10:27:57 +02:00
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-09 10:56:15 +02:00
|
|
|
const ruleScreenshotMeta = (): FlagMatchRule[] => {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"name",
|
|
|
|
|
match: ["screenshot"],
|
|
|
|
|
description: "Screen capture",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-01 16:26:05 +02:00
|
|
|
const ruleCamera = (): FlagMatchRule[] => {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType",
|
|
|
|
|
match: [C2PASourceTypeDigitalCapture, C2PASourceTypeComputationalCapture, C2PASourceTypeCompositeCapture],
|
|
|
|
|
description: "Captured by camera",
|
|
|
|
|
},
|
2025-09-29 11:24:06 +02:00
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType",
|
|
|
|
|
match: [C2PASourceTypeDigitalCapture, C2PASourceTypeComputationalCapture, C2PASourceTypeCompositeCapture],
|
|
|
|
|
description: "Captured by camera",
|
|
|
|
|
},
|
2025-09-01 16:26:05 +02:00
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ruleAiGenerated = (): FlagMatchRule[] => {
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]/digitalSourceType",
|
|
|
|
|
match: [C2PASourceTypeTrainedAlgorithmicMedia, C2PASourceTypeCompositeWithTrainedAlgorithmicMedia],
|
|
|
|
|
description: "Generated by AI",
|
|
|
|
|
},
|
2025-09-29 11:24:06 +02:00
|
|
|
{
|
|
|
|
|
field:
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType",
|
|
|
|
|
match: [C2PASourceTypeTrainedAlgorithmicMedia, C2PASourceTypeCompositeWithTrainedAlgorithmicMedia],
|
|
|
|
|
description: "Generated by AI",
|
|
|
|
|
},
|
2025-09-01 16:26:05 +02:00
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-09 11:34:48 +02:00
|
|
|
const ruleAiMeta = (): FlagMatchRule[] => {
|
2025-08-28 10:27:57 +02:00
|
|
|
const knownAIServices = [
|
|
|
|
|
"ChatGPT",
|
|
|
|
|
"OpenAI-API",
|
|
|
|
|
"Adobe Firefly",
|
|
|
|
|
"RunwayML",
|
|
|
|
|
"Runway AI",
|
|
|
|
|
"Google AI",
|
|
|
|
|
"Stable Diffusion",
|
|
|
|
|
];
|
|
|
|
|
return [
|
|
|
|
|
{ field: "name", match: ["^DALL_E_", "^Gen-3"], description: "File name" },
|
|
|
|
|
{
|
|
|
|
|
field: "integrity/c2pa/manifest_info/manifests[]/claim_generator",
|
|
|
|
|
match: knownAIServices,
|
|
|
|
|
description: "C2PA claim generator",
|
|
|
|
|
},
|
|
|
|
|
{ field: "iptc/Credit", match: knownAIServices, description: "IPTC Credit" },
|
|
|
|
|
{ field: "iptc/Provider", match: knownAIServices, description: "IPTC Provider" },
|
|
|
|
|
{ field: "iptc/ImageSupplier[]", match: knownAIServices, description: "IPTC ImageSupplier" },
|
|
|
|
|
{ field: "iptc/ImageCreator[]", match: knownAIServices, description: "IPTC ImageCreator" },
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
const matchFlag = (rules: FlagMatchRule[], path: FlagMatchRulePathSegment[]) => {
|
2025-08-28 10:27:57 +02:00
|
|
|
let result = false;
|
|
|
|
|
let resultInfo: FlagMatchInfo[] = [];
|
|
|
|
|
for (let rule of rules) {
|
|
|
|
|
try {
|
|
|
|
|
const re: RegExp[] = (!Array.isArray(rule.match) ? [rule.match] : rule.match).map((m) => new RegExp(m, "gi"));
|
2025-09-23 12:52:52 +02:00
|
|
|
const values = extractFlagValues(rule.field, path);
|
2025-09-04 14:20:45 +02:00
|
|
|
values.forEach((v) => {
|
|
|
|
|
re.forEach((r) => {
|
2025-09-23 12:52:52 +02:00
|
|
|
if (typeof v.value === "string" && r.test(v.value)) {
|
2025-09-05 11:16:50 +02:00
|
|
|
result = true;
|
2025-09-23 12:52:52 +02:00
|
|
|
resultInfo.push({ field: v.path.map((v) => v.path).join("/"), value: v.value, re: r.source });
|
2025-09-04 14:20:45 +02:00
|
|
|
}
|
2025-08-28 10:27:57 +02:00
|
|
|
});
|
2025-09-05 11:16:50 +02:00
|
|
|
});
|
2025-08-28 10:27:57 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Invalid RE", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return { result: result, matches: resultInfo };
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
const extractFlagValues = (flagPath: string, path: FlagMatchRulePathSegment[]): FlagMatchRuleValue[] => {
|
2025-09-05 11:16:50 +02:00
|
|
|
const getValues = (
|
2025-09-23 12:52:52 +02:00
|
|
|
keys: string[],
|
|
|
|
|
path: FlagMatchRulePathSegment[]
|
2025-09-05 11:16:50 +02:00
|
|
|
): FlagMatchRuleValue[] | undefined => {
|
2025-09-23 12:52:52 +02:00
|
|
|
if (keys.length == 0 || path.length == 0) return undefined;
|
|
|
|
|
|
|
|
|
|
const o = path[0].object;
|
|
|
|
|
let key = keys[0];
|
|
|
|
|
|
|
|
|
|
const lastBracket = key.lastIndexOf("[");
|
|
|
|
|
if (key === "..") {
|
|
|
|
|
return getValues(keys.slice(1), path.slice(1));
|
2025-09-01 16:26:05 +02:00
|
|
|
}
|
2025-09-23 12:52:52 +02:00
|
|
|
if (key.endsWith("]") && lastBracket > 0) {
|
|
|
|
|
const optionalConstraint = key.substring(lastBracket + 1, key.length - 1);
|
|
|
|
|
key = key.substring(0, lastBracket);
|
|
|
|
|
if (o[key] != undefined) {
|
|
|
|
|
let opart: any[] = o[key];
|
2025-09-01 16:26:05 +02:00
|
|
|
if (!Array.isArray(opart)) {
|
|
|
|
|
opart = Object.values(opart) ?? [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Any constraints controlling what array object(s) to consider?
|
|
|
|
|
if (optionalConstraint) {
|
2025-09-23 12:52:52 +02:00
|
|
|
if (optionalConstraint.startsWith("!!")) {
|
|
|
|
|
// Ignore this path in the tree if ANY of the values contain the constraint match.
|
|
|
|
|
const [prop, val] = optionalConstraint.substring(2).split("=");
|
|
|
|
|
if (opart.some((item) => item[prop] === val)) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
} else if (optionalConstraint.startsWith("!")) {
|
|
|
|
|
const [prop, val] = optionalConstraint.substring(1).split("=");
|
|
|
|
|
opart = opart.filter((item) => item[prop] !== val);
|
|
|
|
|
} else {
|
|
|
|
|
const [prop, val] = optionalConstraint.split("=");
|
|
|
|
|
opart = opart.filter((item) => item[prop] === val);
|
|
|
|
|
}
|
2025-09-01 16:26:05 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
if (keys.length == 1) {
|
|
|
|
|
return opart.map((s, i) => {
|
|
|
|
|
return { value: s, path: [{ object: o, path: key + "[" + i + "]"}, ... path] };
|
|
|
|
|
});
|
2025-09-01 16:26:05 +02:00
|
|
|
} else {
|
2025-09-23 12:52:52 +02:00
|
|
|
return opart.reduce((res: FlagMatchRuleValue[] | undefined, oin: any, i: number) => {
|
|
|
|
|
let matches = getValues(keys.slice(1), [ { object: oin, path: key + "[" + i + "]"}, ... path]);
|
2025-09-01 16:26:05 +02:00
|
|
|
if (matches) {
|
|
|
|
|
const r2 = res || [];
|
|
|
|
|
r2.push(...matches);
|
|
|
|
|
return r2;
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}, undefined);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-09-23 12:52:52 +02:00
|
|
|
if (o[key] != undefined) {
|
|
|
|
|
if (keys.length == 1) {
|
|
|
|
|
return [{ value: o[key], path: [{object: o[key], path: key}, ...path] }];
|
2025-09-01 16:26:05 +02:00
|
|
|
} else {
|
2025-09-23 12:52:52 +02:00
|
|
|
return getValues(keys.slice(1), [{object: o[key], path: key}, ...path]);
|
2025-09-01 16:26:05 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let result: FlagMatchRuleValue[] = [];
|
|
|
|
|
try {
|
2025-09-23 12:52:52 +02:00
|
|
|
let keys = flagPath.split("/");
|
|
|
|
|
result = getValues(keys, path) ?? [];
|
2025-09-01 16:26:05 +02:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error("Invalid RE", e);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
const getFirstWithData = (flagPaths: string[], path: FlagMatchRulePathSegment[]): string | undefined => {
|
|
|
|
|
for (let idx = 0; idx < flagPaths.length; idx++) {
|
|
|
|
|
const result = extractFlagValues(flagPaths[idx], path);
|
|
|
|
|
if (result.length > 0) {
|
|
|
|
|
return result[0].value as string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getFirstWithDataAsDate = (flagPaths: string[], path: FlagMatchRulePathSegment[]): Date | undefined => {
|
|
|
|
|
const val = getFirstWithData(flagPaths, path);
|
|
|
|
|
if (val) {
|
|
|
|
|
try {
|
|
|
|
|
const date = new Date(Date.parse(val));
|
|
|
|
|
return date;
|
|
|
|
|
} catch (error) {}
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const softwareAgentFromAction = (action: FlagMatchRuleValue): string | undefined => {
|
|
|
|
|
const agent = getFirstWithData([
|
|
|
|
|
"softwareAgent", "../../../claim_generator"
|
|
|
|
|
], action.path);
|
|
|
|
|
return agent;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-28 10:27:57 +02:00
|
|
|
export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined => {
|
|
|
|
|
if (!proof) return undefined;
|
|
|
|
|
|
2025-09-09 10:56:15 +02:00
|
|
|
let edits: ProofHintFlagsEdit[] | undefined = undefined;
|
2025-08-28 10:27:57 +02:00
|
|
|
let valid = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let results = proof.integrity?.c2pa?.manifest_info.validation_results?.activeManifest;
|
|
|
|
|
if (results) {
|
|
|
|
|
valid = results.failure.length == 0 && results.success.length > 0;
|
|
|
|
|
}
|
2025-09-05 11:16:50 +02:00
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
const rootMatchPath = [{object: proof, path: ""}];
|
2025-09-05 11:16:50 +02:00
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
let source: string | undefined = undefined;
|
|
|
|
|
let dateCreated: Date | undefined = undefined;
|
2025-09-23 17:17:26 +02:00
|
|
|
let dateCreatedSource: ProofHintFlagSource | undefined = undefined;
|
2025-09-23 12:52:52 +02:00
|
|
|
|
2025-09-29 11:24:06 +02:00
|
|
|
let creationAction = extractFlagValues(
|
2025-09-23 12:52:52 +02:00
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[action=c2pa.created]",
|
|
|
|
|
rootMatchPath
|
2025-09-05 11:16:50 +02:00
|
|
|
);
|
2025-09-29 11:24:06 +02:00
|
|
|
if (creationAction.length == 0) {
|
|
|
|
|
creationAction = extractFlagValues(
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions.v2]/data/actions[action=c2pa.created]",
|
|
|
|
|
rootMatchPath
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-23 12:52:52 +02:00
|
|
|
if (creationAction.length > 0) {
|
|
|
|
|
source = softwareAgentFromAction(creationAction[0]);
|
|
|
|
|
dateCreated = getFirstWithDataAsDate(["when", "../../metadata/dateTime", "../../../signature_info/time"], creationAction[0].path);
|
2025-09-23 17:17:26 +02:00
|
|
|
dateCreatedSource = "c2pa";
|
2025-08-28 10:27:57 +02:00
|
|
|
}
|
2025-09-23 12:52:52 +02:00
|
|
|
console.log("DATE CREATED", dateCreated);
|
2025-09-05 11:16:50 +02:00
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
let generator: ProofHintFlagsGenerator = matchFlag(ruleAiGenerated(), rootMatchPath).result ? "ai" : matchFlag(ruleScreenshotC2PA(), rootMatchPath).result ? "screenshot" : matchFlag(ruleCamera(), rootMatchPath).result ? "camera" : "unknown";
|
2025-09-23 17:17:26 +02:00
|
|
|
let generatorSource: ProofHintFlagSource | undefined = undefined;
|
2025-09-09 10:56:15 +02:00
|
|
|
|
|
|
|
|
if (generator !== "unknown" && valid) {
|
|
|
|
|
generatorSource = "c2pa";
|
|
|
|
|
} else {
|
2025-09-23 12:52:52 +02:00
|
|
|
if (matchFlag(ruleScreenshotMeta(), rootMatchPath).result) {
|
2025-09-09 10:56:15 +02:00
|
|
|
generator = "screenshot";
|
|
|
|
|
generatorSource = "metadata";
|
2025-09-23 12:52:52 +02:00
|
|
|
} else if (matchFlag(ruleAiMeta(), rootMatchPath).result) {
|
2025-09-09 11:34:48 +02:00
|
|
|
generator = "ai";
|
|
|
|
|
generatorSource = "metadata";
|
2025-09-09 10:56:15 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 12:52:52 +02:00
|
|
|
console.error("PROOF", proof);
|
|
|
|
|
|
|
|
|
|
const c2paEdits = extractFlagValues(
|
|
|
|
|
"integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions]/data/actions[!!action=c2pa.created]",
|
|
|
|
|
rootMatchPath
|
|
|
|
|
);
|
|
|
|
|
if (c2paEdits.length > 0) {
|
|
|
|
|
edits = c2paEdits.map((edit) => {
|
|
|
|
|
return {
|
|
|
|
|
editor: softwareAgentFromAction(edit) ?? "",
|
|
|
|
|
date: getFirstWithDataAsDate(["when", "../../metadata/dateTime", "../../../signature_info/time"], edit.path)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:34:48 +02:00
|
|
|
// Do we have any data? Else, return "undefined", we don't just want to send an object with all defaults.
|
2025-09-23 16:10:28 +02:00
|
|
|
if (source === undefined && dateCreated === undefined && generator === "unknown" && (!edits || edits.length == 0)) {
|
2025-09-09 11:34:48 +02:00
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-05 11:16:50 +02:00
|
|
|
const flags: ProofHintFlags = {
|
2025-09-23 12:52:52 +02:00
|
|
|
device: source,
|
|
|
|
|
creationDate: dateCreated,
|
2025-09-23 17:17:26 +02:00
|
|
|
creationDateSource: dateCreatedSource,
|
2025-09-09 10:56:15 +02:00
|
|
|
generator: generator,
|
|
|
|
|
generatorSource: generatorSource,
|
|
|
|
|
edits: edits,
|
2025-09-23 16:10:28 +02:00
|
|
|
containsC2PA: proof.integrity?.c2pa !== undefined,
|
|
|
|
|
containsEXIF: proof.integrity?.exif !== undefined
|
2025-09-05 11:16:50 +02:00
|
|
|
};
|
|
|
|
|
return flags;
|
2025-08-28 10:27:57 +02:00
|
|
|
} catch (error) {}
|
|
|
|
|
return undefined;
|
|
|
|
|
};
|