Start on C2PA and Exif info boxes on upload

This commit is contained in:
N-Pex 2025-07-04 12:51:57 +02:00
parent 114c09eb50
commit 54a1c05ddf
14 changed files with 337 additions and 49 deletions

View file

@ -0,0 +1,73 @@
<template>
<div v-if="c2pa">
<div class="detail-title">
{{ t("file_mode.content_credentials") }}
</div>
<div class="detail-row" v-if="dateCreated"><v-icon>$vuetify.icons.ic_exif_time</v-icon>{{ dateCreated }}</div>
<div class="detail-row" v-if="creator"><v-icon>$vuetify.icons.ic_exif_device_camera</v-icon>{{ creator }}</div>
<div class="detail-row" v-if="aiInferenceResult?.aiGenerated">
<v-icon>$vuetify.icons.ic_cc_ai</v-icon>{{ t("file_mode.ai_used") }}
</div>
<!-- {{ JSON.stringify(props.c2pa, undefined, 4) }} -->
</div>
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { AIInferenceResult, C2PAActionsAssertion, C2PAData } from "../../models/proof";
import { computed } from "vue";
import dayjs from "dayjs";
const { t } = useI18n();
const props = defineProps<{
c2pa?: C2PAData;
aiInferenceResult?: AIInferenceResult;
}>();
const dateCreated = computed(() => {
try {
const manifests = Object.values(props.c2pa?.manifest_info.manifests ?? {});
for (const manifest of manifests) {
const createAssertion = manifest.assertions.find((a) => {
if (a.label === "c2pa.actions") {
const actions = (a.data as C2PAActionsAssertion)?.actions ?? [];
if (actions.find((a) => a.action === "c2pa.created")) {
return true;
}
}
return false;
});
if (createAssertion) {
const d = dayjs(Date.parse(manifest.signature_info.time));
return d.format("lll");
}
}
} catch (error) {}
return undefined;
});
const creator = computed(() => {
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) {
return a.softwareAgent;
}
}
return false;
}
}
} catch (error) {}
return undefined;
});
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>

View file

@ -0,0 +1,113 @@
<template>
<div v-if="exif">
<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) }} -->
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const props = defineProps<{
exif?: { [key: string]: string | Object };
}>();
const { exif } = props;
const getSimpleValue = (key: string): string | undefined => {
return exif ? (exif[key] as string)?.replace(/^"(.+(?="$))"$/, "$1") : undefined;
};
const toDegrees = (dms: string, direction: string) => {
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 getLocation = () => {
try {
if (exif) {
const gpsLat = getSimpleValue("GPSLatitude");
const gpsLon = getSimpleValue("GPSLongitude");
if (gpsLat && gpsLon) {
const lat = toDegrees(gpsLat, getSimpleValue("GPSLatitudeRef") ?? "");
const lon = toDegrees(gpsLon, getSimpleValue("GPSLongitudeRef") ?? "");
return {lat, lon};
}
}
} catch (error) {}
return undefined;
}
const location = computed(() => {
const pos = getLocation();
if (pos) {
return pos.lat + " " + pos.lon;
}
return undefined;
});
const locationLink = computed(() => {
const pos = getLocation();
if (pos) {
return "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(pos.lat) + "," + encodeURIComponent(pos.lon);
}
return undefined;
});
const dateTime = computed(() => {
try {
if (exif) {
const date = getSimpleValue("DateTimeOriginal");
if (date) {
return dayjs(Date.parse(date)).format("lll");
}
}
} catch (error) {}
return undefined;
});
const makeAndModel = computed(() => {
let result = "";
if (exif) {
const make = getSimpleValue("Make");
const model = getSimpleValue("Model");
if (make) {
result += make;
}
if (model) {
if (result.length > 0) {
result += ", ";
}
result += model;
}
}
return result.length > 0 ? result : undefined;
});
</script>
<style lang="scss">
@use "@/assets/css/chat.scss" as *;
</style>