More work on sending/reading proof hint flags

This commit is contained in:
N-Pex 2025-09-05 11:16:50 +02:00
parent dd76692640
commit 66eef037e0
9 changed files with 267 additions and 219 deletions

View file

@ -1,132 +1,99 @@
<template>
<div v-if="c2pa">
<div v-if="props.flags">
<div class="detail-title">
{{ t("file_mode.content_credentials") }}
<v-icon>$vuetify.icons.ic_cr</v-icon>
</div>
<div class="detail-subtitle">
{{ t("file_mode.content_credentials_info") }}
<!-- <a href="" target="_blank">{{ t("file_mode.learn_more") }}</a> -->
</div>
<div class="detail-row" v-if="screenCapture">
<v-icon>$vuetify.icons.ic_media_screenshot</v-icon>{{ screenCapture }}
</div>
<template v-else>
<div class="detail-row" v-if="dateCreated">
<v-icon>$vuetify.icons.ic_exif_time</v-icon>{{ dateCreatedDisplay }}
</div>
<div class="detail-row" v-if="creator"><v-icon>$vuetify.icons.ic_media_device</v-icon>{{ creator }}</div>
<div class="detail-row" v-if="valid && cameraCapture">
<v-icon>$vuetify.icons.ic_media_camera</v-icon>{{ cameraCapture }}
</div>
</template>
<div class="detail-row" v-if="ai || aiInferenceResult?.aiGenerated">
<v-icon>$vuetify.icons.ic_cc_ai</v-icon>{{ t("file_mode.ai_used") }}
</div>
<div class="detail-row" v-if="showAgeWarning">
<v-icon>$vuetify.icons.ic_media_flag</v-icon>{{ t("file_mode.old_photo") }}
</div>
<!-- {{ JSON.stringify(props.c2pa, undefined, 4) }} -->
<div class="detail-info" v-if="infoText !== undefined">
{{ infoText }}
</div>
<CCProperty
v-if="props.flags?.source"
icon="$vuetify.icons.ic_media_camera"
:title="t('file_mode.cc_source')"
:value="props.flags?.source"
/>
<CCProperty
v-if="creationDate"
icon="$vuetify.icons.ic_exif_time"
:title="t('file_mode.cc_capture_timestamp')"
:value="creationDate"
/>
</div>
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import {
AIInferenceResult,
C2PAActionsAssertion,
C2PAData,
C2PASourceTypeCompositeCapture,
C2PASourceTypeCompositeWithTrainedAlgorithmicMedia,
C2PASourceTypeComputationalCapture,
C2PASourceTypeDigitalCapture,
C2PASourceTypeScreenCapture,
C2PASourceTypeTrainedAlgorithmicMedia,
ProofHintFlags,
} from "../../models/proof";
import { computed, ref, Ref, watch } from "vue";
import dayjs, { Dayjs } from "dayjs";
import { ref, Ref, watch } from "vue";
import dayjs from "dayjs";
import CCProperty from "./CCProperty.vue";
import relativeTime from "dayjs/plugin/relativeTime";
dayjs.extend(relativeTime);
const { t } = useI18n();
const props = defineProps<{
c2pa?: C2PAData;
aiInferenceResult?: AIInferenceResult;
flags?: ProofHintFlags;
}>();
//console.error("C2PA", JSON.stringify(props.c2pa, undefined, 4));
const creator: Ref<string | undefined> = ref(undefined);
const dateCreated: Ref<Dayjs | undefined> = ref(undefined);
const screenCapture: Ref<string | undefined> = ref(undefined);
const cameraCapture: Ref<string | undefined> = ref(undefined);
const ai: Ref<boolean> = ref(false);
const infoText: Ref<string | undefined> = ref(undefined);
const creationDate: Ref<string | undefined> = ref(undefined);
const valid: Ref<boolean> = ref(false);
watch(
props,
() => {
creator.value = undefined;
dateCreated.value = undefined;
screenCapture.value = undefined;
cameraCapture.value = undefined;
ai.value = false;
infoText.value = undefined;
creationDate.value = undefined;
valid.value = false;
try {
const manifests = Object.values(props.c2pa?.manifest_info.manifests ?? {});
for (const manifest of manifests) {
for (const assertion of manifest.assertions) {
if (assertion.label === "c2pa.actions") {
const actions = (assertion.data as C2PAActionsAssertion)?.actions ?? [];
const a = actions.find((a) => a.action === "c2pa.created");
if (a) {
creator.value = a.softwareAgent;
dateCreated.value = dayjs(Date.parse(manifest.signature_info.time));
if (a.digitalSourceType === C2PASourceTypeScreenCapture) {
screenCapture.value = t("file_mode.screenshot_taken_on", { date: dateCreated.value });
}
if (
a.digitalSourceType === C2PASourceTypeDigitalCapture ||
a.digitalSourceType === C2PASourceTypeComputationalCapture ||
a.digitalSourceType === C2PASourceTypeCompositeCapture
) {
cameraCapture.value = t("file_mode.captured_with_camera");
}
if (
a.digitalSourceType === C2PASourceTypeTrainedAlgorithmicMedia ||
a.digitalSourceType === C2PASourceTypeCompositeWithTrainedAlgorithmicMedia
) {
ai.value = true;
}
return;
}
if (props.flags) {
let date = props.flags.creationDate ? dayjs(props.flags.creationDate) : undefined;
if (date) {
creationDate.value = date.format("lll");
}
let result = "";
if (props.flags.camera) {
result += t("file_mode.captured_with_camera");
}
if (props.flags.screenshot) {
if (date) {
result += t("file_mode.captured_screenshot_ago", { ago: date.fromNow(true) });
} else {
result += t("file_mode.captured_screenshot");
}
}
if (props.flags.aiGenerated) {
if (date) {
result += t("file_mode.generated_with_ai_ago", { ago: date.fromNow(true) });
} else {
result += t("file_mode.generated_with_ai");
}
}
if (date && dayjs().diff(date, "month") >= 3) {
result += t("file_mode.old_photo");
}
infoText.value = result === "" ? undefined : result;
}
let results = props.c2pa?.manifest_info.validation_results?.activeManifest;
if (results) {
valid.value = results.failure.length == 0 && results.success.length > 0;
}
valid.value = props.flags?.valid ?? false;
} catch (error) {}
},
{ immediate: true }
);
const dateCreatedDisplay = computed(() => {
if (dateCreated.value) {
return dateCreated.value.format("lll");
}
return undefined;
});
const showAgeWarning = computed(() => {
if (dateCreated.value) {
return dayjs().diff(dateCreated.value, "month") >= 3;
}
return false;
});
</script>
<style lang="scss">

View file

@ -0,0 +1,23 @@
<template>
<div class="detail-row">
<v-icon>{{ props.icon }}</v-icon>
<div class="detail-row__text">
<div class="detail-row__title">{{ props.title }}</div>
<div>
<slot name="default">{{ props.value }}</slot>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
icon: string;
title: string;
value?: string;
}>();
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -3,16 +3,13 @@
<div class="detail-title">
{{ t("file_mode.exif_data") }}
</div>
<div class="detail-row" v-if="dateTime">
<v-icon>$vuetify.icons.ic_exif_time</v-icon>{{ dateTime }}
</div>
<div class="detail-row" v-if="location">
<v-icon>$vuetify.icons.ic_exif_location</v-icon><a :href="locationLink">{{ location }}</a>
</div>
<div class="detail-row" v-if="makeAndModel">
<v-icon>$vuetify.icons.ic_exif_device_camera</v-icon>{{ makeAndModel }}
</div>
<!-- Exif {{ JSON.stringify(props.exif, undefined, 4) }} -->
<CCProperty v-if="makeAndModel" icon="$vuetify.icons.ic_exif_device_camera" :title="t('file_mode.cc_source')" :value="makeAndModel" />
<CCProperty v-if="dateTime" icon="$vuetify.icons.ic_exif_time" :title="t('file_mode.cc_capture_timestamp')" :value="dateTime" />
<CCProperty v-if="location" icon="$vuetify.icons.ic_exif_location" :title="t('file_mode.cc_location')">
<template v-slot:default>
<a :href="locationLink">{{ location }}</a>
</template>
</CCProperty>
</div>
</template>
@ -20,6 +17,7 @@
import { computed } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import CCProperty from "./CCProperty.vue";
const { t } = useI18n();