Rename ProofHintFlags to MediaMetadata

Also, introduce the MediaInterventionFlags class to not send all metadata across, just the info needed for showing the intervention flags.
This commit is contained in:
N-Pex 2025-10-23 10:13:14 +02:00
parent 5fcbcb77fb
commit 1aff02c7d4
13 changed files with 101 additions and 90 deletions

View file

@ -21,7 +21,7 @@
</template>
</i18n-t>
</div>
<div class="attachment-info__verify-info" v-else-if="flags">
<div class="attachment-info__verify-info" v-else-if="metadata">
{{ t("cc.cc_no_info") }}
</div>
<div class="attachment-info__verify-info" v-else-if="props.metaStripped">
@ -32,7 +32,7 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { Proof, ProofHintFlags } from "../../models/proof";
import { Proof, MediaMetadata, MediaInterventionFlags, mediaMetadataToMediaInterventionFlags } from "../../models/proof";
import { computed, ref, Ref, watch } from "vue";
import dayjs from "dayjs";
import CCProperty, { CCPropertyProps } from "./CCProperty.vue";
@ -45,14 +45,13 @@ const { t } = useI18n();
const props = defineProps<{
proof?: Proof;
flags?: ProofHintFlags;
metadata?: MediaMetadata;
interventionFlags?: MediaInterventionFlags;
metaStripped?: boolean;
showFlagsOnly?: boolean;
}>();
const infoText: Ref<string | undefined> = ref(undefined);
const creationDate: Ref<string | undefined> = ref(undefined);
const details: Ref<(CCPropertyProps & { link?: string })[]> = ref([]);
const hasC2PA = computed(() => {
@ -62,19 +61,19 @@ const hasC2PA = computed(() => {
const updateDetails = () => {
let d: (CCPropertyProps & { link?: string })[] = [];
if (props.flags?.device) {
if (props.metadata?.device) {
d.push({
icon: "$vuetify.icons.ic_media_camera",
title: t("file_mode.cc_source"),
value: props.flags?.device,
value: props.metadata?.device,
});
}
if (creationDate.value) {
if (props.metadata?.creationDate) {
d.push({
icon: "$vuetify.icons.ic_exif_time",
title: t("file_mode.cc_capture_timestamp"),
value: creationDate.value,
value: dayjs(props.metadata.creationDate)?.format("lll"),
});
}
@ -121,20 +120,9 @@ const updateDetails = () => {
watch(
props,
() => {
infoText.value = undefined;
creationDate.value = undefined;
try {
if (props.flags) {
let date = props.flags.creationDate ? dayjs(props.flags.creationDate) : undefined;
if (date) {
creationDate.value = date.format("lll");
}
infoText.value = interventionText(t, props.flags);
}
} catch (error) { }
infoText.value = props.metadata ?
interventionText(t, mediaMetadataToMediaInterventionFlags(props.metadata)) :
props.interventionFlags ? interventionText(t, props.interventionFlags) : undefined;
updateDetails();
},
{ immediate: true }

View file

@ -1,5 +1,5 @@
<template>
<div class="cc-summary bubble-inset" v-if="flags.length > 0 && infoText.length > 0">
<div class="cc-summary bubble-inset" v-if="mediaInterventionFlags.length > 0 && infoText.length > 0">
<v-icon class="intervention-icon">{{
showCheck ? "$vuetify.icons.ic_intervention_check" : "$vuetify.icons.ic_intervention"
}}</v-icon
@ -9,32 +9,32 @@
<script setup lang="ts">
import { computed } from "vue";
import { ProofHintFlags } from "../../models/proof";
import { MediaInterventionFlags } from "../../models/proof";
import { useI18n } from "vue-i18n";
import { interventionText } from "./intervention";
const { t } = useI18n()
const { multiple, flags } = defineProps<{
const { multiple, mediaInterventionFlags } = defineProps<{
multiple: boolean;
flags: ProofHintFlags[];
mediaInterventionFlags: MediaInterventionFlags[];
}>();
const showCheck = computed(() => {
if (!multiple && flags.length == 1) {
return !!flags[0].containsC2PA;
if (!multiple && mediaInterventionFlags.length == 1) {
return !!mediaInterventionFlags[0].containsC2PA;
} else if (multiple) {
return flags.every((f) => !!f.containsC2PA)
return mediaInterventionFlags.every((f) => !!f.containsC2PA)
}
return false;
});
const infoText = computed(() => {
if (!multiple && flags.length == 1) {
return interventionText(t, flags[0]) ?? "";
} else if (multiple && flags.length >= 1) {
const modifiedMedia = flags.some((f) => f.edits?.length);
const aiGeneratedMedia = flags.some((f) => f.generator === "ai");
if (!multiple && mediaInterventionFlags.length == 1) {
return interventionText(t, mediaInterventionFlags[0]) ?? "";
} else if (multiple && mediaInterventionFlags.length >= 1) {
const modifiedMedia = mediaInterventionFlags.some((f) => f.modified);
const aiGeneratedMedia = mediaInterventionFlags.some((f) => f.generator === "ai");
if (aiGeneratedMedia && modifiedMedia) {
return "<b>" + t("cc.contains_modified_and_ai_generated") + "</b> " + t("cc.take_a_closer_look");

View file

@ -1,16 +1,16 @@
import { ProofHintFlags } from "../../models/proof";
import { MediaInterventionFlags } from "../../models/proof";
import dayjs from "dayjs";
export const interventionText = (t: any, f: ProofHintFlags): string | undefined => {
export const interventionText = (t: any, f: MediaInterventionFlags): string | undefined => {
let res = "";
if (f.generator === "camera") {
res += "<b>" + t("cc.captured_with_camera") + "</b> ";
} else if (f.generator === "screenshot") {
res += "<b>" + (f.generatorSource === "c2pa" ? t("cc.screenshot_probably") : t("cc.screenshot_probably")) + "</b> ";
res += "<b>" + t("cc.screenshot_probably") + "</b> ";
} else if (f.generator === "ai") {
res += "<b>" + t("cc.generated_with_ai") + "</b> ";
}
if ((f.edits?.length ?? 0) > 0) {
if (f.modified) {
res += "<b>" + t("cc.modified") + "</b> ";
}
if (f.creationDate && dayjs().diff(f.creationDate, "month") >= 3) {

View file

@ -28,7 +28,7 @@
</div>
</div>
<C2PAInfo class="attachment-info__detail-box" v-if="showC2PAInfo || hasExif" :proof="attachment.proof" :flags="attachment.proofHintFlags" :showFlagsOnly="attachment.useScaled" />
<C2PAInfo class="attachment-info__detail-box" v-if="showC2PAInfo || hasExif" :proof="attachment.proof" :metadata="attachment.mediaMetadata" :showFlagsOnly="attachment.useScaled" />
</div>
</template>
@ -48,7 +48,7 @@ const { attachment } = defineProps<{
//console.error("ATTACHMENT", attachment.proof);
const showC2PAInfo = computed(() => {
return attachment.proof?.integrity?.c2pa !== undefined || attachment.proofHintFlags !== undefined;
return attachment.proof?.integrity?.c2pa !== undefined || attachment.mediaMetadata !== undefined;
});
const hasExif = computed(() => {

View file

@ -7,7 +7,7 @@
</div>
</div>
<div v-else>
<C2PAInfo :proof="attachment?.proof" :flags="attachment?.proofHintFlags" :metaStripped="metaStripped" />
<C2PAInfo :proof="attachment?.proof" :metadata="attachment?.mediaMetadata" :interventionFlags="attachment?.mediaInterventionFlags" :metaStripped="metaStripped" />
</div>
</div>
<v-btn variant="flat" block class="attachment-info__download-button" @click.stop="() => {}">
@ -21,7 +21,7 @@ import C2PAInfo from "../content-credentials/C2PAInfo.vue";
import { onMounted, Ref, ref } from "vue";
import { EventAttachment } from "../../models/eventAttachment";
import proofmode from "../../plugins/proofmode";
import { extractProofHintFlags } from "../../models/proof";
import { extractMediaMetadata } from "../../models/proof";
const { attachment } = defineProps<{
attachment?: EventAttachment;
@ -30,8 +30,14 @@ const { attachment } = defineProps<{
const loadingProof: Ref<boolean> = ref(false);
const metaStripped: Ref<boolean> = ref(false);
const updateMetaStripped = (a: EventAttachment | undefined) => {
const hadC2PA = a?.mediaInterventionFlags ? a.mediaInterventionFlags.containsC2PA : false;
const hadExif = a?.mediaInterventionFlags ? a.mediaInterventionFlags.containsEXIF : false;
metaStripped.value = (hadC2PA && a?.proof?.integrity?.c2pa === undefined) || (hadExif && a?.proof?.integrity?.exif === undefined);
};
onMounted(() => {
if (attachment?.proofHintFlags && attachment.proof === undefined) {
if ((attachment?.mediaMetadata || attachment?.mediaInterventionFlags) && attachment.proof === undefined) {
const a = attachment;
loadingProof.value = true;
metaStripped.value = true;
@ -42,9 +48,9 @@ onMounted(() => {
a.proof = res;
if (res?.integrity?.c2pa) {
// If we have proof, overwrite the flags
a.proofHintFlags = extractProofHintFlags(a.proof);
a.mediaMetadata = extractMediaMetadata(a.proof);
}
metaStripped.value = a?.proof?.integrity?.c2pa === undefined && a?.proof?.integrity?.exif === undefined;
updateMetaStripped(a);
});
}
})
@ -53,8 +59,7 @@ onMounted(() => {
loadingProof.value = false;
});
} else {
metaStripped.value =
attachment?.proof?.integrity?.c2pa === undefined && attachment?.proof?.integrity?.exif === undefined;
updateMetaStripped(attachment);
}
});
</script>

View file

@ -84,7 +84,7 @@ const displayDate = computed(() => {
});
const showInfoButton = computed(() => {
return currentAttachment.value?.proofHintFlags !== undefined;
return currentAttachment.value?.mediaMetadata !== undefined || currentAttachment.value?.mediaInterventionFlags !== undefined;
});
const moreMenuItems = computed(() => {

View file

@ -5,7 +5,7 @@
v-bind="{ ...$props, ...$attrs }"
>
<div class="bubble image-bubble" ref="imageRef">
<CCSummary :multiple="false" :flags="attachment?.proofHintFlags ? [attachment.proofHintFlags] : []" />
<MediaIntervention :multiple="false" :media-intervention-flags="attachment?.mediaInterventionFlags ? [attachment.mediaInterventionFlags] : []" />
<ImageWithProgress v-if="attachment"
:aspect-ratio="16 / 9"
@ -34,7 +34,7 @@ import { MessageProps, useMessage } from "./useMessage";
import { EventAttachment } from "../../../models/eventAttachment";
import { useDisplay } from "vuetify";
import Hammer from "hammerjs";
import CCSummary from "../../content-credentials/CCSummary.vue";
import MediaIntervention from "../../content-credentials/MediaIntervention.vue";
const { t } = useI18n()
const $matrix: any = inject('globalMatrix');

View file

@ -1,7 +1,7 @@
<template>
<component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview">
<div class="bubble">
<CCSummary :multiple="items.length > 1" :flags="proofHintFlags" />
<MediaIntervention :multiple="items.length > 1" :mediaInterventionFlags="mediaInterventionFlags" />
<div class="original-message bubble-inset" v-if="inReplyToText">
<div class="original-message-sender">{{ inReplyToSender }}</div>
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
@ -60,13 +60,13 @@ import util, { ROOM_TYPE_CHANNEL, ROOM_TYPE_FILE_MODE } from "@/plugins/utils";
import GalleryItemsView from "../../file_mode/GalleryItemsView.vue";
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
import SwipeableThumbnailsView from "../channel/SwipeableThumbnailsView.vue";
import CCSummary from "@/components/content-credentials/CCSummary.vue";
import { computed, inject, onBeforeUnmount, ref, Ref, useTemplateRef, watch } from "vue";
import { EventAttachment } from "../../../models/eventAttachment";
import { useI18n } from "vue-i18n";
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk";
import { useLazyLoad } from "./useLazyLoad";
import { ProofHintFlags } from "../../../models/proof";
import { MediaInterventionFlags } from "../../../models/proof";
import MediaIntervention from "../../content-credentials/MediaIntervention.vue";
const { t } = useI18n();
const $matrix: any = inject("globalMatrix");
@ -176,14 +176,10 @@ const showMultiview = computed((): boolean => {
);
});
const showCCSummary = computed(() => {
return items.value?.some((i) => i.proofHintFlags !== undefined);
});
const proofHintFlags = computed(() => {
return items.value.reduce((res: ProofHintFlags[], item) => {
if (item.proofHintFlags) {
res.push(item.proofHintFlags);
const mediaInterventionFlags = computed(() => {
return items.value.reduce((res: MediaInterventionFlags[], item) => {
if (item.mediaInterventionFlags) {
res.push(item.mediaInterventionFlags);
}
return res;
}, []);