mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
refactor(server): modularize getFfmpegOptions (#3138)
* refactored `getFfmpegOptions` refactor transcoding, make separate service * fixed enum casing * use `Logger` instead of `console.log` * review suggestions * use enum for `getHandler` * fixed formatting * Update server/src/domain/media/media.util.ts Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * Update server/src/domain/media/media.util.ts Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * More specific imports, renamed codec classes * simplified code * removed unused import * added tests * added base implementation for bitrate and threads --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
@@ -51,24 +51,36 @@ export enum SystemConfigKey {
|
||||
STORAGE_TEMPLATE = 'storageTemplate.template',
|
||||
}
|
||||
|
||||
export enum TranscodePreset {
|
||||
export enum TranscodePolicy {
|
||||
ALL = 'all',
|
||||
OPTIMAL = 'optimal',
|
||||
REQUIRED = 'required',
|
||||
DISABLED = 'disabled',
|
||||
}
|
||||
|
||||
export enum VideoCodec {
|
||||
H264 = 'h264',
|
||||
HEVC = 'hevc',
|
||||
VP9 = 'vp9',
|
||||
}
|
||||
|
||||
export enum AudioCodec {
|
||||
MP3 = 'mp3',
|
||||
AAC = 'aac',
|
||||
OPUS = 'opus',
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
ffmpeg: {
|
||||
crf: number;
|
||||
threads: number;
|
||||
preset: string;
|
||||
targetVideoCodec: string;
|
||||
targetAudioCodec: string;
|
||||
targetVideoCodec: VideoCodec;
|
||||
targetAudioCodec: AudioCodec;
|
||||
targetResolution: string;
|
||||
maxBitrate: string;
|
||||
twoPass: boolean;
|
||||
transcode: TranscodePreset;
|
||||
transcode: TranscodePolicy;
|
||||
};
|
||||
job: Record<QueueName, { concurrency: number }>;
|
||||
oauth: {
|
||||
|
||||
@@ -65,7 +65,6 @@ const providers: Provider[] = [
|
||||
{ provide: IJobRepository, useClass: JobRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||
{ provide: IMediaRepository, useClass: MediaRepository },
|
||||
{ provide: IPartnerRepository, useClass: PartnerRepository },
|
||||
{ provide: IPersonRepository, useClass: PersonRepository },
|
||||
{ provide: ISearchRepository, useClass: TypesenseRepository },
|
||||
@@ -74,6 +73,7 @@ const providers: Provider[] = [
|
||||
{ provide: IStorageRepository, useClass: FilesystemProvider },
|
||||
{ provide: ISystemConfigRepository, useClass: SystemConfigRepository },
|
||||
{ provide: ITagRepository, useClass: TagRepository },
|
||||
{ provide: IMediaRepository, useClass: MediaRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
{ provide: IUserTokenRepository, useClass: UserTokenRepository },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo } from '@app/domain';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
|
||||
import fs from 'fs/promises';
|
||||
import sharp from 'sharp';
|
||||
@@ -7,6 +8,8 @@ import { promisify } from 'util';
|
||||
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
||||
|
||||
export class MediaRepository implements IMediaRepository {
|
||||
private logger = new Logger(MediaRepository.name);
|
||||
|
||||
crop(input: string, options: CropOptions): Promise<Buffer> {
|
||||
return sharp(input, { failOnError: false })
|
||||
.extract({
|
||||
@@ -47,7 +50,10 @@ export class MediaRepository implements IMediaRepository {
|
||||
`-vf scale='min(${size},iw)':'min(${size},ih)':force_original_aspect_ratio=increase`,
|
||||
])
|
||||
.output(output)
|
||||
.on('error', reject)
|
||||
.on('error', (err, stdout, stderr) => {
|
||||
this.logger.error(stderr);
|
||||
reject(err);
|
||||
})
|
||||
.on('end', resolve)
|
||||
.run();
|
||||
});
|
||||
@@ -87,7 +93,10 @@ export class MediaRepository implements IMediaRepository {
|
||||
ffmpeg(input, { niceness: 10 })
|
||||
.outputOptions(options.outputOptions)
|
||||
.output(output)
|
||||
.on('error', reject)
|
||||
.on('error', (err, stdout, stderr) => {
|
||||
this.logger.error(stderr);
|
||||
reject(err);
|
||||
})
|
||||
.on('end', resolve)
|
||||
.run();
|
||||
});
|
||||
@@ -102,7 +111,10 @@ export class MediaRepository implements IMediaRepository {
|
||||
.addOptions('-passlogfile', output)
|
||||
.addOptions('-f null')
|
||||
.output('/dev/null') // first pass output is not saved as only the .log file is needed
|
||||
.on('error', reject)
|
||||
.on('error', (err, stdout, stderr) => {
|
||||
this.logger.error(stderr);
|
||||
reject(err);
|
||||
})
|
||||
.on('end', () => {
|
||||
// second pass
|
||||
ffmpeg(input, { niceness: 10 })
|
||||
@@ -110,7 +122,10 @@ export class MediaRepository implements IMediaRepository {
|
||||
.addOptions('-pass', '2')
|
||||
.addOptions('-passlogfile', output)
|
||||
.output(output)
|
||||
.on('error', reject)
|
||||
.on('error', (err, stdout, stderr) => {
|
||||
this.logger.error(stderr);
|
||||
reject(err);
|
||||
})
|
||||
.on('end', () => fs.unlink(`${output}-0.log`))
|
||||
.on('end', () => fs.rm(`${output}-0.log.mbtree`, { force: true }))
|
||||
.on('end', resolve)
|
||||
|
||||
Reference in New Issue
Block a user