From 1aff02c7d4e015ca91ac56ba13f6a947722b583a Mon Sep 17 00:00:00 2001 From: N-Pex Date: Thu, 23 Oct 2025 10:13:14 +0200 Subject: [PATCH] Rename ProofHintFlags to MediaMetadata Also, introduce the MediaInterventionFlags class to not send all metadata across, just the info needed for showing the intervention flags. --- .../content-credentials/C2PAInfo.vue | 34 +++++--------- .../{CCSummary.vue => MediaIntervention.vue} | 24 +++++----- .../content-credentials/intervention.ts | 8 ++-- src/components/file_mode/AttachmentInfo.vue | 4 +- .../file_mode/EventAttachmentInfo.vue | 19 +++++--- src/components/file_mode/GalleryItemsView.vue | 2 +- .../messages/composition/MessageImage.vue | 4 +- .../messages/composition/MessageThread.vue | 18 +++----- src/models/attachment.ts | 4 +- src/models/attachmentManager.ts | 13 +++--- src/models/eventAttachment.ts | 5 +- src/models/proof.ts | 46 +++++++++++++------ src/plugins/utils.js | 10 ++-- 13 files changed, 101 insertions(+), 90 deletions(-) rename src/components/content-credentials/{CCSummary.vue => MediaIntervention.vue} (60%) diff --git a/src/components/content-credentials/C2PAInfo.vue b/src/components/content-credentials/C2PAInfo.vue index e24742e..61c50fd 100644 --- a/src/components/content-credentials/C2PAInfo.vue +++ b/src/components/content-credentials/C2PAInfo.vue @@ -21,7 +21,7 @@ -
+
{{ t("cc.cc_no_info") }}
@@ -32,7 +32,7 @@ diff --git a/src/components/file_mode/GalleryItemsView.vue b/src/components/file_mode/GalleryItemsView.vue index 44236d7..1cabd3d 100644 --- a/src/components/file_mode/GalleryItemsView.vue +++ b/src/components/file_mode/GalleryItemsView.vue @@ -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(() => { diff --git a/src/components/messages/composition/MessageImage.vue b/src/components/messages/composition/MessageImage.vue index 1a9802a..a87a93e 100644 --- a/src/components/messages/composition/MessageImage.vue +++ b/src/components/messages/composition/MessageImage.vue @@ -5,7 +5,7 @@ v-bind="{ ...$props, ...$attrs }" >
- +
- +
{{ inReplyToSender }}
@@ -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; }, []); diff --git a/src/models/attachment.ts b/src/models/attachment.ts index bc4402e..36d7023 100644 --- a/src/models/attachment.ts +++ b/src/models/attachment.ts @@ -1,5 +1,5 @@ import { ComputedRef, Ref } from "vue"; -import { Proof, ProofHintFlags } from "./proof"; +import { Proof, MediaMetadata } from "./proof"; export class UploadPromise { wrappedPromise: Promise; @@ -58,7 +58,7 @@ export type Attachment = { useScaled: boolean; src?: string; proof?: Proof; - proofHintFlags?: ProofHintFlags; + mediaMetadata?: MediaMetadata; thumbnail?: AttachmentThumbnail; sendInfo: AttachmentSendInfo; }; diff --git a/src/models/attachmentManager.ts b/src/models/attachmentManager.ts index 3a1167b..2b693e0 100644 --- a/src/models/attachmentManager.ts +++ b/src/models/attachmentManager.ts @@ -18,8 +18,8 @@ import { import proofmode from "../plugins/proofmode"; import imageResize from "image-resize"; import { computed, ref, Ref, shallowReactive, unref } from "vue"; -import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, CLIENT_EVENT_PROOF_HINT } from "@/plugins/utils"; -import { extractProofHintFlags } from "./proof"; +import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS } from "@/plugins/utils"; +import { extractMediaMetadata } from "./proof"; export class AttachmentManager { matrixClient: MatrixClient; @@ -118,7 +118,7 @@ export class AttachmentManager { } try { attachment.proof = await proofmode.proofCheckFile(file); - attachment.proofHintFlags = extractProofHintFlags(attachment.proof); + attachment.mediaMetadata = extractMediaMetadata(attachment.proof); // Default to scaled version if the image does not contain Content Credentials // @@ -220,7 +220,7 @@ export class AttachmentManager { const fileSize = this.getSrcFileSize(event); - let proofHintFlags = event.getContent()[CLIENT_EVENT_PROOF_HINT]; + let mediaInterventionFlags = event.getContent()[CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS]; const attachment: EventAttachment = { event: event, @@ -229,7 +229,8 @@ export class AttachmentManager { srcProgress: -1, thumbnailProgress: -1, autoDownloadable: fileSize <= this.maxSizeAutoDownloads, - proofHintFlags: proofHintFlags ? JSON.parse(proofHintFlags) : proofHintFlags, + mediaInterventionFlags: mediaInterventionFlags ? JSON.parse(mediaInterventionFlags) : undefined, + mediaMetadata: undefined, proof: undefined, loadSrc: () => Promise.reject("Not implemented"), loadThumbnail: () => Promise.reject("Not implemented"), @@ -632,7 +633,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room eventId, attachment.dimensions, attachment.thumbnail, - attachment.proofHintFlags + attachment.mediaMetadata ) .then((mediaEventId: string) => { // Look at last item rotation, flipping the sign on this, so looks more like a true stack diff --git a/src/models/eventAttachment.ts b/src/models/eventAttachment.ts index 870c12c..1688fa5 100644 --- a/src/models/eventAttachment.ts +++ b/src/models/eventAttachment.ts @@ -1,6 +1,6 @@ import { MatrixEvent, Room } from "matrix-js-sdk"; import { AttachmentBatch } from "./attachment"; -import { Proof, ProofHintFlags } from "./proof"; +import { Proof, MediaMetadata } from "./proof"; export type KeanuEventExtension = { isMxThread?: boolean; @@ -28,7 +28,8 @@ export type EventAttachment = { thumbnailPromise?: Promise; autoDownloadable: boolean; proof?: Proof; - proofHintFlags?: ProofHintFlags; + mediaInterventionFlags?: MediaInterventionFlags; + mediaMetadata?: MediaMetadata; loadSrc: () => Promise; loadThumbnail: () => Promise; loadBlob: () => Promise<{data: Blob}>; diff --git a/src/models/proof.ts b/src/models/proof.ts index 619ca40..ff0d0c9 100644 --- a/src/models/proof.ts +++ b/src/models/proof.ts @@ -62,21 +62,29 @@ export type Proof = { ai?: { inferenceResult?: AIInferenceResult }; }; -export type ProofHintFlagsGenerator = "unknown" | "camera" | "screenshot" | "ai"; -export type ProofHintFlagSource = "c2pa" | "exif" | "metadata"; +export type MediaMetadataGenerator = "unknown" | "camera" | "screenshot" | "ai"; +export type MediaMetadataPropertySource = "c2pa" | "exif" | "metadata"; -export type ProofHintFlagsEdit = { +export type MediaMetadataEdit = { editor: string; date?: Date; } -export type ProofHintFlags = { +export type MediaInterventionFlags = { + creationDate?: Date; + generator?: MediaMetadataGenerator; + modified?: boolean; + containsC2PA?: boolean; + containsEXIF?: boolean; +} + +export type MediaMetadata = { device?: string; creationDate?: Date; - creationDateSource?: ProofHintFlagSource; - generator?: ProofHintFlagsGenerator; - generatorSource?: ProofHintFlagSource; - edits?: ProofHintFlagsEdit[]; + creationDateSource?: MediaMetadataPropertySource; + generator?: MediaMetadataGenerator; + generatorSource?: MediaMetadataPropertySource; + edits?: MediaMetadataEdit[]; containsC2PA?: boolean; containsEXIF?: boolean; }; @@ -371,10 +379,20 @@ const getFirstWithDataAsDate = (flagValues: FlagValue[], path: FlagMatchRulePath return undefined; }; -export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined => { +export const mediaMetadataToMediaInterventionFlags = (mediaMetadata: MediaMetadata): MediaInterventionFlags => { + return { + creationDate: mediaMetadata.creationDate, + generator: mediaMetadata.generator, + modified: mediaMetadata.edits && mediaMetadata.edits.length > 0, + containsC2PA: mediaMetadata.containsC2PA, + containsEXIF: mediaMetadata.containsEXIF + } +} + +export const extractMediaMetadata = (proof?: Proof): MediaMetadata | undefined => { if (!proof) return undefined; - let edits: ProofHintFlagsEdit[] | undefined = undefined; + let edits: MediaMetadataEdit[] | undefined = undefined; let valid = false; try { @@ -387,7 +405,7 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined let source: string | undefined = undefined; let dateCreated: Date | undefined = undefined; - let dateCreatedSource: ProofHintFlagSource | undefined = undefined; + let dateCreatedSource: MediaMetadataPropertySource | undefined = undefined; source = getFirstWithData(pathsC2PASource(), rootMatchPath); if (!source) { @@ -405,8 +423,8 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined } - let generator: ProofHintFlagsGenerator = "unknown"; - let generatorSource: ProofHintFlagSource | undefined = undefined; + let generator: MediaMetadataGenerator = "unknown"; + let generatorSource: MediaMetadataPropertySource | undefined = undefined; let digitalSourceType = extractFlagValues("integrity/c2pa/manifest_info/manifests[]/assertions[label=c2pa.actions|c2pa.actions.v2]/data/actions[action=c2pa.created]/digitalSourceType", rootMatchPath)?.at(0)?.value as string; if ([C2PASourceTypeScreenCapture].includes(digitalSourceType)) { @@ -460,7 +478,7 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined return undefined; } - const flags: ProofHintFlags = { + const flags: MediaMetadata = { device: source, creationDate: dateCreated, creationDateSource: dateCreatedSource, diff --git a/src/plugins/utils.js b/src/plugins/utils.js index 16e79e1..e780f14 100644 --- a/src/plugins/utils.js +++ b/src/plugins/utils.js @@ -14,6 +14,7 @@ import i18n from "./lang"; import { toRaw, isRef, isReactive, isProxy } from "vue"; import { UploadPromise } from "../models/attachment"; import png from "@/plugins/png.ts"; +import { mediaMetadataToMediaInterventionFlags } from "../models/proof"; export const STATE_EVENT_ROOM_DELETION_NOTICE = "im.keanu.room_deletion_notice"; export const STATE_EVENT_ROOM_DELETED = "im.keanu.room_deleted"; @@ -24,7 +25,7 @@ export const ROOM_TYPE_FILE_MODE = "im.keanu.room_type_file"; export const ROOM_TYPE_CHANNEL = "im.keanu.room_type_channel"; export const STATE_EVENT_ROOM_TYPE = "im.keanu.room_type"; -export const CLIENT_EVENT_PROOF_HINT = "im.keanu.proof_hint"; +export const CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS = "im.keanu.proof_hint"; export const THUMBNAIL_MAX_WIDTH = 640; export const THUMBNAIL_MAX_HEIGHT = 640; @@ -397,7 +398,7 @@ class Util { return [encryptedBytes, encryptedFile]; } - sendFile(matrixClient, roomId, file, onUploadProgress, threadRoot, dimensions, thumbnail, proofHintFlags) { + sendFile(matrixClient, roomId, file, onUploadProgress, threadRoot, dimensions, thumbnail, mediaMetadata) { const uploadPromise = new UploadPromise(undefined); uploadPromise.wrappedPromise = new Promise((resolve, reject) => { var reader = new FileReader(); @@ -478,8 +479,9 @@ class Util { msgtype: msgtype, }; - if (proofHintFlags) { - messageContent[CLIENT_EVENT_PROOF_HINT] = JSON.stringify(proofHintFlags); + if (mediaMetadata) { + const interventionFlags = mediaMetadataToMediaInterventionFlags(mediaMetadata); + messageContent[CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS] = JSON.stringify(interventionFlags); } // If thread root (an eventId) is set, add that here