From e0b7633a7adb6f2744e1142637bbbd6ac6624031 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Thu, 9 Mar 2023 18:37:44 +0100 Subject: enhance(backend): restore OpenAPI endpoints (#10281) * enhance(backend): restore OpenAPI endpoints * Update CHANGELOG.md * version * set max-age * update redoc * follow redoc documentation --------- Co-authored-by: tamaina --- packages/backend/src/server/web/ClientServerService.ts | 5 ----- 1 file changed, 5 deletions(-) (limited to 'packages/backend/src/server/web') diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 98cdd31206..fb76f07e48 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -194,11 +194,6 @@ export class ClientServerService { //#region static assets - fastify.register(fastifyStatic, { - root: _dirname, - serve: false, - }); - fastify.register(fastifyStatic, { root: staticAssets, prefix: '/static-assets/', -- cgit v1.2.3-freya From 88e3d3e8cbe26c0280308e819965a64a91491f90 Mon Sep 17 00:00:00 2001 From: tamaina Date: Sat, 11 Mar 2023 14:11:40 +0900 Subject: enhance(server): 画像圧縮周り(主にサムネイルの仕様)の変更 (#10287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DriveService, is-mime-image * static, previewをavifに, アニメーション画像でもthumbnailを生成 * fallback * animated: true * fix * avatarはwebp * revert ?? file.url --------- Co-authored-by: syuilo --- packages/backend/src/core/DriveService.ts | 37 ++++----- .../backend/src/core/ImageProcessingService.ts | 91 +++++++++++++--------- .../src/core/entities/DriveFileEntityService.ts | 4 +- packages/backend/src/misc/is-mime-image.ts | 8 +- packages/backend/src/server/FileServerService.ts | 10 +-- .../backend/src/server/web/UrlPreviewService.ts | 2 +- packages/frontend/src/components/MkMediaImage.vue | 2 +- packages/frontend/src/pages/user/index.photos.vue | 2 +- packages/frontend/src/scripts/media-proxy.ts | 7 +- packages/frontend/src/widgets/WidgetPhotos.vue | 2 +- 10 files changed, 91 insertions(+), 74 deletions(-) (limited to 'packages/backend/src/server/web') diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index dfaacffc1d..7eccf4b3b1 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -2,6 +2,7 @@ import * as fs from 'node:fs'; import { Inject, Injectable } from '@nestjs/common'; import { v4 as uuid } from 'uuid'; import sharp from 'sharp'; +import { sharpBmp } from 'sharp-read-bmp'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; @@ -34,6 +35,7 @@ import { FileInfoService } from '@/core/FileInfoService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; import { correctFilename } from '@/misc/correct-filename.js'; +import { isMimeImage } from '@/misc/is-mime-image.js'; import type S3 from 'aws-sdk/clients/s3.js'; type AddFileArgs = { @@ -274,8 +276,8 @@ export class DriveService { } } - if (!['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/svg+xml'].includes(type)) { - this.registerLogger.debug('web image and thumbnail not created (not an required file)'); + if (!isMimeImage(type, 'sharp-convertible-image-with-bmp')) { + this.registerLogger.debug('web image and thumbnail not created (cannot convert by sharp)'); return { webpublic: null, thumbnail: null, @@ -284,22 +286,16 @@ export class DriveService { let img: sharp.Sharp | null = null; let satisfyWebpublic: boolean; + let isAnimated: boolean; try { - img = sharp(path); + img = await sharpBmp(path, type); const metadata = await img.metadata(); - const isAnimated = metadata.pages && metadata.pages > 1; - - // skip animated - if (isAnimated) { - return { - webpublic: null, - thumbnail: null, - }; - } + isAnimated = !!(metadata.pages && metadata.pages > 1); satisfyWebpublic = !!( - type !== 'image/svg+xml' && type !== 'image/avif' && + type !== 'image/svg+xml' && // security reason + type !== 'image/avif' && // not supported by Mastodon !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && metadata.width && metadata.width <= 2048 && metadata.height && metadata.height <= 2048 @@ -315,15 +311,13 @@ export class DriveService { // #region webpublic let webpublic: IImage | null = null; - if (generateWeb && !satisfyWebpublic) { + if (generateWeb && !satisfyWebpublic && !isAnimated) { this.registerLogger.info('creating web image'); try { - if (type === 'image/jpeg') { - webpublic = await this.imageProcessingService.convertSharpToJpeg(img, 2048, 2048); - } else if (['image/webp', 'image/avif'].includes(type)) { + if (['image/jpeg', 'image/webp', 'image/avif'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToWebp(img, 2048, 2048); - } else if (['image/png', 'image/svg+xml'].includes(type)) { + } else if (['image/png', 'image/bmp', 'image/svg+xml'].includes(type)) { webpublic = await this.imageProcessingService.convertSharpToPng(img, 2048, 2048); } else { this.registerLogger.debug('web image not created (not an required image)'); @@ -333,6 +327,7 @@ export class DriveService { } } else { if (satisfyWebpublic) this.registerLogger.info('web image not created (original satisfies webpublic)'); + else if (isAnimated) this.registerLogger.info('web image not created (animated image)'); else this.registerLogger.info('web image not created (from remote)'); } // #endregion webpublic @@ -341,10 +336,10 @@ export class DriveService { let thumbnail: IImage | null = null; try { - if (['image/jpeg', 'image/webp', 'image/avif', 'image/png', 'image/svg+xml'].includes(type)) { - thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 280); + if (isAnimated) { + thumbnail = await this.imageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 }); } else { - this.registerLogger.debug('thumbnail not created (not an required file)'); + thumbnail = await this.imageProcessingService.convertSharpToAvif(img, 498, 422); } } catch (err) { this.registerLogger.warn('thumbnail not created (an error occured)', err as Error); diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts index 7c88f5e9a0..3246475d12 100644 --- a/packages/backend/src/core/ImageProcessingService.ts +++ b/packages/backend/src/core/ImageProcessingService.ts @@ -15,15 +15,28 @@ export type IImageStream = { type: string; }; -export type IImageStreamable = IImage | IImageStream; +export type IImageSharp = { + data: sharp.Sharp; + ext: string | null; + type: string; +}; + +export type IImageStreamable = IImage | IImageStream | IImageSharp; export const webpDefault: sharp.WebpOptions = { - quality: 85, + quality: 77, alphaQuality: 95, lossless: false, nearLossless: false, smartSubsample: true, mixed: true, + effort: 2, +}; + +export const avifDefault: sharp.AvifOptions = { + quality: 60, + lossless: false, + effort: 2, }; import { bindThis } from '@/decorators.js'; @@ -38,90 +51,96 @@ export class ImageProcessingService { } /** - * Convert to JPEG + * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ @bindThis - public async convertToJpeg(path: string, width: number, height: number): Promise { - return this.convertSharpToJpeg(await sharp(path), width, height); + public async convertToWebp(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise { + return this.convertSharpToWebp(sharp(path), width, height, options); } @bindThis - public async convertSharpToJpeg(sharp: sharp.Sharp, width: number, height: number): Promise { - const data = await sharp + public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise { + const result = this.convertSharpToWebpStream(sharp, width, height, options); + + return { + data: await result.data.toBuffer(), + ext: result.ext, + type: result.type, + }; + } + + @bindThis + public convertToWebpStream(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageSharp { + return this.convertSharpToWebpStream(sharp(path), width, height, options); + } + + @bindThis + public convertSharpToWebpStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageSharp { + const data = sharp .resize(width, height, { fit: 'inside', withoutEnlargement: true, }) .rotate() - .jpeg({ - quality: 85, - progressive: true, - }) - .toBuffer(); + .webp(options); return { data, - ext: 'jpg', - type: 'image/jpeg', + ext: 'webp', + type: 'image/webp', }; } /** - * Convert to WebP + * Convert to Avif * with resize, remove metadata, resolve orientation, stop animation */ @bindThis - public async convertToWebp(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise { - return this.convertSharpToWebp(sharp(path), width, height, options); + public async convertToAvif(path: string, width: number, height: number, options: sharp.AvifOptions = avifDefault): Promise { + return this.convertSharpToAvif(sharp(path), width, height, options); } @bindThis - public async convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): Promise { - const data = await sharp - .resize(width, height, { - fit: 'inside', - withoutEnlargement: true, - }) - .rotate() - .webp(options) - .toBuffer(); + public async convertSharpToAvif(sharp: sharp.Sharp, width: number, height: number, options: sharp.AvifOptions = avifDefault): Promise { + const result = this.convertSharpToAvifStream(sharp, width, height, options); return { - data, - ext: 'webp', - type: 'image/webp', + data: await result.data.toBuffer(), + ext: result.ext, + type: result.type, }; } @bindThis - public convertToWebpStream(path: string, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageStream { - return this.convertSharpToWebpStream(sharp(path), width, height, options); + public convertToAvifStream(path: string, width: number, height: number, options: sharp.AvifOptions = avifDefault): IImageSharp { + return this.convertSharpToAvifStream(sharp(path), width, height, options); } @bindThis - public convertSharpToWebpStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.WebpOptions = webpDefault): IImageStream { + public convertSharpToAvifStream(sharp: sharp.Sharp, width: number, height: number, options: sharp.AvifOptions = avifDefault): IImageSharp { const data = sharp .resize(width, height, { fit: 'inside', withoutEnlargement: true, }) .rotate() - .webp(options); + .avif(options); return { data, - ext: 'webp', - type: 'image/webp', + ext: 'avif', + type: 'image/avif', }; } + /** * Convert to PNG * with resize, remove metadata, resolve orientation, stop animation */ @bindThis public async convertToPng(path: string, width: number, height: number): Promise { - return this.convertSharpToPng(await sharp(path), width, height); + return this.convertSharpToPng(sharp(path), width, height); } @bindThis diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 74a0689d89..1a6913b800 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -76,7 +76,7 @@ export class DriveFileEntityService { @bindThis private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string { return appendQuery( - `${this.config.mediaProxy}/${mode ?? 'image'}.webp`, + `${this.config.mediaProxy}/${mode ?? 'image'}.${mode === 'avatar' ? 'webp' : 'avif'}`, query({ url, ...(mode ? { [mode]: '1' } : {}), @@ -104,7 +104,7 @@ export class DriveFileEntityService { const url = file.webpublicUrl ?? file.url; - return file.thumbnailUrl ?? (isMimeImage(file.type, 'sharp-convertible-image') ? this.getProxiedUrl(url, 'static') : null); + return file.thumbnailUrl ?? (isMimeImage(file.type, 'sharp-convertible-image') ? url : null); } @bindThis diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts index 0b6d147dc1..46a66efc0f 100644 --- a/packages/backend/src/misc/is-mime-image.ts +++ b/packages/backend/src/misc/is-mime-image.ts @@ -2,10 +2,10 @@ import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; const dictionary = { 'safe-file': FILE_TYPE_BROWSERSAFE, - 'sharp-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'], - 'sharp-animation-convertible-image': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml'], - 'sharp-convertible-image-with-bmp': ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], - 'sharp-animation-convertible-image-with-bmp': ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], + 'sharp-convertible-image': ['image/jpeg', 'image/tiff', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml'], + 'sharp-animation-convertible-image': ['image/jpeg', 'image/tiff', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml'], + 'sharp-convertible-image-with-bmp': ['image/jpeg', 'image/tiff', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], + 'sharp-animation-convertible-image-with-bmp': ['image/jpeg', 'image/tiff', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/svg+xml', 'image/x-icon', 'image/bmp'], }; export const isMimeImage = (mime: string, type: keyof typeof dictionary): boolean => dictionary[type].includes(mime); diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index 6db9a9672c..fb1c67f20d 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -130,7 +130,7 @@ export class FileServerService { if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { reply.header('Cache-Control', 'max-age=31536000, immutable'); - const url = new URL(`${this.config.mediaProxy}/static.webp`); + const url = new URL(`${this.config.mediaProxy}/static.avif`); url.searchParams.set('url', file.url); url.searchParams.set('static', '1'); @@ -151,7 +151,7 @@ export class FileServerService { if (['image/svg+xml'].includes(file.mime)) { reply.header('Cache-Control', 'max-age=31536000, immutable'); - const url = new URL(`${this.config.mediaProxy}/svg.webp`); + const url = new URL(`${this.config.mediaProxy}/svg.avif`); url.searchParams.set('url', file.url); file.cleanup(); @@ -291,9 +291,9 @@ export class FileServerService { }; } } else if ('static' in request.query) { - image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 280); + image = this.imageProcessingService.convertSharpToAvifStream(await sharpBmp(file.path, file.mime), 498, 422); } else if ('preview' in request.query) { - image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200); + image = this.imageProcessingService.convertSharpToAvifStream(await sharpBmp(file.path, file.mime), 200, 200); } else if ('badge' in request.query) { const mask = (await sharpBmp(file.path, file.mime)) .resize(96, 96, { @@ -325,7 +325,7 @@ export class FileServerService { type: 'image/png', }; } else if (file.mime === 'image/svg+xml') { - image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048); + image = this.imageProcessingService.convertToAvifStream(file.path, 2048, 2048); } else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) { throw new StatusError('Rejected type', 403, 'Rejected type'); } diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 2ce7293a52..5f4d53d0ec 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -33,7 +33,7 @@ export class UrlPreviewService { private wrap(url?: string | null): string | null { return url != null ? url.match(/^https?:\/\//) - ? `${this.config.mediaProxy}/preview.webp?${query({ + ? `${this.config.mediaProxy}/preview.avif?${query({ url, preview: '1', })}` diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 6091b40016..a4065dcd07 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -43,7 +43,7 @@ let darkMode = $ref(defaultStore.state.darkMode); const url = (props.raw || defaultStore.state.loadRawImages) ? props.image.url : defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(props.image.thumbnailUrl) + ? getStaticImageUrl(props.image.url) : props.image.thumbnailUrl; // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする diff --git a/packages/frontend/src/pages/user/index.photos.vue b/packages/frontend/src/pages/user/index.photos.vue index 607082c1e4..85f6591eee 100644 --- a/packages/frontend/src/pages/user/index.photos.vue +++ b/packages/frontend/src/pages/user/index.photos.vue @@ -41,7 +41,7 @@ let images = $ref<{ function thumbnail(image: misskey.entities.DriveFile): string { return defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(image.thumbnailUrl) + ? getStaticImageUrl(image.url) : image.thumbnailUrl; } diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts index 2fe5bdcf8f..d0c95e0b75 100644 --- a/packages/frontend/src/scripts/media-proxy.ts +++ b/packages/frontend/src/scripts/media-proxy.ts @@ -10,7 +10,10 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigi imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl; } - return `${mustOrigin ? localProxy : instance.mediaProxy}/image.webp?${query({ + return `${mustOrigin ? localProxy : instance.mediaProxy}/${ + type === 'preview' ? 'preview.avif' + : 'image.webp' + }?${query({ url: imageUrl, fallback: '1', ...(type ? { [type]: '1' } : {}), @@ -38,7 +41,7 @@ export function getStaticImageUrl(baseUrl: string): string { return u.href; } - return `${instance.mediaProxy}/static.webp?${query({ + return `${instance.mediaProxy}/static.avif?${query({ url: u.href, static: '1', })}`; diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 562249f094..716bbb4274 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -67,7 +67,7 @@ const onDriveFileCreated = (file) => { const thumbnail = (image: any): string => { return defaultStore.state.disableShowingAnimatedImages - ? getStaticImageUrl(image.thumbnailUrl) + ? getStaticImageUrl(image.url) : image.thumbnailUrl; }; -- cgit v1.2.3-freya From dff0bee7484fd65927cb9b85780ada2922986768 Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 12 Mar 2023 17:31:52 +0900 Subject: fix: Do not use avif (#10301) * fix: Do not use avif * fix * fix --- CHANGELOG.md | 3 ++- packages/backend/src/core/DriveService.ts | 4 ++-- packages/backend/src/core/entities/DriveFileEntityService.ts | 2 +- packages/backend/src/server/FileServerService.ts | 10 +++++----- packages/backend/src/server/web/UrlPreviewService.ts | 2 +- packages/frontend/src/scripts/media-proxy.ts | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) (limited to 'packages/backend/src/server/web') diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aaa4524da..13102ef7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,8 @@ You should also include the user name that made the change. - enhance(client): DM作成時にメンションも含むように - enhance(client): フォロー申請のボタンのデザインを改善 - enhance(backend): OpenAPIエンドポイントを復旧 -- 透明なWebP/AVIF映像はJPEGではなくWebPに変換するように +- WebP/AVIF/JPEGのweb公開用画像は、サーバーサイドではJPEGではなくWebPに変換するように +- アニメーション画像のサムネイルを生成するように - アクティブユーザー数チャートの記録上限値を拡張 - Playのソースコード上限文字数を2倍に拡張 - 付箋ウィジェットの高さを設定可能に diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 7eccf4b3b1..f1e93d6dd9 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -295,7 +295,7 @@ export class DriveService { satisfyWebpublic = !!( type !== 'image/svg+xml' && // security reason - type !== 'image/avif' && // not supported by Mastodon + type !== 'image/avif' && // not supported by Mastodon and MS Edge !(metadata.exif ?? metadata.iptc ?? metadata.xmp ?? metadata.tifftagPhotoshop) && metadata.width && metadata.width <= 2048 && metadata.height && metadata.height <= 2048 @@ -339,7 +339,7 @@ export class DriveService { if (isAnimated) { thumbnail = await this.imageProcessingService.convertSharpToWebp(sharp(path, { animated: true }), 374, 317, { alphaQuality: 70 }); } else { - thumbnail = await this.imageProcessingService.convertSharpToAvif(img, 498, 422); + thumbnail = await this.imageProcessingService.convertSharpToWebp(img, 498, 422); } } catch (err) { this.registerLogger.warn('thumbnail not created (an error occured)', err as Error); diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts index 1a6913b800..2d40f444cb 100644 --- a/packages/backend/src/core/entities/DriveFileEntityService.ts +++ b/packages/backend/src/core/entities/DriveFileEntityService.ts @@ -76,7 +76,7 @@ export class DriveFileEntityService { @bindThis private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string { return appendQuery( - `${this.config.mediaProxy}/${mode ?? 'image'}.${mode === 'avatar' ? 'webp' : 'avif'}`, + `${this.config.mediaProxy}/${mode ?? 'image'}.webp`, query({ url, ...(mode ? { [mode]: '1' } : {}), diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts index fb1c67f20d..794fa76d9e 100644 --- a/packages/backend/src/server/FileServerService.ts +++ b/packages/backend/src/server/FileServerService.ts @@ -130,7 +130,7 @@ export class FileServerService { if (isMimeImage(file.mime, 'sharp-convertible-image-with-bmp')) { reply.header('Cache-Control', 'max-age=31536000, immutable'); - const url = new URL(`${this.config.mediaProxy}/static.avif`); + const url = new URL(`${this.config.mediaProxy}/static.webp`); url.searchParams.set('url', file.url); url.searchParams.set('static', '1'); @@ -151,7 +151,7 @@ export class FileServerService { if (['image/svg+xml'].includes(file.mime)) { reply.header('Cache-Control', 'max-age=31536000, immutable'); - const url = new URL(`${this.config.mediaProxy}/svg.avif`); + const url = new URL(`${this.config.mediaProxy}/svg.webp`); url.searchParams.set('url', file.url); file.cleanup(); @@ -291,9 +291,9 @@ export class FileServerService { }; } } else if ('static' in request.query) { - image = this.imageProcessingService.convertSharpToAvifStream(await sharpBmp(file.path, file.mime), 498, 422); + image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 498, 422); } else if ('preview' in request.query) { - image = this.imageProcessingService.convertSharpToAvifStream(await sharpBmp(file.path, file.mime), 200, 200); + image = this.imageProcessingService.convertSharpToWebpStream(await sharpBmp(file.path, file.mime), 200, 200); } else if ('badge' in request.query) { const mask = (await sharpBmp(file.path, file.mime)) .resize(96, 96, { @@ -325,7 +325,7 @@ export class FileServerService { type: 'image/png', }; } else if (file.mime === 'image/svg+xml') { - image = this.imageProcessingService.convertToAvifStream(file.path, 2048, 2048); + image = this.imageProcessingService.convertToWebpStream(file.path, 2048, 2048); } else if (!file.mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(file.mime)) { throw new StatusError('Rejected type', 403, 'Rejected type'); } diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 5f4d53d0ec..2ce7293a52 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -33,7 +33,7 @@ export class UrlPreviewService { private wrap(url?: string | null): string | null { return url != null ? url.match(/^https?:\/\//) - ? `${this.config.mediaProxy}/preview.avif?${query({ + ? `${this.config.mediaProxy}/preview.webp?${query({ url, preview: '1', })}` diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts index d0c95e0b75..91ac14c06d 100644 --- a/packages/frontend/src/scripts/media-proxy.ts +++ b/packages/frontend/src/scripts/media-proxy.ts @@ -11,7 +11,7 @@ export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigi } return `${mustOrigin ? localProxy : instance.mediaProxy}/${ - type === 'preview' ? 'preview.avif' + type === 'preview' ? 'preview.webp' : 'image.webp' }?${query({ url: imageUrl, @@ -41,7 +41,7 @@ export function getStaticImageUrl(baseUrl: string): string { return u.href; } - return `${instance.mediaProxy}/static.avif?${query({ + return `${instance.mediaProxy}/static.webp?${query({ url: u.href, static: '1', })}`; -- cgit v1.2.3-freya From 0944c1cd6feca939422f3b10047ee37f8ce57696 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 16 Mar 2023 14:14:14 +0900 Subject: update tabler-icons --- packages/backend/src/server/web/views/base.pug | 2 +- packages/frontend/package.json | 2 +- pnpm-lock.yaml | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'packages/backend/src/server/web') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 8d6897c46d..a9a0dfd4ee 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -36,7 +36,7 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') //- https://github.com/misskey-dev/misskey/issues/9842 - link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.2.0') + link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.10.0') link(rel='modulepreload' href=`/vite/${clientEntry.file}`) if !config.clientManifestExists diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2192553a5f..66d6d81fb5 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -16,7 +16,7 @@ "@rollup/plugin-json": "6.0.0", "@rollup/pluginutils": "5.0.2", "@syuilo/aiscript": "0.13.0", - "@tabler/icons-webfont": "2.2.0", + "@tabler/icons-webfont": "2.10.0", "@vitejs/plugin-vue": "4.0.0", "@vue/compiler-sfc": "3.2.47", "autobind-decorator": "2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 826411d184..329b0c9453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -399,7 +399,7 @@ importers: '@rollup/plugin-json': 6.0.0 '@rollup/pluginutils': 5.0.2 '@syuilo/aiscript': 0.13.0 - '@tabler/icons-webfont': 2.2.0 + '@tabler/icons-webfont': 2.10.0 '@testing-library/vue': ^6.6.1 '@types/escape-regexp': 0.0.1 '@types/gulp': 4.0.10 @@ -487,7 +487,7 @@ importers: '@rollup/plugin-json': 6.0.0_rollup@3.19.0 '@rollup/pluginutils': 5.0.2_rollup@3.19.0 '@syuilo/aiscript': 0.13.0 - '@tabler/icons-webfont': 2.2.0 + '@tabler/icons-webfont': 2.10.0 '@vitejs/plugin-vue': 4.0.0_vite@4.1.4+vue@3.2.47 '@vue/compiler-sfc': 3.2.47 autobind-decorator: 2.4.0 @@ -2318,14 +2318,14 @@ packages: defer-to-connect: 2.0.1 dev: false - /@tabler/icons-webfont/2.2.0: - resolution: {integrity: sha512-rAok1gpbi3XzsgyXGAg1jNueXm1L5YGR2ab0RWMR8T8W8x6Kl2IlCOPZ5mHTJoac+2BA3s9dAPRsMM47nfPMYw==} + /@tabler/icons-webfont/2.10.0: + resolution: {integrity: sha512-5WvGhztlM3la7NWf8Y6ktT+KD7zb/Hz/zdMeFjExXvEFupGvuANEnbGo1wXI4ADdSWUaRDtnQHcSGIjZ+gZ+OQ==} dependencies: - '@tabler/icons': 2.2.0 + '@tabler/icons': 2.10.0 dev: false - /@tabler/icons/2.2.0: - resolution: {integrity: sha512-s2mm+7JqmLObKdU89Dtiy+USmUpOlACsoXZZPykjAJZC4pK3wMYxLsclJxViWLeLTb6Bc0oga92V7R+9nrj1ZQ==} + /@tabler/icons/2.10.0: + resolution: {integrity: sha512-rj9xrHTSw7bPpylx8g9xhhUgO9NYKX1wGnGrMaFS5CQ9KS+jhwhKFqbZaQKhXNhpvI0cLEEW6GaRXdrC3iBs1A==} dev: false /@tensorflow/tfjs-backend-cpu/4.2.0_tkoh6rxfpzme3tc2ndqbqcrg7y: -- cgit v1.2.3-freya From c091d9e6d558ceb743fb8b647b28dbce8f994d41 Mon Sep 17 00:00:00 2001 From: Kagami Sascha Rosylight Date: Sun, 19 Mar 2023 08:59:31 +0100 Subject: feat(frontend/MkUrlPreview): oEmbedのサポート (#10306) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(frontend/MkUrlPreview): oEmbedのサポート * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * playerとoEmbedの統合 * Update CHANGELOG.md * loading=lazyはここでは不要 * border: 0 * プレビュー直後にautoplayできる機能の復旧 * add test * refactor test * explain about cache * expandPreviewはもう使わない * summaly v4 * update summaly * scrolling=no to fix pixiv --------- Co-authored-by: tamaina --- CHANGELOG.md | 4 + .../backend/src/server/web/UrlPreviewService.ts | 17 ++- packages/frontend/package.json | 2 + packages/frontend/src/components/MkUrlPreview.vue | 32 +++-- packages/frontend/test/init.ts | 4 + packages/frontend/test/url-preview.test.ts | 140 +++++++++++++++++++++ pnpm-lock.yaml | 68 +++++----- 7 files changed, 207 insertions(+), 60 deletions(-) create mode 100644 packages/frontend/test/url-preview.test.ts (limited to 'packages/backend/src/server/web') diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b043cd3ad..b3a5639ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ You should also include the user name that made the change. - アクティブユーザー数チャートの記録上限値を拡張 - Playのソースコード上限文字数を2倍に拡張 - 付箋ウィジェットの高さを設定可能に +- oEmbedをサポートしているウェブサイトのプレビューができるように + - YouTubeをoEmbedでロードし、プレビューで共有ボタンを押すとOSの共有画面がでるように + - ([FirefoxでSpotifyのプレビューを開けるとフルサイズじゃなくプレビューサイズだけ再生できる問題](https://bugzilla.mozilla.org/show_bug.cgi?id=1792395)があります) + - (すでにブラウザーでキャッシュされたリンクに対しては以前のプレビュー行動が行われてます。その場合、ブラウザーのキャッシュをクリアしてまた試してください。) - 配送先サーバーが410 Goneで応答してきた場合は自動で配送停止をするように - avatarBlurHash/bannerBlurHashの型をstringに限定 - APオブジェクトを入力してフェッチする機能とユーザーやノートの検索機能を分離 diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 2ce7293a52..21cf414087 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -19,9 +19,6 @@ export class UrlPreviewService { @Inject(DI.config) private config: Config, - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, - private metaService: MetaService, private httpRequestService: HttpRequestService, private loggerService: LoggerService, @@ -51,15 +48,15 @@ export class UrlPreviewService { reply.code(400); return; } - + const lang = request.query.lang; if (Array.isArray(lang)) { reply.code(400); return; } - + const meta = await this.metaService.fetch(); - + this.logger.info(meta.summalyProxy ? `(Proxy) Getting preview of ${url}@${lang} ...` : `Getting preview of ${url}@${lang} ...`); @@ -85,16 +82,16 @@ export class UrlPreviewService { throw new Error('unsupported schema included'); } - if (summary.player?.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) { + if (summary.player.url && !(summary.player.url.startsWith('http://') || summary.player.url.startsWith('https://'))) { throw new Error('unsupported schema included'); } - + summary.icon = this.wrap(summary.icon); summary.thumbnail = this.wrap(summary.thumbnail); - + // Cache 7days reply.header('Cache-Control', 'max-age=604800, immutable'); - + return summary; } catch (err) { this.logger.warn(`Failed to get preview of ${url}: ${err}`); diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 594f8781bd..54404c8c53 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -97,7 +97,9 @@ "eslint-plugin-vue": "9.9.0", "happy-dom": "8.9.0", "start-server-and-test": "2.0.0", + "summaly": "github:misskey-dev/summaly", "vitest": "^0.29.2", + "vitest-fetch-mock": "^0.2.2", "vue-eslint-parser": "9.1.0", "vue-tsc": "1.2.0" } diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 5381ecbfa5..094709e093 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -1,7 +1,18 @@