mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-07 12:36:57 +00:00
merge: Add web optimization for video files during processing (!1054)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1054 Approved-by: Hazelnoot <acomputerdog@gmail.com> Approved-by: dakkar <dakkar@thenautilus.net>
This commit is contained in:
commit
bba8e9fc79
2 changed files with 71 additions and 1 deletions
|
@ -159,6 +159,14 @@ export class DriveService {
|
||||||
// thunbnail, webpublic を必要なら生成
|
// thunbnail, webpublic を必要なら生成
|
||||||
const alts = await this.generateAlts(path, type, !file.uri);
|
const alts = await this.generateAlts(path, type, !file.uri);
|
||||||
|
|
||||||
|
if (type && type.startsWith('video/')) {
|
||||||
|
try {
|
||||||
|
await this.videoProcessingService.webOptimizeVideo(path, type);
|
||||||
|
} catch (err) {
|
||||||
|
this.registerLogger.warn(`Video optimization failed: ${err instanceof Error ? err.message : String(err)}`, { error: err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.meta.useObjectStorage) {
|
if (this.meta.useObjectStorage) {
|
||||||
//#region ObjectStorage params
|
//#region ObjectStorage params
|
||||||
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
|
let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
|
||||||
|
|
|
@ -3,24 +3,41 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import FFmpeg from 'fluent-ffmpeg';
|
import FFmpeg from 'fluent-ffmpeg';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
|
import { ImageProcessingService } from '@/core/ImageProcessingService.js';
|
||||||
import type { IImage } from '@/core/ImageProcessingService.js';
|
import type { IImage } from '@/core/ImageProcessingService.js';
|
||||||
import { createTempDir } from '@/misc/create-temp.js';
|
import { createTemp, createTempDir } from '@/misc/create-temp.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { appendQuery, query } from '@/misc/prelude/url.js';
|
import { appendQuery, query } from '@/misc/prelude/url.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
|
||||||
|
// faststart is only supported for MP4, M4A, M4W and MOV files (the MOV family).
|
||||||
|
// WebM (and Matroska) files always support faststart-like behavior.
|
||||||
|
const supportedMimeTypes = new Map([
|
||||||
|
['video/mp4', 'mp4'],
|
||||||
|
['video/m4a', 'mp4'],
|
||||||
|
['video/m4v', 'mp4'],
|
||||||
|
['video/quicktime', 'mov'],
|
||||||
|
]);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VideoProcessingService {
|
export class VideoProcessingService {
|
||||||
|
private readonly logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
|
||||||
private imageProcessingService: ImageProcessingService,
|
private imageProcessingService: ImageProcessingService,
|
||||||
|
|
||||||
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('video-processing');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -60,5 +77,50 @@ export class VideoProcessingService {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optimize video for web playback by adding faststart flag.
|
||||||
|
* This allows the video to start playing before it is fully downloaded.
|
||||||
|
* The original file is modified in-place.
|
||||||
|
* @param source Path to the video file
|
||||||
|
* @param mimeType The MIME type of the video
|
||||||
|
* @returns Promise that resolves when optimization is complete
|
||||||
|
*/
|
||||||
|
@bindThis
|
||||||
|
public async webOptimizeVideo(source: string, mimeType: string): Promise<void> {
|
||||||
|
const outputFormat = supportedMimeTypes.get(mimeType);
|
||||||
|
if (!outputFormat) {
|
||||||
|
this.logger.debug(`Skipping web optimization for unsupported MIME type: ${mimeType}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [tempPath, cleanup] = await createTemp();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
FFmpeg(source)
|
||||||
|
.format(outputFormat) // Specify output format
|
||||||
|
.addOutputOptions('-c copy') // Copy streams without re-encoding
|
||||||
|
.addOutputOptions('-movflags +faststart')
|
||||||
|
.on('error', reject)
|
||||||
|
.on('end', async () => {
|
||||||
|
try {
|
||||||
|
// Replace original file with optimized version
|
||||||
|
await fs.copyFile(tempPath, source);
|
||||||
|
this.logger.info(`Web-optimized video: ${source}`);
|
||||||
|
resolve();
|
||||||
|
} catch (copyError) {
|
||||||
|
reject(copyError);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.save(tempPath);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Failed to web-optimize video: ${source}`, { error });
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue