mirror of
https://github.com/gabehf/Tdarr_Plugins.git
synced 2026-03-15 02:05:54 -07:00
Update flows
This commit is contained in:
parent
658857fdf4
commit
25c4fab8d9
73 changed files with 4295 additions and 839 deletions
|
|
@ -240,7 +240,7 @@ class CLI {
|
|||
const errorLogFull: string[] = [];
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Running ${this.config.cli} ${this.config.spawnArgs.join(' ')}`);
|
||||
this.config.jobLog(`Running ${this.config.cli} ${this.config.spawnArgs.join(' ')}`);
|
||||
const cliExitCode: number = await new Promise((resolve) => {
|
||||
try {
|
||||
const opts = this.config.spawnOpts || {};
|
||||
|
|
@ -248,14 +248,14 @@ class CLI {
|
|||
|
||||
thread.stdout.on('data', (data: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data.toString());
|
||||
// console.log(data.toString());
|
||||
errorLogFull.push(data.toString());
|
||||
this.parseOutput(data);
|
||||
});
|
||||
|
||||
thread.stderr.on('data', (data: string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(data.toString());
|
||||
// console.log(data.toString());
|
||||
errorLogFull.push(data.toString());
|
||||
this.parseOutput(data);
|
||||
});
|
||||
|
|
@ -272,7 +272,7 @@ class CLI {
|
|||
thread.on('close', (code: number) => {
|
||||
if (code !== 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(code, 'FFmpeg error');
|
||||
console.log(code, 'CLI error');
|
||||
}
|
||||
resolve(code);
|
||||
});
|
||||
|
|
@ -284,6 +284,10 @@ class CLI {
|
|||
}
|
||||
});
|
||||
|
||||
if (!this.config.logFullCliOutput) {
|
||||
this.config.jobLog(errorLogFull.slice(-1000).join(''));
|
||||
}
|
||||
|
||||
return {
|
||||
cliExitCode,
|
||||
errorLogFull,
|
||||
13
FlowPluginsTs/FlowHelpers/1.0.0/fileUtils.ts
Normal file
13
FlowPluginsTs/FlowHelpers/1.0.0/fileUtils.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export const getContainer = (filePath: string):string => {
|
||||
const parts = filePath.split('.');
|
||||
return parts[parts.length - 1];
|
||||
};
|
||||
|
||||
export const getFileName = (filePath: string):string => {
|
||||
const parts = filePath.split('/');
|
||||
const fileNameAndContainer = parts[parts.length - 1];
|
||||
const parts2 = fileNameAndContainer.split('.');
|
||||
return parts2[0];
|
||||
};
|
||||
|
||||
export const getFfType = (codecType:string):string => (codecType === 'video' ? 'v' : 'a');
|
||||
24
FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.test.ts
Normal file
24
FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { getEncoder } from './hardwareUtils';
|
||||
|
||||
const run = async () => {
|
||||
const encoderProperties = await getEncoder({
|
||||
targetCodec: 'h264',
|
||||
hardwareEncoding: true,
|
||||
// @ts-expect-error type
|
||||
args: {
|
||||
workerType: 'transcodegpu',
|
||||
ffmpegPath: 'ffmpeg',
|
||||
jobLog: (t:string) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(t);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log({
|
||||
encoderProperties,
|
||||
});
|
||||
};
|
||||
|
||||
void run();
|
||||
293
FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.ts
Normal file
293
FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.ts
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
import { IpluginInputArgs } from './interfaces/interfaces';
|
||||
|
||||
export const hasEncoder = async ({
|
||||
ffmpegPath,
|
||||
encoder,
|
||||
inputArgs,
|
||||
filter,
|
||||
}: {
|
||||
ffmpegPath: string,
|
||||
encoder: string,
|
||||
inputArgs: string[],
|
||||
filter: string,
|
||||
}): Promise<boolean> => {
|
||||
const { exec } = require('child_process');
|
||||
let isEnabled = false;
|
||||
try {
|
||||
isEnabled = await new Promise((resolve) => {
|
||||
const command = `${ffmpegPath} ${inputArgs.join(' ') || ''} -f lavfi -i color=c=black:s=256x256:d=1:r=30`
|
||||
+ ` ${filter || ''}`
|
||||
+ ` -c:v ${encoder} -f null /dev/null`;
|
||||
exec(command, (
|
||||
// eslint-disable-next-line
|
||||
error: any,
|
||||
// stdout,
|
||||
// stderr,
|
||||
) => {
|
||||
if (error) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
return isEnabled;
|
||||
};
|
||||
|
||||
interface IgpuEncoder {
|
||||
encoder: string,
|
||||
enabled: boolean,
|
||||
|
||||
inputArgs: string[],
|
||||
outputArgs: string[],
|
||||
filter: string,
|
||||
}
|
||||
|
||||
// credit to UNCode101 for this
|
||||
export const getBestNvencDevice = ({
|
||||
args,
|
||||
nvencDevice,
|
||||
}: {
|
||||
args: IpluginInputArgs
|
||||
nvencDevice: IgpuEncoder,
|
||||
}): IgpuEncoder => {
|
||||
const { execSync } = require('child_process');
|
||||
let gpu_num = -1;
|
||||
let lowest_gpu_util = 100000;
|
||||
let result_util = 0;
|
||||
let gpu_count = -1;
|
||||
let gpu_names = '';
|
||||
const gpus_to_exclude: string[] = [];
|
||||
// inputs.exclude_gpus === '' ? [] : inputs.exclude_gpus.split(',').map(Number);
|
||||
try {
|
||||
gpu_names = execSync('nvidia-smi --query-gpu=name --format=csv,noheader');
|
||||
gpu_names = gpu_names.toString().trim();
|
||||
const gpu_namesArr = gpu_names.split(/\r?\n/);
|
||||
/* When nvidia-smi returns an error it contains 'nvidia-smi' in the error
|
||||
Example: Linux: nvidia-smi: command not found
|
||||
Windows: 'nvidia-smi' is not recognized as an internal or external command,
|
||||
operable program or batch file. */
|
||||
if (!gpu_namesArr[0].includes('nvidia-smi')) {
|
||||
gpu_count = gpu_namesArr.length;
|
||||
}
|
||||
} catch (error) {
|
||||
args.jobLog('Error in reading nvidia-smi output! \n');
|
||||
}
|
||||
|
||||
if (gpu_count > 0) {
|
||||
for (let gpui = 0; gpui < gpu_count; gpui += 1) {
|
||||
// Check if GPU # is in GPUs to exclude
|
||||
if (gpus_to_exclude.includes(String(gpui))) {
|
||||
args.jobLog(`GPU ${gpui}: ${gpu_names[gpui]} is in exclusion list, will not be used!\n`);
|
||||
} else {
|
||||
try {
|
||||
const cmd_gpu = `nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i ${gpui}`;
|
||||
result_util = parseInt(execSync(cmd_gpu), 10);
|
||||
if (!Number.isNaN(result_util)) { // != "No devices were found") {
|
||||
args.jobLog(`GPU ${gpui} : Utilization ${result_util}%\n`);
|
||||
|
||||
if (result_util < lowest_gpu_util) {
|
||||
gpu_num = gpui;
|
||||
lowest_gpu_util = result_util;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
args.jobLog(`Error in reading GPU ${gpui} Utilization\nError: ${error}\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gpu_num >= 0) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
nvencDevice.inputArgs.push('-hwaccel_device', `${gpu_num}`);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
nvencDevice.outputArgs.push('-gpu', `${gpu_num}`);
|
||||
}
|
||||
|
||||
return nvencDevice;
|
||||
};
|
||||
|
||||
const encoderFilter = (encoder:string, targetCodec:string) => {
|
||||
if (targetCodec === 'hevc' && (encoder.includes('hevc') || encoder.includes('h265'))) {
|
||||
return true;
|
||||
} if (targetCodec === 'h264' && encoder.includes('h264')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getEncoder = async ({
|
||||
targetCodec,
|
||||
hardwareEncoding,
|
||||
args,
|
||||
}: {
|
||||
targetCodec: string,
|
||||
hardwareEncoding: boolean,
|
||||
args: IpluginInputArgs,
|
||||
}): Promise<{
|
||||
encoder: string,
|
||||
inputArgs: string[],
|
||||
outputArgs: string[],
|
||||
isGpu: boolean,
|
||||
}> => {
|
||||
if (
|
||||
args.workerType
|
||||
&& args.workerType.includes('gpu')
|
||||
&& hardwareEncoding && (targetCodec === 'hevc' || targetCodec === 'h264')) {
|
||||
const gpuEncoders: IgpuEncoder[] = [
|
||||
{
|
||||
encoder: 'hevc_nvenc',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'cuda',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'hevc_amf',
|
||||
enabled: false,
|
||||
inputArgs: [],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'hevc_vaapi',
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'vaapi',
|
||||
'-hwaccel_device',
|
||||
'/dev/dri/renderD128',
|
||||
'-hwaccel_output_format',
|
||||
'vaapi',
|
||||
],
|
||||
outputArgs: [],
|
||||
enabled: false,
|
||||
filter: '-vf format=nv12,hwupload',
|
||||
},
|
||||
{
|
||||
encoder: 'hevc_qsv',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'qsv',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'hevc_videotoolbox',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'videotoolbox',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
|
||||
{
|
||||
encoder: 'h264_nvenc',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'cuda',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'h264_amf',
|
||||
enabled: false,
|
||||
inputArgs: [],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'h264_qsv',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'qsv',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
{
|
||||
encoder: 'h264_videotoolbox',
|
||||
enabled: false,
|
||||
inputArgs: [
|
||||
'-hwaccel',
|
||||
'videotoolbox',
|
||||
],
|
||||
outputArgs: [],
|
||||
filter: '',
|
||||
},
|
||||
];
|
||||
|
||||
const filteredGpuEncoders = gpuEncoders.filter((device) => encoderFilter(device.encoder, targetCodec));
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const gpuEncoder of filteredGpuEncoders) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
gpuEncoder.enabled = await hasEncoder({
|
||||
ffmpegPath: args.ffmpegPath,
|
||||
encoder: gpuEncoder.encoder,
|
||||
inputArgs: gpuEncoder.inputArgs,
|
||||
filter: gpuEncoder.filter,
|
||||
});
|
||||
}
|
||||
|
||||
const enabledDevices = gpuEncoders.filter((device) => device.enabled === true);
|
||||
|
||||
if (enabledDevices.length > 0) {
|
||||
if (enabledDevices[0].encoder.includes('nvenc')) {
|
||||
const res = getBestNvencDevice({
|
||||
args,
|
||||
nvencDevice: enabledDevices[0],
|
||||
});
|
||||
|
||||
return {
|
||||
...res,
|
||||
isGpu: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
encoder: enabledDevices[0].encoder,
|
||||
inputArgs: enabledDevices[0].inputArgs,
|
||||
outputArgs: enabledDevices[0].outputArgs,
|
||||
isGpu: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (targetCodec === 'hevc') {
|
||||
return {
|
||||
encoder: 'libx265',
|
||||
inputArgs: [],
|
||||
outputArgs: [],
|
||||
isGpu: false,
|
||||
};
|
||||
} if (targetCodec === 'h264') {
|
||||
return {
|
||||
encoder: 'libx264',
|
||||
inputArgs: [],
|
||||
outputArgs: [],
|
||||
isGpu: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
encoder: targetCodec,
|
||||
inputArgs: [],
|
||||
outputArgs: [],
|
||||
isGpu: false,
|
||||
};
|
||||
};
|
||||
|
|
@ -2,8 +2,16 @@ import { IFileObject, Istreams } from './synced/IFileObject';
|
|||
import Ijob from './synced/jobInterface';
|
||||
|
||||
export interface IpluginInputUi {
|
||||
type: 'dropdown' | 'text',
|
||||
options: string[],
|
||||
type: 'dropdown' | 'text' | 'textarea',
|
||||
options?: string[],
|
||||
style?:Record<string, unknown>,
|
||||
onSelect?: {
|
||||
'hevc': {
|
||||
update: {
|
||||
quality: '28',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export interface IpluginInputs {
|
||||
|
|
@ -43,18 +51,23 @@ export interface IupdateWorker {
|
|||
|
||||
export interface IffmpegCommandStream extends Istreams {
|
||||
removed: boolean,
|
||||
targetCodec: string,
|
||||
args: string[],
|
||||
forceEncoding: boolean,
|
||||
inputArgs: string[],
|
||||
outputArgs: string[],
|
||||
}
|
||||
|
||||
export interface IffmpegCommand {
|
||||
inputFiles: string[],
|
||||
streams: IffmpegCommandStream[]
|
||||
container: string,
|
||||
hardwareDecoding: boolean,
|
||||
shouldProcess: boolean,
|
||||
overallInputArguments: string[],
|
||||
overallOuputArguments: string[],
|
||||
}
|
||||
|
||||
export interface Ivariables {
|
||||
ffmpegCommand?: IffmpegCommand
|
||||
ffmpegCommand: IffmpegCommand
|
||||
}
|
||||
|
||||
export interface IpluginOutputArgs {
|
||||
|
|
@ -63,7 +76,6 @@ export interface IpluginOutputArgs {
|
|||
_id: string,
|
||||
},
|
||||
variables: Ivariables
|
||||
|
||||
}
|
||||
|
||||
export interface IpluginInputArgs {
|
||||
|
|
@ -92,5 +104,16 @@ export interface IpluginInputArgs {
|
|||
lastSuccessfulRun: any,
|
||||
updateWorker: IupdateWorker,
|
||||
logFullCliOutput: boolean,
|
||||
logOutcome:(outcome:string) => void,
|
||||
logOutcome: (outcome: string) => void,
|
||||
deps: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fsextra: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parseArgsStringToArgv: any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
importFresh(path: string): any,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
axiosMiddleware: (endpoint: string, data: Record<string, unknown>) => Promise<any>,
|
||||
requireFromString: (pluginText: string, relativePath:string) => Record<string, unknown>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export interface Itags {
|
|||
}
|
||||
export interface Istreams {
|
||||
codec_name: string;
|
||||
codec_type?: string,
|
||||
codec_type: string,
|
||||
bit_rate?: number,
|
||||
channels?: number,
|
||||
tags?: Itags,
|
||||
|
|
@ -134,6 +134,7 @@ export interface ImediaInfo {
|
|||
'IsStreamable': string,
|
||||
'Encoded_Application': string,
|
||||
'Encoded_Library': string,
|
||||
BitRate: number,
|
||||
'extra': {
|
||||
'ErrorDetectionType': string,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue