Send and respect MSC4230 is_animated flag (#28513)
* PoC implementation for is_animated m.image flag Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update MSC reference Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
7d20bd4d06
commit
3d6664109b
4 changed files with 26 additions and 18 deletions
7
src/@types/matrix-js-sdk.d.ts
vendored
7
src/@types/matrix-js-sdk.d.ts
vendored
|
@ -22,6 +22,13 @@ declare module "matrix-js-sdk/src/types" {
|
||||||
[BLURHASH_FIELD]?: string;
|
[BLURHASH_FIELD]?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImageInfo {
|
||||||
|
/**
|
||||||
|
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4230
|
||||||
|
*/
|
||||||
|
"org.matrix.msc4230.is_animated"?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StateEvents {
|
export interface StateEvents {
|
||||||
// Jitsi-backed video room state events
|
// Jitsi-backed video room state events
|
||||||
[JitsiCallMemberEventType]: JitsiCallMemberContent;
|
[JitsiCallMemberEventType]: JitsiCallMemberContent;
|
||||||
|
|
|
@ -56,6 +56,7 @@ import { createThumbnail } from "./utils/image-media";
|
||||||
import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer";
|
import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer";
|
||||||
import { doMaybeLocalRoomAction } from "./utils/local-room";
|
import { doMaybeLocalRoomAction } from "./utils/local-room";
|
||||||
import { SdkContextClass } from "./contexts/SDKContext";
|
import { SdkContextClass } from "./contexts/SDKContext";
|
||||||
|
import { blobIsAnimated } from "./utils/Image.ts";
|
||||||
|
|
||||||
// scraped out of a macOS hidpi (5660ppm) screenshot png
|
// scraped out of a macOS hidpi (5660ppm) screenshot png
|
||||||
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
||||||
|
@ -150,15 +151,20 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
|
||||||
thumbnailType = "image/jpeg";
|
thumbnailType = "image/jpeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't await this immediately so it can happen in the background
|
||||||
|
const isAnimatedPromise = blobIsAnimated(imageFile.type, imageFile);
|
||||||
|
|
||||||
const imageElement = await loadImageElement(imageFile);
|
const imageElement = await loadImageElement(imageFile);
|
||||||
|
|
||||||
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
|
const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
|
||||||
const imageInfo = result.info;
|
const imageInfo = result.info;
|
||||||
|
|
||||||
|
imageInfo["org.matrix.msc4230.is_animated"] = await isAnimatedPromise;
|
||||||
|
|
||||||
// For lesser supported image types, always include the thumbnail even if it is larger
|
// For lesser supported image types, always include the thumbnail even if it is larger
|
||||||
if (!ALWAYS_INCLUDE_THUMBNAIL.includes(imageFile.type)) {
|
if (!ALWAYS_INCLUDE_THUMBNAIL.includes(imageFile.type)) {
|
||||||
// we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
|
// we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
|
||||||
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size;
|
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size!;
|
||||||
if (
|
if (
|
||||||
// image is small enough already
|
// image is small enough already
|
||||||
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
|
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
|
||||||
|
|
|
@ -275,7 +275,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = this.props.mxEvent.getContent<ImageContent>();
|
const content = this.props.mxEvent.getContent<ImageContent>();
|
||||||
let isAnimated = mayBeAnimated(content.info?.mimetype);
|
let isAnimated = content.info?.["org.matrix.msc4230.is_animated"] ?? mayBeAnimated(content.info?.mimetype);
|
||||||
|
|
||||||
// If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server
|
// If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server
|
||||||
// because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail.
|
// because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail.
|
||||||
|
@ -298,8 +298,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const blob = await this.props.mediaEventHelper!.sourceBlob.value;
|
// If we didn't receive the MSC4230 is_animated flag
|
||||||
if (!(await blobIsAnimated(content.info?.mimetype, blob))) {
|
// then we need to check if the image is animated by downloading it.
|
||||||
|
if (
|
||||||
|
content.info?.["org.matrix.msc4230.is_animated"] === false ||
|
||||||
|
!(await blobIsAnimated(
|
||||||
|
content.info?.mimetype,
|
||||||
|
await this.props.mediaEventHelper!.sourceBlob.value,
|
||||||
|
))
|
||||||
|
) {
|
||||||
isAnimated = false;
|
isAnimated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
import { ImageInfo } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { BlurhashEncoder } from "../BlurhashEncoder";
|
import { BlurhashEncoder } from "../BlurhashEncoder";
|
||||||
|
|
||||||
|
@ -15,19 +15,7 @@ type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||||
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
|
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
|
||||||
|
|
||||||
interface IThumbnail {
|
interface IThumbnail {
|
||||||
info: {
|
info: ImageInfo;
|
||||||
thumbnail_info?: {
|
|
||||||
w: number;
|
|
||||||
h: number;
|
|
||||||
mimetype: string;
|
|
||||||
size: number;
|
|
||||||
};
|
|
||||||
w: number;
|
|
||||||
h: number;
|
|
||||||
[BLURHASH_FIELD]?: string;
|
|
||||||
thumbnail_url?: string;
|
|
||||||
thumbnail_file?: EncryptedFile;
|
|
||||||
};
|
|
||||||
thumbnail: Blob;
|
thumbnail: Blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue