mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 22:51:59 +00:00
use old filters for cpu and when vulkan gpu is not available
This commit is contained in:
parent
86f113cc96
commit
9ffc2e59ef
6 changed files with 250 additions and 108 deletions
54
server/package-lock.json
generated
54
server/package-lock.json
generated
|
@ -58,6 +58,7 @@
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sharp": "^0.33.0",
|
"sharp": "^0.33.0",
|
||||||
|
"shelljs": "^0.8.5",
|
||||||
"sirv": "^2.0.4",
|
"sirv": "^2.0.4",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typeorm": "^0.3.17",
|
"typeorm": "^0.3.17",
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
"@types/node": "^20.5.7",
|
"@types/node": "^20.5.7",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
|
"@types/shelljs": "^0.8.15",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
@ -5741,6 +5743,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/glob": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/minimatch": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/http-assert": {
|
"node_modules/@types/http-assert": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz",
|
||||||
|
@ -5847,6 +5859,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
||||||
"integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg=="
|
"integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/minimatch": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/mock-fs": {
|
"node_modules/@types/mock-fs": {
|
||||||
"version": "4.13.4",
|
"version": "4.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
|
||||||
|
@ -6043,6 +6061,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/shelljs": {
|
||||||
|
"version": "0.8.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz",
|
||||||
|
"integrity": "sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/glob": "~7.2.0",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/shimmer": {
|
"node_modules/@types/shimmer": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz",
|
||||||
|
@ -20045,6 +20073,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/glob": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/minimatch": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/http-assert": {
|
"@types/http-assert": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz",
|
||||||
|
@ -20151,6 +20189,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz",
|
||||||
"integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg=="
|
"integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg=="
|
||||||
},
|
},
|
||||||
|
"@types/minimatch": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/mock-fs": {
|
"@types/mock-fs": {
|
||||||
"version": "4.13.4",
|
"version": "4.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.4.tgz",
|
||||||
|
@ -20334,6 +20378,16 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/shelljs": {
|
||||||
|
"version": "0.8.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz",
|
||||||
|
"integrity": "sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/glob": "~7.2.0",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/shimmer": {
|
"@types/shimmer": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz",
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sharp": "^0.33.0",
|
"sharp": "^0.33.0",
|
||||||
|
"shelljs": "^0.8.5",
|
||||||
"sirv": "^2.0.4",
|
"sirv": "^2.0.4",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typeorm": "^0.3.17",
|
"typeorm": "^0.3.17",
|
||||||
|
@ -107,6 +108,7 @@
|
||||||
"@types/node": "^20.5.7",
|
"@types/node": "^20.5.7",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
|
"@types/shelljs": "^0.8.15",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@types/ua-parser-js": "^0.7.36",
|
"@types/ua-parser-js": "^0.7.36",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
|
|
|
@ -66,6 +66,25 @@ export interface BitrateDistribution {
|
||||||
unit: string;
|
unit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum VulkanDeviceType {
|
||||||
|
OTHER = 'OTHER',
|
||||||
|
INTEGRATED_GPU = 'INTEGRATED_GPU',
|
||||||
|
DISCRETE_GPU = 'DISCRETE_GPU',
|
||||||
|
VIRTUAL_GPU = 'VIRTUAL_GPU',
|
||||||
|
CPU = 'CPU',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VulkanDevice {
|
||||||
|
index: number;
|
||||||
|
type: VulkanDeviceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeviceSummary {
|
||||||
|
driDevices: string[];
|
||||||
|
hasOpenCL: boolean;
|
||||||
|
vulkanDevices: VulkanDevice[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface VideoCodecSWConfig {
|
export interface VideoCodecSWConfig {
|
||||||
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeOptions;
|
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeOptions;
|
||||||
}
|
}
|
||||||
|
@ -84,4 +103,5 @@ export interface IMediaRepository {
|
||||||
// video
|
// video
|
||||||
probe(input: string): Promise<VideoInfo>;
|
probe(input: string): Promise<VideoInfo>;
|
||||||
transcode(input: string, output: string | Writable, options: TranscodeOptions): Promise<void>;
|
transcode(input: string, output: string | Writable, options: TranscodeOptions): Promise<void>;
|
||||||
|
getVulkanDevices(): Promise<VulkanDevice[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import fs from 'node:fs/promises';
|
||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
import shell, { ShellString } from 'shelljs';
|
||||||
import { Colorspace } from 'src/entities/system-config.entity';
|
import { Colorspace } from 'src/entities/system-config.entity';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import {
|
import {
|
||||||
|
@ -13,11 +14,14 @@ import {
|
||||||
ThumbnailOptions,
|
ThumbnailOptions,
|
||||||
TranscodeOptions,
|
TranscodeOptions,
|
||||||
VideoInfo,
|
VideoInfo,
|
||||||
|
VulkanDevice,
|
||||||
|
VulkanDeviceType,
|
||||||
} from 'src/interfaces/media.interface';
|
} from 'src/interfaces/media.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
import { Instrumentation } from 'src/utils/instrumentation';
|
||||||
import { handlePromiseError } from 'src/utils/misc';
|
import { handlePromiseError } from 'src/utils/misc';
|
||||||
|
|
||||||
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
const probe = promisify<string, FfprobeData>(ffmpeg.ffprobe);
|
||||||
|
const exec = promisify<string, ShellString>(shell.exec);
|
||||||
sharp.concurrency(0);
|
sharp.concurrency(0);
|
||||||
sharp.cache({ files: 0 });
|
sharp.cache({ files: 0 });
|
||||||
|
|
||||||
|
@ -150,6 +154,31 @@ export class MediaRepository implements IMediaRepository {
|
||||||
return { width, height };
|
return { width, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getVulkanDevices(): Promise<VulkanDevice[]> {
|
||||||
|
return [
|
||||||
|
{ index: 0, type: VulkanDeviceType.DISCRETE_GPU },
|
||||||
|
{ index: 1, type: VulkanDeviceType.CPU },
|
||||||
|
];
|
||||||
|
const devices = [];
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const vulkanInfo = JSON.parse(await exec(`vulkaninfo --json=${i} -o /dev/tty`));
|
||||||
|
const deviceType =
|
||||||
|
vulkanInfo['capabilities']['device']['properties']['VkPhysicalDeviceProperties']['deviceType'];
|
||||||
|
devices.push({
|
||||||
|
index: i,
|
||||||
|
type: deviceType.replace('VK_PHYSICAL_DEVICE_TYPE_', ''),
|
||||||
|
});
|
||||||
|
i++;
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
private configureFfmpegCall(input: string, output: string | Writable, options: TranscodeOptions) {
|
private configureFfmpegCall(input: string, output: string | Writable, options: TranscodeOptions) {
|
||||||
return ffmpeg(input, { niceness: 10 })
|
return ffmpeg(input, { niceness: 10 })
|
||||||
.inputOptions(options.inputOptions)
|
.inputOptions(options.inputOptions)
|
||||||
|
|
|
@ -27,7 +27,13 @@ import {
|
||||||
QueueName,
|
QueueName,
|
||||||
} from 'src/interfaces/job.interface';
|
} from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from 'src/interfaces/media.interface';
|
import {
|
||||||
|
AudioStreamInfo,
|
||||||
|
DeviceSummary,
|
||||||
|
IMediaRepository,
|
||||||
|
VideoCodecHWConfig,
|
||||||
|
VideoStreamInfo,
|
||||||
|
} from 'src/interfaces/media.interface';
|
||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
|
@ -50,8 +56,7 @@ import { usePagination } from 'src/utils/pagination';
|
||||||
export class MediaService {
|
export class MediaService {
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private openCL: boolean | null = null;
|
private deviceSummary: DeviceSummary | null = null;
|
||||||
private devices: string[] | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
|
@ -492,22 +497,23 @@ export class MediaService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getHWCodecConfig(config: SystemConfigFFmpegDto) {
|
private async getHWCodecConfig(config: SystemConfigFFmpegDto) {
|
||||||
|
const deviceSummary = await this.getDeviceSummary();
|
||||||
let handler: VideoCodecHWConfig;
|
let handler: VideoCodecHWConfig;
|
||||||
switch (config.accel) {
|
switch (config.accel) {
|
||||||
case TranscodeHWAccel.NVENC: {
|
case TranscodeHWAccel.NVENC: {
|
||||||
handler = new NVENCConfig(config);
|
handler = new NVENCConfig(config, deviceSummary);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TranscodeHWAccel.QSV: {
|
case TranscodeHWAccel.QSV: {
|
||||||
handler = new QSVConfig(config, await this.getDevices());
|
handler = new QSVConfig(config, deviceSummary);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TranscodeHWAccel.VAAPI: {
|
case TranscodeHWAccel.VAAPI: {
|
||||||
handler = new VAAPIConfig(config, await this.getDevices());
|
handler = new VAAPIConfig(config, deviceSummary);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case TranscodeHWAccel.RKMPP: {
|
case TranscodeHWAccel.RKMPP: {
|
||||||
handler = new RKMPPConfig(config, await this.getDevices());
|
handler = new RKMPPConfig(config, deviceSummary);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -559,26 +565,42 @@ export class MediaService {
|
||||||
return extractedSize >= targetSize;
|
return extractedSize >= targetSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getDevices() {
|
private async getDeviceSummary(): Promise<DeviceSummary> {
|
||||||
if (!this.devices) {
|
if (!this.deviceSummary) {
|
||||||
this.devices = await this.storageRepository.readdir('/dev/dri');
|
this.deviceSummary = {
|
||||||
|
driDevices: await this.getDriDevices(),
|
||||||
|
hasOpenCL: await this.hasOpenCL(),
|
||||||
|
vulkanDevices: await this.mediaRepository.getVulkanDevices(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.devices;
|
return this.deviceSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDriDevices() {
|
||||||
|
const devices = await this.storageRepository.readdir('/dev/dri');
|
||||||
|
return devices
|
||||||
|
.filter((device) => device.startsWith('renderD') || device.startsWith('card'))
|
||||||
|
.sort((a, b) => {
|
||||||
|
// order GPU devices first
|
||||||
|
if (a.startsWith('card') && b.startsWith('renderD')) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a.startsWith('renderD') && b.startsWith('card')) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return -a.localeCompare(b);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async hasOpenCL() {
|
private async hasOpenCL() {
|
||||||
if (this.openCL === null) {
|
try {
|
||||||
try {
|
const maliIcdStat = await this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd');
|
||||||
const maliIcdStat = await this.storageRepository.stat('/etc/OpenCL/vendors/mali.icd');
|
const maliDeviceStat = await this.storageRepository.stat('/dev/mali0');
|
||||||
const maliDeviceStat = await this.storageRepository.stat('/dev/mali0');
|
return maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice();
|
||||||
this.openCL = maliIcdStat.isFile() && maliDeviceStat.isCharacterDevice();
|
} catch {
|
||||||
} catch {
|
this.logger.warn('OpenCL not available for transcoding, using CPU instead.');
|
||||||
this.logger.warn('OpenCL not available for transcoding, using CPU instead.');
|
return false;
|
||||||
this.openCL = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.openCL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,12 @@ import { CQMode, ToneMapping, TranscodeHWAccel, TranscodeTarget, VideoCodec } fr
|
||||||
import {
|
import {
|
||||||
AudioStreamInfo,
|
AudioStreamInfo,
|
||||||
BitrateDistribution,
|
BitrateDistribution,
|
||||||
|
DeviceSummary,
|
||||||
TranscodeOptions,
|
TranscodeOptions,
|
||||||
VideoCodecHWConfig,
|
VideoCodecHWConfig,
|
||||||
VideoCodecSWConfig,
|
VideoCodecSWConfig,
|
||||||
VideoStreamInfo,
|
VideoStreamInfo,
|
||||||
|
VulkanDeviceType,
|
||||||
} from 'src/interfaces/media.interface';
|
} from 'src/interfaces/media.interface';
|
||||||
|
|
||||||
class BaseConfig implements VideoCodecSWConfig {
|
class BaseConfig implements VideoCodecSWConfig {
|
||||||
|
@ -37,7 +39,7 @@ class BaseConfig implements VideoCodecSWConfig {
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
getBaseInputOptions(videoStream: VideoStreamInfo): string[] {
|
getBaseInputOptions(videoStream: VideoStreamInfo): string[] {
|
||||||
return [...this.getDeviceOptions(), ...this.getInputThreadOptions()];
|
return this.getInputThreadOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
|
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
|
||||||
|
@ -79,39 +81,20 @@ class BaseConfig implements VideoCodecSWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterOptions(videoStream: VideoStreamInfo) {
|
getFilterOptions(videoStream: VideoStreamInfo) {
|
||||||
const options = ['hwupload=derive_device=vulkan'];
|
const options = [];
|
||||||
if (this.shouldScale(videoStream)) {
|
if (this.shouldScale(videoStream)) {
|
||||||
const { width, height } = this.getSize(videoStream);
|
const { width, height } = this.getSize(videoStream);
|
||||||
options.push(`scale_vulkan=w=${width}:h=${height}`);
|
options.push(`scale=w=${width}:h=${height}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const colors = this.getColors();
|
if (this.shouldToneMap(videoStream)) {
|
||||||
const libplaceboOptions = [
|
options.push(...this.getToneMapping(videoStream));
|
||||||
'format=yuv420p',
|
|
||||||
'upscaler=none',
|
|
||||||
'downscaler=none',
|
|
||||||
`tonemapping=${this.shouldToneMap(videoStream) ? this.config.tonemap : 'clip'}`,
|
|
||||||
`colorspace=${colors.matrix}`,
|
|
||||||
`color_primaries=${colors.primaries}`,
|
|
||||||
`color_trc=${colors.transfer}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
// use faster settings on cpu, nicer settings on gpu
|
|
||||||
if (this.config.accel === TranscodeHWAccel.DISABLED) {
|
|
||||||
libplaceboOptions.push('peak_detect=false');
|
|
||||||
} else {
|
|
||||||
libplaceboOptions.push('deband=true', 'deband_iterations=3', 'deband_radius=8', 'deband_threshold=6');
|
|
||||||
}
|
}
|
||||||
|
options.push('format=yuv420p');
|
||||||
|
|
||||||
const libplacebo = `libplacebo=${libplaceboOptions.join(':')}`;
|
|
||||||
options.push(libplacebo, this.getFilterEnd(), 'format=yuv420p');
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterEnd(): string {
|
|
||||||
return 'hwdownload';
|
|
||||||
}
|
|
||||||
|
|
||||||
getPresetOptions() {
|
getPresetOptions() {
|
||||||
return [`-preset ${this.config.preset}`];
|
return [`-preset ${this.config.preset}`];
|
||||||
}
|
}
|
||||||
|
@ -243,34 +226,17 @@ class BaseConfig implements VideoCodecSWConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceOptions() {
|
getToneMapping(videoStream: VideoStreamInfo) {
|
||||||
return [
|
if (!this.shouldToneMap(videoStream)) {
|
||||||
`-init_hw_device ${this.getAccel()}=${this.getDevice()}`,
|
return [];
|
||||||
`-filter_hw_device ${this.getAccel()}`,
|
|
||||||
`-hwaccel ${this.getAccel()}`,
|
|
||||||
`-hwaccel_output_format ${this.getOutputFormat()}`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
getDevice() {
|
|
||||||
let device = this.getAccel();
|
|
||||||
if (this.getDeviceSpecifier() !== null) {
|
|
||||||
device += `:${this.getDeviceSpecifier()}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return device;
|
const colors = this.getColors();
|
||||||
}
|
return [
|
||||||
|
`zscale=t=linear:npl=${this.getNPL()}`,
|
||||||
getDeviceSpecifier(): string | null {
|
`tonemap=${this.config.tonemap}:desat=0`,
|
||||||
return null;
|
`zscale=p=${colors.primaries}:t=${colors.transfer}:m=${colors.matrix}:range=pc`,
|
||||||
}
|
];
|
||||||
|
|
||||||
getAccel() {
|
|
||||||
return 'vulkan';
|
|
||||||
}
|
|
||||||
|
|
||||||
getOutputFormat() {
|
|
||||||
return this.getAccel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAudioCodec(): string {
|
getAudioCodec(): string {
|
||||||
|
@ -299,35 +265,50 @@ class BaseConfig implements VideoCodecSWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
|
export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
|
||||||
protected devices: string[];
|
protected deviceSummary: DeviceSummary;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected config: SystemConfigFFmpegDto,
|
protected config: SystemConfigFFmpegDto,
|
||||||
devices: string[] = [],
|
deviceSummary: DeviceSummary,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
this.devices = this.validateDevices(devices);
|
this.deviceSummary = deviceSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterOptions(videoStream: VideoStreamInfo) {
|
||||||
|
const options = ['hwupload=derive_device=vulkan'];
|
||||||
|
if (this.shouldScale(videoStream)) {
|
||||||
|
const { width, height } = this.getSize(videoStream);
|
||||||
|
options.push(`scale_vulkan=w=${width}:h=${height}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push(...this.getToneMapping(videoStream), `hwupload=derive_device=${this.getAccel()}`);
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getToneMapping(videoStream: VideoStreamInfo) {
|
||||||
|
const colors = this.getColors();
|
||||||
|
const libplaceboOptions = [
|
||||||
|
`tonemapping=${this.shouldToneMap(videoStream) ? this.config.tonemap : 'clip'}`,
|
||||||
|
`colorspace=${colors.matrix}`,
|
||||||
|
`color_primaries=${colors.primaries}`,
|
||||||
|
`color_trc=${colors.transfer}`,
|
||||||
|
'format=yuv420p',
|
||||||
|
'deband=true',
|
||||||
|
'deband_iterations=3',
|
||||||
|
'deband_radius=8',
|
||||||
|
'deband_threshold=6',
|
||||||
|
'upscaler=none',
|
||||||
|
'downscaler=none',
|
||||||
|
];
|
||||||
|
|
||||||
|
return [`libplacebo=${libplaceboOptions.join(':')}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportedCodecs() {
|
getSupportedCodecs() {
|
||||||
return [VideoCodec.H264, VideoCodec.HEVC];
|
return [VideoCodec.H264, VideoCodec.HEVC];
|
||||||
}
|
}
|
||||||
|
|
||||||
validateDevices(devices: string[]) {
|
|
||||||
return devices
|
|
||||||
.filter((device) => device.startsWith('renderD') || device.startsWith('card'))
|
|
||||||
.sort((a, b) => {
|
|
||||||
// order GPU devices first
|
|
||||||
if (a.startsWith('card') && b.startsWith('renderD')) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.startsWith('renderD') && b.startsWith('card')) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return -a.localeCompare(b);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getVideoCodec(): string {
|
getVideoCodec(): string {
|
||||||
return `${this.config.targetVideoCodec}_${this.config.accel}`;
|
return `${this.config.targetVideoCodec}_${this.config.accel}`;
|
||||||
}
|
}
|
||||||
|
@ -346,23 +327,61 @@ export class BaseHWConfig extends BaseConfig implements VideoCodecHWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceName = device.replace('/dev/dri/', '');
|
const deviceName = device.replace('/dev/dri/', '');
|
||||||
if (!this.devices.includes(deviceName)) {
|
if (!this.deviceSummary.driDevices.includes(deviceName)) {
|
||||||
throw new Error(`Device '${device}' does not exist`);
|
throw new Error(`Device '${device}' does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return device;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
getBaseInputOptions(videoStream: VideoStreamInfo): string[] {
|
||||||
|
return [...this.getDeviceOptions(), ...this.getInputThreadOptions()];
|
||||||
|
}
|
||||||
|
|
||||||
getInputThreadOptions() {
|
getInputThreadOptions() {
|
||||||
return [`-threads ${this.config.threads <= 0 ? 1 : this.config.threads}`];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getOutputThreadOptions() {
|
getOutputThreadOptions() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterEnd(): string {
|
getDeviceOptions() {
|
||||||
return `hwupload=derive_device=${this.getAccel()}`;
|
return [
|
||||||
|
`-init_hw_device ${this.getAccel()}=${this.getDevice()}`,
|
||||||
|
`-filter_hw_device ${this.getAccel()}`,
|
||||||
|
`-hwaccel ${this.getAccel()}`,
|
||||||
|
`-hwaccel_output_format ${this.getOutputFormat()}`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getDevice() {
|
||||||
|
let device = this.getAccel();
|
||||||
|
if (this.getDeviceSpecifier() !== null) {
|
||||||
|
device += `:${this.getDeviceSpecifier()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDeviceSpecifier(): string | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccel() {
|
||||||
|
if (!this.deviceSummary.vulkanDevices.some((device) => device.type !== VulkanDeviceType.CPU)) {
|
||||||
|
return 'vulkan';
|
||||||
|
}
|
||||||
|
return this.getPreferredAccel();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreferredAccel() {
|
||||||
|
return 'vulkan';
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutputFormat() {
|
||||||
|
return this.getAccel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,7 +507,7 @@ export class AV1Config extends BaseConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NVENCConfig extends BaseHWConfig {
|
export class NVENCConfig extends BaseHWConfig {
|
||||||
getAccel() {
|
getPreferredAccel() {
|
||||||
return 'cuda';
|
return 'cuda';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,18 +580,18 @@ export class NVENCConfig extends BaseHWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QSVConfig extends BaseHWConfig {
|
export class QSVConfig extends BaseHWConfig {
|
||||||
getAccel() {
|
getPreferredAccel() {
|
||||||
return 'qsv';
|
return 'qsv';
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceSpecifier() {
|
getDeviceSpecifier() {
|
||||||
if (this.devices.length === 0) {
|
if (this.deviceSummary.driDevices.length === 0) {
|
||||||
throw new Error('No VAAPI device found');
|
throw new Error('No VAAPI device found');
|
||||||
}
|
}
|
||||||
|
|
||||||
let hwDevice = this.getPreferredDevice();
|
let hwDevice = this.getPreferredDevice();
|
||||||
if (hwDevice === null) {
|
if (hwDevice === null) {
|
||||||
hwDevice = `/dev/dri/${this.devices[0]}`;
|
hwDevice = `/dev/dri/${this.deviceSummary.driDevices[0]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hwDevice;
|
return hwDevice;
|
||||||
|
@ -631,18 +650,18 @@ export class QSVConfig extends BaseHWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VAAPIConfig extends BaseHWConfig {
|
export class VAAPIConfig extends BaseHWConfig {
|
||||||
getAccel() {
|
getPreferredAccel() {
|
||||||
return 'vaapi';
|
return 'vaapi';
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceSpecifier() {
|
getDeviceSpecifier() {
|
||||||
if (this.devices.length === 0) {
|
if (this.deviceSummary.driDevices.length === 0) {
|
||||||
throw new Error('No VAAPI device found');
|
throw new Error('No VAAPI device found');
|
||||||
}
|
}
|
||||||
|
|
||||||
let hwDevice = this.getPreferredDevice();
|
let hwDevice = this.getPreferredDevice();
|
||||||
if (hwDevice === null) {
|
if (hwDevice === null) {
|
||||||
hwDevice = `/dev/dri/${this.devices[0]}`;
|
hwDevice = `/dev/dri/${this.deviceSummary.driDevices[0]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hwDevice;
|
return hwDevice;
|
||||||
|
@ -697,14 +716,14 @@ export class RKMPPConfig extends BaseHWConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceOptions(): string[] {
|
getDeviceOptions(): string[] {
|
||||||
if (this.devices.length === 0) {
|
if (this.deviceSummary.driDevices.length === 0) {
|
||||||
throw new Error('No RKMPP device found');
|
throw new Error('No RKMPP device found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...super.getDeviceOptions(), '-afbc rga'];
|
return [...super.getDeviceOptions(), '-afbc rga'];
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccel() {
|
getPreferredAccel() {
|
||||||
return 'rkmpp';
|
return 'rkmpp';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -741,8 +760,4 @@ export class RKMPPConfig extends BaseHWConfig {
|
||||||
getSupportedCodecs() {
|
getSupportedCodecs() {
|
||||||
return [VideoCodec.H264, VideoCodec.HEVC];
|
return [VideoCodec.H264, VideoCodec.HEVC];
|
||||||
}
|
}
|
||||||
|
|
||||||
getVideoCodec(): string {
|
|
||||||
return `${this.config.targetVideoCodec}_rkmpp`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue