mirror of
https://github.com/gabehf/Tdarr_Plugins.git
synced 2026-03-17 11:16:26 -07:00
Auto detect nvenc gpu with least utilisation
This commit is contained in:
parent
fe8864fcc0
commit
911a388d21
1 changed files with 115 additions and 10 deletions
|
|
@ -151,6 +151,25 @@ const details = () => ({
|
||||||
\\nExample:\\n
|
\\nExample:\\n
|
||||||
false`,
|
false`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'exclude_gpus',
|
||||||
|
type: 'string',
|
||||||
|
defaultValue: '',
|
||||||
|
inputUI: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
tooltip: `Specify the id(s) of any GPUs that needs to be excluded from assigning transcoding tasks.
|
||||||
|
\\n Seperate with a comma (,). Leave empty to disable.
|
||||||
|
\\n Get GPU numbers in the node by running 'nvidia-smi'
|
||||||
|
\\nExample:\\n
|
||||||
|
0,1,3,8
|
||||||
|
|
||||||
|
\\nExample:\\n
|
||||||
|
3
|
||||||
|
|
||||||
|
\\nExample:\\n
|
||||||
|
0`,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -162,15 +181,18 @@ const bframeSupport = [
|
||||||
const hasEncoder = async ({
|
const hasEncoder = async ({
|
||||||
ffmpegPath,
|
ffmpegPath,
|
||||||
encoder,
|
encoder,
|
||||||
|
inputArgs,
|
||||||
}) => {
|
}) => {
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
let isEnabled = false;
|
let isEnabled = false;
|
||||||
try {
|
try {
|
||||||
isEnabled = await new Promise((resolve) => {
|
isEnabled = await new Promise((resolve) => {
|
||||||
exec(`${ffmpegPath} -f lavfi -i color=c=black:s=256x256:d=1:r=30 -c:v ${encoder} -f null /dev/null`, (
|
const command = `${ffmpegPath} ${inputArgs || ''} -f lavfi -i color=c=black:s=256x256:d=1:r=30`
|
||||||
|
+ ` -c:v ${encoder} -f null /dev/null`;
|
||||||
|
exec(command, (
|
||||||
error,
|
error,
|
||||||
// stdout,
|
// stdout,
|
||||||
// stderr
|
// stderr,
|
||||||
) => {
|
) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
|
|
@ -187,7 +209,66 @@ const hasEncoder = async ({
|
||||||
return isEnabled;
|
return isEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// credit to UNCode101 for this
|
||||||
|
const getBestNvencDevice = async ({
|
||||||
|
response,
|
||||||
|
inputs,
|
||||||
|
nvencDevice,
|
||||||
|
}) => {
|
||||||
|
const { execSync } = require('child_process');
|
||||||
|
let gpu_num = -1;
|
||||||
|
let gpu_util = 100000;
|
||||||
|
let result_util = 0;
|
||||||
|
let gpu_count = -1;
|
||||||
|
let gpu_names = '';
|
||||||
|
const gpus_to_exclude = 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();
|
||||||
|
gpu_names = 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_names[0].includes('nvidia-smi')) {
|
||||||
|
gpu_count = gpu_names.length;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
response.infoLog += 'Error in reading nvidia-smi output! \n';
|
||||||
|
// response.infoLog += error.message;
|
||||||
|
}
|
||||||
|
if (gpu_count > 0) {
|
||||||
|
for (let gpui = 0; gpui < gpu_count; gpui++) {
|
||||||
|
// Check if GPU # is in GPUs to exclude
|
||||||
|
if (gpus_to_exclude.includes(gpui)) {
|
||||||
|
response.infoLog += `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") {
|
||||||
|
response.infoLog += `GPU ${gpui} : Utilization ${result_util}%\n`;
|
||||||
|
if (result_util < gpu_util) {
|
||||||
|
gpu_num = gpui;
|
||||||
|
gpu_util = result_util;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
response.infoLog += `Error in reading GPU ${gpui} Utilization\nError: ${error}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gpu_num >= 0) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
nvencDevice.inputArgs = `-hwaccel_device ${gpu_num}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nvencDevice;
|
||||||
|
};
|
||||||
|
|
||||||
const getEncoder = async ({
|
const getEncoder = async ({
|
||||||
|
response,
|
||||||
inputs,
|
inputs,
|
||||||
otherArguments,
|
otherArguments,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
@ -208,6 +289,11 @@ const getEncoder = async ({
|
||||||
encoder: 'hevc_qsv',
|
encoder: 'hevc_qsv',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
encoder: 'hevc_vaapi',
|
||||||
|
inputArgs: '-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi',
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
encoder: 'hevc_videotoolbox',
|
encoder: 'hevc_videotoolbox',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|
@ -239,23 +325,40 @@ const getEncoder = async ({
|
||||||
gpuEncoder.enabled = await hasEncoder({
|
gpuEncoder.enabled = await hasEncoder({
|
||||||
ffmpegPath: otherArguments.ffmpegPath,
|
ffmpegPath: otherArguments.ffmpegPath,
|
||||||
encoder: gpuEncoder.encoder,
|
encoder: gpuEncoder.encoder,
|
||||||
|
inputArgs: gpuEncoder.inputArgs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const enabledDevices = gpuEncoders.filter((device) => device.enabled === true);
|
const enabledDevices = gpuEncoders.filter((device) => device.enabled === true);
|
||||||
|
|
||||||
if (enabledDevices.length > 0) {
|
if (enabledDevices.length > 0) {
|
||||||
return enabledDevices[0].encoder;
|
if (enabledDevices[0].encoder.includes('nvenc')) {
|
||||||
|
return getBestNvencDevice({
|
||||||
|
response,
|
||||||
|
inputs,
|
||||||
|
nvencDevice: enabledDevices[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return enabledDevices[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputs.target_codec === 'hevc') {
|
if (inputs.target_codec === 'hevc') {
|
||||||
return 'libx265';
|
return {
|
||||||
|
encoder: 'libx265',
|
||||||
|
inputArgs: '',
|
||||||
|
};
|
||||||
} if (inputs.target_codec === 'h264') {
|
} if (inputs.target_codec === 'h264') {
|
||||||
return 'libx264';
|
return {
|
||||||
|
encoder: 'libx264',
|
||||||
|
inputArgs: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return {
|
||||||
|
encoder: '',
|
||||||
|
inputArgs: '',
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
|
@ -272,7 +375,8 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => {
|
||||||
infoLog: '',
|
infoLog: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const encoder = await getEncoder({
|
const encoderProperties = await getEncoder({
|
||||||
|
response,
|
||||||
inputs,
|
inputs,
|
||||||
otherArguments,
|
otherArguments,
|
||||||
});
|
});
|
||||||
|
|
@ -392,7 +496,7 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if b frame variable is true.
|
// Check if b frame variable is true.
|
||||||
if (bframeSupport.includes(encoder) && inputs.bframes_enabled === true) {
|
if (bframeSupport.includes(encoderProperties.encoder) && inputs.bframes_enabled === true) {
|
||||||
// If set to true then add b frames argument
|
// If set to true then add b frames argument
|
||||||
extraArguments += `-bf ${inputs.bframes_value} `;
|
extraArguments += `-bf ${inputs.bframes_value} `;
|
||||||
}
|
}
|
||||||
|
|
@ -462,7 +566,7 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => {
|
||||||
response.infoLog += `Minimum = ${minimumBitrate} \n`;
|
response.infoLog += `Minimum = ${minimumBitrate} \n`;
|
||||||
response.infoLog += `Maximum = ${maximumBitrate} \n`;
|
response.infoLog += `Maximum = ${maximumBitrate} \n`;
|
||||||
|
|
||||||
if (encoder.includes('nvenc')) {
|
if (encoderProperties.encoder.includes('nvenc')) {
|
||||||
if (file.video_codec_name === 'h263') {
|
if (file.video_codec_name === 'h263') {
|
||||||
response.preset = '-c:v h263_cuvid';
|
response.preset = '-c:v h263_cuvid';
|
||||||
} else if (file.video_codec_name === 'h264' && CPU10 === false) {
|
} else if (file.video_codec_name === 'h264' && CPU10 === false) {
|
||||||
|
|
@ -482,7 +586,8 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.preset += `${genpts}<io> -map 0 -c copy -c:v ${encoder} -cq:v 19 ${bitrateSettings} `
|
response.preset += ` ${encoderProperties.inputArgs ? encoderProperties.inputArgs : ''} ${genpts}<io>`
|
||||||
|
+ ` -map 0 -c copy -c:v ${encoderProperties.encoder} -cq:v 19 ${bitrateSettings} `
|
||||||
+ `-spatial_aq:v 1 -rc-lookahead:v 32 -max_muxing_queue_size 9999 ${extraArguments}`;
|
+ `-spatial_aq:v 1 -rc-lookahead:v 32 -max_muxing_queue_size 9999 ${extraArguments}`;
|
||||||
response.processFile = true;
|
response.processFile = true;
|
||||||
response.infoLog += `File is not in ${inputs.target_codec}. Transcoding. \n`;
|
response.infoLog += `File is not in ${inputs.target_codec}. Transcoding. \n`;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue