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:
Mert
2023-08-01 21:56:10 -04:00
committed by GitHub
parent b9cda59172
commit ee49f470b7
44 changed files with 1308 additions and 68 deletions

View File

@@ -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: {

View File

@@ -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;
}

View File

@@ -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)