mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(server): transcoding hardware acceleration (#3171)
* added transcode configs for nvenc,qsv and vaapi * updated dev docker compose * added software fallback * working vaapi * minor fixes and added tests * updated api * compile libvips * move hwaccel settings to `hwaccel.yml` * changed default dockerfile, moved `readdir` call * removed unused import * minor cleanup * fix for arm build * added documentation, minor fixes * added intel driver * updated docs styling * uppercase codec and api names * formatting * added tests * updated docs * removed semicolons * added link to `hwaccel.yml` * added newlines * added `hwaccel` section to docker-compose.prod.yml * ensure mesa drivers are installed * switch to mimalloc for sharp * moved build version and sha256 to json * let libmfx set the render device * possible fix for vp9 on qsv * updated tests * formatting * review suggestions * semicolon * moved `LD_PRELOAD` to start script * switched to jellyfin's ffmpeg package * fixed dockerfile * use cqp instead of icq for qsv vp9 * updated dockerfile * added sha256sum for other platforms * fixtures
This commit is contained in:
@@ -23,6 +23,7 @@ export enum SystemConfigKey {
|
||||
FFMPEG_MAX_BITRATE = 'ffmpeg.maxBitrate',
|
||||
FFMPEG_TWO_PASS = 'ffmpeg.twoPass',
|
||||
FFMPEG_TRANSCODE = 'ffmpeg.transcode',
|
||||
FFMPEG_ACCEL = 'ffmpeg.accel',
|
||||
|
||||
JOB_THUMBNAIL_GENERATION_CONCURRENCY = 'job.thumbnailGeneration.concurrency',
|
||||
JOB_METADATA_EXTRACTION_CONCURRENCY = 'job.metadataExtraction.concurrency',
|
||||
@@ -71,6 +72,13 @@ export enum AudioCodec {
|
||||
OPUS = 'opus',
|
||||
}
|
||||
|
||||
export enum TranscodeHWAccel {
|
||||
NVENC = 'nvenc',
|
||||
QSV = 'qsv',
|
||||
VAAPI = 'vaapi',
|
||||
DISABLED = 'disabled',
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
ffmpeg: {
|
||||
crf: number;
|
||||
@@ -82,6 +90,7 @@ export interface SystemConfig {
|
||||
maxBitrate: string;
|
||||
twoPass: boolean;
|
||||
transcode: TranscodePolicy;
|
||||
accel: TranscodeHWAccel;
|
||||
};
|
||||
job: Record<QueueName, { concurrency: number }>;
|
||||
oauth: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DiskUsage, ImmichReadStream, ImmichZipStream, IStorageRepository } from '@app/domain';
|
||||
import archiver from 'archiver';
|
||||
import { constants, createReadStream, existsSync, mkdirSync } from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import fs, { readdir } from 'fs/promises';
|
||||
import mv from 'mv';
|
||||
import { promisify } from 'node:util';
|
||||
import path from 'path';
|
||||
@@ -92,4 +92,6 @@ export class FilesystemProvider implements IStorageRepository {
|
||||
total: stats.blocks * stats.bsize,
|
||||
};
|
||||
}
|
||||
|
||||
readdir = readdir;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import sharp from 'sharp';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
||||
sharp.concurrency(0);
|
||||
|
||||
export class MediaRepository implements IMediaRepository {
|
||||
private logger = new Logger(MediaRepository.name);
|
||||
@@ -73,7 +74,7 @@ export class MediaRepository implements IMediaRepository {
|
||||
.map((stream) => ({
|
||||
height: stream.height || 0,
|
||||
width: stream.width || 0,
|
||||
codecName: stream.codec_name,
|
||||
codecName: stream.codec_name === 'h265' ? 'hevc' : stream.codec_name,
|
||||
codecType: stream.codec_type,
|
||||
frameCount: Number.parseInt(stream.nb_frames ?? '0'),
|
||||
rotation: Number.parseInt(`${stream.rotation ?? 0}`),
|
||||
@@ -91,6 +92,7 @@ export class MediaRepository implements IMediaRepository {
|
||||
if (!options.twoPass) {
|
||||
return new Promise((resolve, reject) => {
|
||||
ffmpeg(input, { niceness: 10 })
|
||||
.inputOptions(options.inputOptions)
|
||||
.outputOptions(options.outputOptions)
|
||||
.output(output)
|
||||
.on('error', (err, stdout, stderr) => {
|
||||
@@ -106,6 +108,7 @@ export class MediaRepository implements IMediaRepository {
|
||||
// recommended for vp9 for better quality and compression
|
||||
return new Promise((resolve, reject) => {
|
||||
ffmpeg(input, { niceness: 10 })
|
||||
.inputOptions(options.inputOptions)
|
||||
.outputOptions(options.outputOptions)
|
||||
.addOptions('-pass', '1')
|
||||
.addOptions('-passlogfile', output)
|
||||
@@ -118,6 +121,7 @@ export class MediaRepository implements IMediaRepository {
|
||||
.on('end', () => {
|
||||
// second pass
|
||||
ffmpeg(input, { niceness: 10 })
|
||||
.inputOptions(options.inputOptions)
|
||||
.outputOptions(options.outputOptions)
|
||||
.addOptions('-pass', '2')
|
||||
.addOptions('-passlogfile', output)
|
||||
|
||||
Reference in New Issue
Block a user