Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop

This commit is contained in:
syuilo 2023-02-12 10:21:20 +09:00
commit a8feed1eff
17 changed files with 338 additions and 16 deletions

View file

@ -67,6 +67,7 @@ export type Source = {
mediaProxy?: string;
proxyRemoteFiles?: boolean;
videoThumbnailGenerator?: string;
signToActivityPubGet?: boolean;
};
@ -89,6 +90,7 @@ export type Mixin = {
clientManifestExists: boolean;
mediaProxy: string;
externalMediaProxyEnabled: boolean;
videoThumbnailGenerator: string | null;
};
export type Config = Source & Mixin;
@ -144,6 +146,10 @@ export function loadConfig() {
mixin.mediaProxy = externalMediaProxy ?? internalMediaProxy;
mixin.externalMediaProxyEnabled = externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy;
mixin.videoThumbnailGenerator = config.videoThumbnailGenerator ?
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
: null;
if (!config.redis.prefix) config.redis.prefix = mixin.host;
return Object.assign(config, mixin);

View file

@ -250,6 +250,14 @@ export class DriveService {
@bindThis
public async generateAlts(path: string, type: string, generateWeb: boolean) {
if (type.startsWith('video/')) {
if (this.config.videoThumbnailGenerator != null) {
// videoThumbnailGeneratorが指定されていたら動画サムネイル生成はスキップ
return {
webpublic: null,
thumbnail: null,
}
}
try {
const thumbnail = await this.videoProcessingService.generateVideoThumbnail(path);
return {

View file

@ -6,6 +6,7 @@ import { ImageProcessingService } from '@/core/ImageProcessingService.js';
import type { IImage } from '@/core/ImageProcessingService.js';
import { createTempDir } from '@/misc/create-temp.js';
import { bindThis } from '@/decorators.js';
import { appendQuery, query } from '@/misc/prelude/url.js';
@Injectable()
export class VideoProcessingService {
@ -41,5 +42,18 @@ export class VideoProcessingService {
cleanup();
}
}
@bindThis
public getExternalVideoThumbnailUrl(url: string): string | null {
if (this.config.videoThumbnailGenerator == null) return null;
return appendQuery(
`${this.config.videoThumbnailGenerator}/thumbnail.webp`,
query({
thumbnail: '1',
url,
})
)
}
}

View file

@ -13,6 +13,7 @@ import { deepClone } from '@/misc/clone.js';
import { UtilityService } from '../UtilityService.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFolderEntityService } from './DriveFolderEntityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
type PackOptions = {
detail?: boolean,
@ -43,6 +44,7 @@ export class DriveFileEntityService {
private utilityService: UtilityService,
private driveFolderEntityService: DriveFolderEntityService,
private videoProcessingService: VideoProcessingService,
) {
}
@ -72,40 +74,63 @@ export class DriveFileEntityService {
}
@bindThis
public getPublicUrl(file: DriveFile, mode? : 'static' | 'avatar'): string | null { // static = thumbnail
const proxiedUrl = (url: string) => appendQuery(
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string | null {
return appendQuery(
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
query({
url,
...(mode ? { [mode]: '1' } : {}),
})
);
)
}
@bindThis
public getThumbnailUrl(file: DriveFile): string | null {
if (file.type.startsWith('video')) {
if (file.thumbnailUrl) return file.thumbnailUrl;
if (this.config.videoThumbnailGenerator == null) {
return this.videoProcessingService.getExternalVideoThumbnailUrl(file.webpublicUrl ?? file.url ?? file.uri);
}
} else if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
// 動画ではなくリモートかつメディアプロキシ
return this.getProxiedUrl(file.uri, 'static');
}
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
// リモートかつ期限切れはローカルプロキシを試みる
// 従来は/files/${thumbnailAccessKey}にアクセスしていたが、
// /filesはメディアプロキシにリダイレクトするようにしたため直接メディアプロキシを指定する
return this.getProxiedUrl(file.uri, 'static');
}
const url = file.webpublicUrl ?? file.url;
return file.thumbnailUrl ?? (isMimeImage(file.type, 'sharp-convertible-image') ? this.getProxiedUrl(url, 'static') : null);
}
@bindThis
public getPublicUrl(file: DriveFile, mode?: 'avatar'): string | null { // static = thumbnail
// リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
if (!(mode === 'static' && file.type.startsWith('video'))) {
return proxiedUrl(file.uri);
}
return this.getProxiedUrl(file.uri, mode);
}
// リモートかつ期限切れはローカルプロキシを試みる
if (file.uri != null && file.isLink && this.config.proxyRemoteFiles) {
const key = mode === 'static' ? file.thumbnailAccessKey : file.webpublicAccessKey;
const key = file.webpublicAccessKey;
if (key && !key.match('/')) { // 古いものはここにオブジェクトストレージキーが入ってるので除外
const url = `${this.config.url}/files/${key}`;
if (mode === 'avatar') return proxiedUrl(file.uri);
if (mode === 'avatar') return this.getProxiedUrl(file.uri, 'avatar');
return url;
}
}
const url = file.webpublicUrl ?? file.url;
if (mode === 'static') {
return file.thumbnailUrl ?? (isMimeImage(file.type, 'sharp-convertible-image') ? proxiedUrl(url) : null);
}
if (mode === 'avatar') {
return proxiedUrl(url);
return this.getProxiedUrl(url, 'avatar');
}
return url;
}
@ -183,7 +208,7 @@ export class DriveFileEntityService {
blurhash: file.blurhash,
properties: opts.self ? file.properties : this.getPublicProperties(file),
url: opts.self ? file.url : this.getPublicUrl(file),
thumbnailUrl: this.getPublicUrl(file, 'static'),
thumbnailUrl: this.getThumbnailUrl(file),
comment: file.comment,
folderId: file.folderId,
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {
@ -218,7 +243,7 @@ export class DriveFileEntityService {
blurhash: file.blurhash,
properties: opts.self ? file.properties : this.getPublicProperties(file),
url: opts.self ? file.url : this.getPublicUrl(file),
thumbnailUrl: this.getPublicUrl(file, 'static'),
thumbnailUrl: this.getThumbnailUrl(file),
comment: file.comment,
folderId: file.folderId,
folder: opts.detail && file.folderId ? this.driveFolderEntityService.pack(file.folderId, {

View file

@ -441,6 +441,14 @@ export class ActivityPubServerService {
fastify.addContentTypeParser('application/activity+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
fastify.addContentTypeParser('application/ld+json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'));
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Access-Control-Allow-Headers', 'Accept');
reply.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
reply.header('Access-Control-Allow-Origin', '*');
reply.header('Access-Control-Expose-Headers', 'Vary');
done();
});
//#region Routing
// inbox (limit: 64kb)
fastify.post('/inbox', { bodyLimit: 1024 * 64 }, async (request, reply) => await this.inbox(request, reply));

View file

@ -150,6 +150,12 @@ export class FileServerService {
file.cleanup();
return await reply.redirect(301, url.toString());
} else if (file.mime.startsWith('video/')) {
const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
if (externalThumbnail) {
file.cleanup();
return await reply.redirect(301, externalThumbnail);
}
image = await this.videoProcessingService.generateVideoThumbnail(file.path);
}
}

View file

@ -21,7 +21,7 @@
<div v-else ref="rootEl">
<div v-show="pagination.reversed && more" key="_more_" class="_margin">
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMoreAhead : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>

View file

@ -60,11 +60,17 @@ import { definePageMetadata } from '@/scripts/page-metadata';
let ads: any[] = $ref([]);
// ISOTZUTCTZ
const localTime = new Date();
const localTimeDiff = localTime.getTimezoneOffset() * 60 * 1000;
os.api('admin/ad/list').then(adsResponse => {
ads = adsResponse.map(r => {
const date = new Date(r.expiresAt);
date.setMilliseconds(date.getMilliseconds() - localTimeDiff);
return {
...r,
expiresAt: new Date(r.expiresAt).toISOString().slice(0, 16),
expiresAt: date.toISOString().slice(0, 16),
};
});
});