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:
parent
5fcbcb77fb
commit
1aff02c7d4
13 changed files with 101 additions and 90 deletions
|
|
@ -21,7 +21,7 @@
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</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") }}
|
{{ t("cc.cc_no_info") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="attachment-info__verify-info" v-else-if="props.metaStripped">
|
<div class="attachment-info__verify-info" v-else-if="props.metaStripped">
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from "vue-i18n";
|
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 { computed, ref, Ref, watch } from "vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import CCProperty, { CCPropertyProps } from "./CCProperty.vue";
|
import CCProperty, { CCPropertyProps } from "./CCProperty.vue";
|
||||||
|
|
@ -45,14 +45,13 @@ const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
proof?: Proof;
|
proof?: Proof;
|
||||||
flags?: ProofHintFlags;
|
metadata?: MediaMetadata;
|
||||||
|
interventionFlags?: MediaInterventionFlags;
|
||||||
metaStripped?: boolean;
|
metaStripped?: boolean;
|
||||||
showFlagsOnly?: boolean;
|
showFlagsOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const infoText: Ref<string | undefined> = ref(undefined);
|
const infoText: Ref<string | undefined> = ref(undefined);
|
||||||
const creationDate: Ref<string | undefined> = ref(undefined);
|
|
||||||
|
|
||||||
const details: Ref<(CCPropertyProps & { link?: string })[]> = ref([]);
|
const details: Ref<(CCPropertyProps & { link?: string })[]> = ref([]);
|
||||||
|
|
||||||
const hasC2PA = computed(() => {
|
const hasC2PA = computed(() => {
|
||||||
|
|
@ -62,19 +61,19 @@ const hasC2PA = computed(() => {
|
||||||
const updateDetails = () => {
|
const updateDetails = () => {
|
||||||
let d: (CCPropertyProps & { link?: string })[] = [];
|
let d: (CCPropertyProps & { link?: string })[] = [];
|
||||||
|
|
||||||
if (props.flags?.device) {
|
if (props.metadata?.device) {
|
||||||
d.push({
|
d.push({
|
||||||
icon: "$vuetify.icons.ic_media_camera",
|
icon: "$vuetify.icons.ic_media_camera",
|
||||||
title: t("file_mode.cc_source"),
|
title: t("file_mode.cc_source"),
|
||||||
value: props.flags?.device,
|
value: props.metadata?.device,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (creationDate.value) {
|
if (props.metadata?.creationDate) {
|
||||||
d.push({
|
d.push({
|
||||||
icon: "$vuetify.icons.ic_exif_time",
|
icon: "$vuetify.icons.ic_exif_time",
|
||||||
title: t("file_mode.cc_capture_timestamp"),
|
title: t("file_mode.cc_capture_timestamp"),
|
||||||
value: creationDate.value,
|
value: dayjs(props.metadata.creationDate)?.format("lll"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,20 +120,9 @@ const updateDetails = () => {
|
||||||
watch(
|
watch(
|
||||||
props,
|
props,
|
||||||
() => {
|
() => {
|
||||||
infoText.value = undefined;
|
infoText.value = props.metadata ?
|
||||||
creationDate.value = undefined;
|
interventionText(t, mediaMetadataToMediaInterventionFlags(props.metadata)) :
|
||||||
|
props.interventionFlags ? interventionText(t, props.interventionFlags) : 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) { }
|
|
||||||
|
|
||||||
updateDetails();
|
updateDetails();
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<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">{{
|
<v-icon class="intervention-icon">{{
|
||||||
showCheck ? "$vuetify.icons.ic_intervention_check" : "$vuetify.icons.ic_intervention"
|
showCheck ? "$vuetify.icons.ic_intervention_check" : "$vuetify.icons.ic_intervention"
|
||||||
}}</v-icon
|
}}</v-icon
|
||||||
|
|
@ -9,32 +9,32 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { ProofHintFlags } from "../../models/proof";
|
import { MediaInterventionFlags } from "../../models/proof";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { interventionText } from "./intervention";
|
import { interventionText } from "./intervention";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const { multiple, flags } = defineProps<{
|
const { multiple, mediaInterventionFlags } = defineProps<{
|
||||||
multiple: boolean;
|
multiple: boolean;
|
||||||
flags: ProofHintFlags[];
|
mediaInterventionFlags: MediaInterventionFlags[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showCheck = computed(() => {
|
const showCheck = computed(() => {
|
||||||
if (!multiple && flags.length == 1) {
|
if (!multiple && mediaInterventionFlags.length == 1) {
|
||||||
return !!flags[0].containsC2PA;
|
return !!mediaInterventionFlags[0].containsC2PA;
|
||||||
} else if (multiple) {
|
} else if (multiple) {
|
||||||
return flags.every((f) => !!f.containsC2PA)
|
return mediaInterventionFlags.every((f) => !!f.containsC2PA)
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const infoText = computed(() => {
|
const infoText = computed(() => {
|
||||||
if (!multiple && flags.length == 1) {
|
if (!multiple && mediaInterventionFlags.length == 1) {
|
||||||
return interventionText(t, flags[0]) ?? "";
|
return interventionText(t, mediaInterventionFlags[0]) ?? "";
|
||||||
} else if (multiple && flags.length >= 1) {
|
} else if (multiple && mediaInterventionFlags.length >= 1) {
|
||||||
const modifiedMedia = flags.some((f) => f.edits?.length);
|
const modifiedMedia = mediaInterventionFlags.some((f) => f.modified);
|
||||||
const aiGeneratedMedia = flags.some((f) => f.generator === "ai");
|
const aiGeneratedMedia = mediaInterventionFlags.some((f) => f.generator === "ai");
|
||||||
|
|
||||||
if (aiGeneratedMedia && modifiedMedia) {
|
if (aiGeneratedMedia && modifiedMedia) {
|
||||||
return "<b>" + t("cc.contains_modified_and_ai_generated") + "</b> " + t("cc.take_a_closer_look");
|
return "<b>" + t("cc.contains_modified_and_ai_generated") + "</b> " + t("cc.take_a_closer_look");
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import { ProofHintFlags } from "../../models/proof";
|
import { MediaInterventionFlags } from "../../models/proof";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export const interventionText = (t: any, f: ProofHintFlags): string | undefined => {
|
export const interventionText = (t: any, f: MediaInterventionFlags): string | undefined => {
|
||||||
let res = "";
|
let res = "";
|
||||||
if (f.generator === "camera") {
|
if (f.generator === "camera") {
|
||||||
res += "<b>" + t("cc.captured_with_camera") + "</b> ";
|
res += "<b>" + t("cc.captured_with_camera") + "</b> ";
|
||||||
} else if (f.generator === "screenshot") {
|
} 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") {
|
} else if (f.generator === "ai") {
|
||||||
res += "<b>" + t("cc.generated_with_ai") + "</b> ";
|
res += "<b>" + t("cc.generated_with_ai") + "</b> ";
|
||||||
}
|
}
|
||||||
if ((f.edits?.length ?? 0) > 0) {
|
if (f.modified) {
|
||||||
res += "<b>" + t("cc.modified") + "</b> ";
|
res += "<b>" + t("cc.modified") + "</b> ";
|
||||||
}
|
}
|
||||||
if (f.creationDate && dayjs().diff(f.creationDate, "month") >= 3) {
|
if (f.creationDate && dayjs().diff(f.creationDate, "month") >= 3) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ const { attachment } = defineProps<{
|
||||||
//console.error("ATTACHMENT", attachment.proof);
|
//console.error("ATTACHMENT", attachment.proof);
|
||||||
|
|
||||||
const showC2PAInfo = computed(() => {
|
const showC2PAInfo = computed(() => {
|
||||||
return attachment.proof?.integrity?.c2pa !== undefined || attachment.proofHintFlags !== undefined;
|
return attachment.proof?.integrity?.c2pa !== undefined || attachment.mediaMetadata !== undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasExif = computed(() => {
|
const hasExif = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<v-btn variant="flat" block class="attachment-info__download-button" @click.stop="() => {}">
|
<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 { onMounted, Ref, ref } from "vue";
|
||||||
import { EventAttachment } from "../../models/eventAttachment";
|
import { EventAttachment } from "../../models/eventAttachment";
|
||||||
import proofmode from "../../plugins/proofmode";
|
import proofmode from "../../plugins/proofmode";
|
||||||
import { extractProofHintFlags } from "../../models/proof";
|
import { extractMediaMetadata } from "../../models/proof";
|
||||||
|
|
||||||
const { attachment } = defineProps<{
|
const { attachment } = defineProps<{
|
||||||
attachment?: EventAttachment;
|
attachment?: EventAttachment;
|
||||||
|
|
@ -30,8 +30,14 @@ const { attachment } = defineProps<{
|
||||||
const loadingProof: Ref<boolean> = ref(false);
|
const loadingProof: Ref<boolean> = ref(false);
|
||||||
const metaStripped: 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(() => {
|
onMounted(() => {
|
||||||
if (attachment?.proofHintFlags && attachment.proof === undefined) {
|
if ((attachment?.mediaMetadata || attachment?.mediaInterventionFlags) && attachment.proof === undefined) {
|
||||||
const a = attachment;
|
const a = attachment;
|
||||||
loadingProof.value = true;
|
loadingProof.value = true;
|
||||||
metaStripped.value = true;
|
metaStripped.value = true;
|
||||||
|
|
@ -42,9 +48,9 @@ onMounted(() => {
|
||||||
a.proof = res;
|
a.proof = res;
|
||||||
if (res?.integrity?.c2pa) {
|
if (res?.integrity?.c2pa) {
|
||||||
// If we have proof, overwrite the flags
|
// 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;
|
loadingProof.value = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
metaStripped.value =
|
updateMetaStripped(attachment);
|
||||||
attachment?.proof?.integrity?.c2pa === undefined && attachment?.proof?.integrity?.exif === undefined;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ const displayDate = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const showInfoButton = computed(() => {
|
const showInfoButton = computed(() => {
|
||||||
return currentAttachment.value?.proofHintFlags !== undefined;
|
return currentAttachment.value?.mediaMetadata !== undefined || currentAttachment.value?.mediaInterventionFlags !== undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
const moreMenuItems = computed(() => {
|
const moreMenuItems = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
v-bind="{ ...$props, ...$attrs }"
|
v-bind="{ ...$props, ...$attrs }"
|
||||||
>
|
>
|
||||||
<div class="bubble image-bubble" ref="imageRef">
|
<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"
|
<ImageWithProgress v-if="attachment"
|
||||||
:aspect-ratio="16 / 9"
|
:aspect-ratio="16 / 9"
|
||||||
|
|
@ -34,7 +34,7 @@ import { MessageProps, useMessage } from "./useMessage";
|
||||||
import { EventAttachment } from "../../../models/eventAttachment";
|
import { EventAttachment } from "../../../models/eventAttachment";
|
||||||
import { useDisplay } from "vuetify";
|
import { useDisplay } from "vuetify";
|
||||||
import Hammer from "hammerjs";
|
import Hammer from "hammerjs";
|
||||||
import CCSummary from "../../content-credentials/CCSummary.vue";
|
import MediaIntervention from "../../content-credentials/MediaIntervention.vue";
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const $matrix: any = inject('globalMatrix');
|
const $matrix: any = inject('globalMatrix');
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview">
|
<component :is="rootComponent" ref="root" v-bind="{ ...$props, ...$attrs }" v-if="showMultiview">
|
||||||
<div class="bubble">
|
<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 bubble-inset" v-if="inReplyToText">
|
||||||
<div class="original-message-sender">{{ inReplyToSender }}</div>
|
<div class="original-message-sender">{{ inReplyToSender }}</div>
|
||||||
<div class="original-message-text" v-html="linkify($$sanitize(inReplyToText))" />
|
<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 GalleryItemsView from "../../file_mode/GalleryItemsView.vue";
|
||||||
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
|
import ThumbnailView from "../../file_mode/ThumbnailView.vue";
|
||||||
import SwipeableThumbnailsView from "../channel/SwipeableThumbnailsView.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 { computed, inject, onBeforeUnmount, ref, Ref, useTemplateRef, watch } from "vue";
|
||||||
import { EventAttachment } from "../../../models/eventAttachment";
|
import { EventAttachment } from "../../../models/eventAttachment";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk";
|
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk";
|
||||||
import { useLazyLoad } from "./useLazyLoad";
|
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 { t } = useI18n();
|
||||||
const $matrix: any = inject("globalMatrix");
|
const $matrix: any = inject("globalMatrix");
|
||||||
|
|
@ -176,14 +176,10 @@ const showMultiview = computed((): boolean => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const showCCSummary = computed(() => {
|
const mediaInterventionFlags = computed(() => {
|
||||||
return items.value?.some((i) => i.proofHintFlags !== undefined);
|
return items.value.reduce((res: MediaInterventionFlags[], item) => {
|
||||||
});
|
if (item.mediaInterventionFlags) {
|
||||||
|
res.push(item.mediaInterventionFlags);
|
||||||
const proofHintFlags = computed(() => {
|
|
||||||
return items.value.reduce((res: ProofHintFlags[], item) => {
|
|
||||||
if (item.proofHintFlags) {
|
|
||||||
res.push(item.proofHintFlags);
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ComputedRef, Ref } from "vue";
|
import { ComputedRef, Ref } from "vue";
|
||||||
import { Proof, ProofHintFlags } from "./proof";
|
import { Proof, MediaMetadata } from "./proof";
|
||||||
|
|
||||||
export class UploadPromise<Type> {
|
export class UploadPromise<Type> {
|
||||||
wrappedPromise: Promise<Type>;
|
wrappedPromise: Promise<Type>;
|
||||||
|
|
@ -58,7 +58,7 @@ export type Attachment = {
|
||||||
useScaled: boolean;
|
useScaled: boolean;
|
||||||
src?: string;
|
src?: string;
|
||||||
proof?: Proof;
|
proof?: Proof;
|
||||||
proofHintFlags?: ProofHintFlags;
|
mediaMetadata?: MediaMetadata;
|
||||||
thumbnail?: AttachmentThumbnail;
|
thumbnail?: AttachmentThumbnail;
|
||||||
sendInfo: AttachmentSendInfo;
|
sendInfo: AttachmentSendInfo;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import {
|
||||||
import proofmode from "../plugins/proofmode";
|
import proofmode from "../plugins/proofmode";
|
||||||
import imageResize from "image-resize";
|
import imageResize from "image-resize";
|
||||||
import { computed, ref, Ref, shallowReactive, unref } from "vue";
|
import { computed, ref, Ref, shallowReactive, unref } from "vue";
|
||||||
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, CLIENT_EVENT_PROOF_HINT } from "@/plugins/utils";
|
import utils, { THUMBNAIL_MAX_WIDTH, THUMBNAIL_MAX_HEIGHT, CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS } from "@/plugins/utils";
|
||||||
import { extractProofHintFlags } from "./proof";
|
import { extractMediaMetadata } from "./proof";
|
||||||
|
|
||||||
export class AttachmentManager {
|
export class AttachmentManager {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
|
@ -118,7 +118,7 @@ export class AttachmentManager {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
attachment.proof = await proofmode.proofCheckFile(file);
|
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
|
// Default to scaled version if the image does not contain Content Credentials
|
||||||
//
|
//
|
||||||
|
|
@ -220,7 +220,7 @@ export class AttachmentManager {
|
||||||
|
|
||||||
const fileSize = this.getSrcFileSize(event);
|
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 = {
|
const attachment: EventAttachment = {
|
||||||
event: event,
|
event: event,
|
||||||
|
|
@ -229,7 +229,8 @@ export class AttachmentManager {
|
||||||
srcProgress: -1,
|
srcProgress: -1,
|
||||||
thumbnailProgress: -1,
|
thumbnailProgress: -1,
|
||||||
autoDownloadable: fileSize <= this.maxSizeAutoDownloads,
|
autoDownloadable: fileSize <= this.maxSizeAutoDownloads,
|
||||||
proofHintFlags: proofHintFlags ? JSON.parse(proofHintFlags) : proofHintFlags,
|
mediaInterventionFlags: mediaInterventionFlags ? JSON.parse(mediaInterventionFlags) : undefined,
|
||||||
|
mediaMetadata: undefined,
|
||||||
proof: undefined,
|
proof: undefined,
|
||||||
loadSrc: () => Promise.reject("Not implemented"),
|
loadSrc: () => Promise.reject("Not implemented"),
|
||||||
loadThumbnail: () => Promise.reject("Not implemented"),
|
loadThumbnail: () => Promise.reject("Not implemented"),
|
||||||
|
|
@ -632,7 +633,7 @@ export const createUploadBatch = (manager: AttachmentManager | null, room: Room
|
||||||
eventId,
|
eventId,
|
||||||
attachment.dimensions,
|
attachment.dimensions,
|
||||||
attachment.thumbnail,
|
attachment.thumbnail,
|
||||||
attachment.proofHintFlags
|
attachment.mediaMetadata
|
||||||
)
|
)
|
||||||
.then((mediaEventId: string) => {
|
.then((mediaEventId: string) => {
|
||||||
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
|
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { MatrixEvent, Room } from "matrix-js-sdk";
|
import { MatrixEvent, Room } from "matrix-js-sdk";
|
||||||
import { AttachmentBatch } from "./attachment";
|
import { AttachmentBatch } from "./attachment";
|
||||||
import { Proof, ProofHintFlags } from "./proof";
|
import { Proof, MediaMetadata } from "./proof";
|
||||||
|
|
||||||
export type KeanuEventExtension = {
|
export type KeanuEventExtension = {
|
||||||
isMxThread?: boolean;
|
isMxThread?: boolean;
|
||||||
|
|
@ -28,7 +28,8 @@ export type EventAttachment = {
|
||||||
thumbnailPromise?: Promise<EventAttachmentUrlData>;
|
thumbnailPromise?: Promise<EventAttachmentUrlData>;
|
||||||
autoDownloadable: boolean;
|
autoDownloadable: boolean;
|
||||||
proof?: Proof;
|
proof?: Proof;
|
||||||
proofHintFlags?: ProofHintFlags;
|
mediaInterventionFlags?: MediaInterventionFlags;
|
||||||
|
mediaMetadata?: MediaMetadata;
|
||||||
loadSrc: () => Promise<EventAttachmentUrlData>;
|
loadSrc: () => Promise<EventAttachmentUrlData>;
|
||||||
loadThumbnail: () => Promise<EventAttachmentUrlData>;
|
loadThumbnail: () => Promise<EventAttachmentUrlData>;
|
||||||
loadBlob: () => Promise<{data: Blob}>;
|
loadBlob: () => Promise<{data: Blob}>;
|
||||||
|
|
|
||||||
|
|
@ -62,21 +62,29 @@ export type Proof = {
|
||||||
ai?: { inferenceResult?: AIInferenceResult };
|
ai?: { inferenceResult?: AIInferenceResult };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProofHintFlagsGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
export type MediaMetadataGenerator = "unknown" | "camera" | "screenshot" | "ai";
|
||||||
export type ProofHintFlagSource = "c2pa" | "exif" | "metadata";
|
export type MediaMetadataPropertySource = "c2pa" | "exif" | "metadata";
|
||||||
|
|
||||||
export type ProofHintFlagsEdit = {
|
export type MediaMetadataEdit = {
|
||||||
editor: string;
|
editor: string;
|
||||||
date?: Date;
|
date?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProofHintFlags = {
|
export type MediaInterventionFlags = {
|
||||||
|
creationDate?: Date;
|
||||||
|
generator?: MediaMetadataGenerator;
|
||||||
|
modified?: boolean;
|
||||||
|
containsC2PA?: boolean;
|
||||||
|
containsEXIF?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MediaMetadata = {
|
||||||
device?: string;
|
device?: string;
|
||||||
creationDate?: Date;
|
creationDate?: Date;
|
||||||
creationDateSource?: ProofHintFlagSource;
|
creationDateSource?: MediaMetadataPropertySource;
|
||||||
generator?: ProofHintFlagsGenerator;
|
generator?: MediaMetadataGenerator;
|
||||||
generatorSource?: ProofHintFlagSource;
|
generatorSource?: MediaMetadataPropertySource;
|
||||||
edits?: ProofHintFlagsEdit[];
|
edits?: MediaMetadataEdit[];
|
||||||
containsC2PA?: boolean;
|
containsC2PA?: boolean;
|
||||||
containsEXIF?: boolean;
|
containsEXIF?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -371,10 +379,20 @@ const getFirstWithDataAsDate = (flagValues: FlagValue[], path: FlagMatchRulePath
|
||||||
return undefined;
|
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;
|
if (!proof) return undefined;
|
||||||
|
|
||||||
let edits: ProofHintFlagsEdit[] | undefined = undefined;
|
let edits: MediaMetadataEdit[] | undefined = undefined;
|
||||||
let valid = false;
|
let valid = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -387,7 +405,7 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
|
||||||
|
|
||||||
let source: string | undefined = undefined;
|
let source: string | undefined = undefined;
|
||||||
let dateCreated: Date | undefined = undefined;
|
let dateCreated: Date | undefined = undefined;
|
||||||
let dateCreatedSource: ProofHintFlagSource | undefined = undefined;
|
let dateCreatedSource: MediaMetadataPropertySource | undefined = undefined;
|
||||||
|
|
||||||
source = getFirstWithData(pathsC2PASource(), rootMatchPath);
|
source = getFirstWithData(pathsC2PASource(), rootMatchPath);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
|
@ -405,8 +423,8 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let generator: ProofHintFlagsGenerator = "unknown";
|
let generator: MediaMetadataGenerator = "unknown";
|
||||||
let generatorSource: ProofHintFlagSource | undefined = undefined;
|
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;
|
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)) {
|
if ([C2PASourceTypeScreenCapture].includes(digitalSourceType)) {
|
||||||
|
|
@ -460,7 +478,7 @@ export const extractProofHintFlags = (proof?: Proof): ProofHintFlags | undefined
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const flags: ProofHintFlags = {
|
const flags: MediaMetadata = {
|
||||||
device: source,
|
device: source,
|
||||||
creationDate: dateCreated,
|
creationDate: dateCreated,
|
||||||
creationDateSource: dateCreatedSource,
|
creationDateSource: dateCreatedSource,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import i18n from "./lang";
|
||||||
import { toRaw, isRef, isReactive, isProxy } from "vue";
|
import { toRaw, isRef, isReactive, isProxy } from "vue";
|
||||||
import { UploadPromise } from "../models/attachment";
|
import { UploadPromise } from "../models/attachment";
|
||||||
import png from "@/plugins/png.ts";
|
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_DELETION_NOTICE = "im.keanu.room_deletion_notice";
|
||||||
export const STATE_EVENT_ROOM_DELETED = "im.keanu.room_deleted";
|
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 ROOM_TYPE_CHANNEL = "im.keanu.room_type_channel";
|
||||||
|
|
||||||
export const STATE_EVENT_ROOM_TYPE = "im.keanu.room_type";
|
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_WIDTH = 640;
|
||||||
export const THUMBNAIL_MAX_HEIGHT = 640;
|
export const THUMBNAIL_MAX_HEIGHT = 640;
|
||||||
|
|
@ -397,7 +398,7 @@ class Util {
|
||||||
return [encryptedBytes, encryptedFile];
|
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);
|
const uploadPromise = new UploadPromise(undefined);
|
||||||
uploadPromise.wrappedPromise = new Promise((resolve, reject) => {
|
uploadPromise.wrappedPromise = new Promise((resolve, reject) => {
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
|
|
@ -478,8 +479,9 @@ class Util {
|
||||||
msgtype: msgtype,
|
msgtype: msgtype,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (proofHintFlags) {
|
if (mediaMetadata) {
|
||||||
messageContent[CLIENT_EVENT_PROOF_HINT] = JSON.stringify(proofHintFlags);
|
const interventionFlags = mediaMetadataToMediaInterventionFlags(mediaMetadata);
|
||||||
|
messageContent[CLIENT_EVENT_MEDIA_INTERVENTION_FLAGS] = JSON.stringify(interventionFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If thread root (an eventId) is set, add that here
|
// If thread root (an eventId) is set, add that here
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue