mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
* refactor(server): video transcode processor * refactor: rename shouldRotate to isVideoVertical, remove unnecessary await * refactor: rename getOptions to getFfmpegOptions to be clearer in that context * fix: optimal preset converting vertical videos already smaller than target resolution --------- Co-authored-by: Zack Pollard <zackpollard@ymail.com>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { IMediaRepository, ResizeOptions, VideoInfo } from '@app/domain';
|
|
import { exiftool } from 'exiftool-vendored';
|
|
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
|
|
import sharp from 'sharp';
|
|
import { promisify } from 'util';
|
|
|
|
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
|
|
|
export class MediaRepository implements IMediaRepository {
|
|
extractThumbnailFromExif(input: string, output: string): Promise<void> {
|
|
return exiftool.extractThumbnail(input, output);
|
|
}
|
|
|
|
async resize(input: string, output: string, options: ResizeOptions): Promise<void> {
|
|
switch (options.format) {
|
|
case 'webp':
|
|
await sharp(input, { failOnError: false })
|
|
.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
|
|
.webp()
|
|
.rotate()
|
|
.toFile(output);
|
|
return;
|
|
|
|
case 'jpeg':
|
|
await sharp(input, { failOnError: false })
|
|
.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
|
|
.jpeg()
|
|
.rotate()
|
|
.toFile(output);
|
|
return;
|
|
}
|
|
}
|
|
|
|
extractVideoThumbnail(input: string, output: string, size: number) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
ffmpeg(input)
|
|
.outputOptions([
|
|
'-ss 00:00:00.000',
|
|
'-frames:v 1',
|
|
`-vf scale='min(${size},iw)':'min(${size},ih)':force_original_aspect_ratio=increase`,
|
|
])
|
|
.output(output)
|
|
.on('error', reject)
|
|
.on('end', resolve)
|
|
.run();
|
|
});
|
|
}
|
|
|
|
async probe(input: string): Promise<VideoInfo> {
|
|
const results = await probe(input);
|
|
|
|
return {
|
|
streams: results.streams.map((stream) => ({
|
|
height: stream.height || 0,
|
|
width: stream.width || 0,
|
|
codecName: stream.codec_name,
|
|
codecType: stream.codec_type,
|
|
frameCount: Number.parseInt(stream.nb_frames ?? '0'),
|
|
rotation: Number.parseInt(`${stream.rotation ?? 0}`),
|
|
})),
|
|
};
|
|
}
|
|
|
|
transcode(input: string, output: string, options: string[]): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
ffmpeg(input)
|
|
//
|
|
.outputOptions(options)
|
|
.output(output)
|
|
.on('error', reject)
|
|
.on('end', resolve)
|
|
.run();
|
|
});
|
|
}
|
|
}
|