|
|
|
@ -26,14 +26,10 @@ const details = () => ({
|
|
|
|
Settings are dependant on file bitrate working by the logic that H265 can support the same amount of data at half
|
|
|
|
Settings are dependant on file bitrate working by the logic that H265 can support the same amount of data at half
|
|
|
|
the bitrate of H264. This plugin will skip files already in HEVC, AV1 & VP9 unless "reconvert_hevc" is marked as
|
|
|
|
the bitrate of H264. This plugin will skip files already in HEVC, AV1 & VP9 unless "reconvert_hevc" is marked as
|
|
|
|
true. If it is then these will be reconverted again if they exceed the bitrate specified in "hevc_max_bitrate".
|
|
|
|
true. If it is then these will be reconverted again if they exceed the bitrate specified in "hevc_max_bitrate".
|
|
|
|
This plugin will also attempt to use mkvpropedit to generate accurate bitrate metadata in MKV files.
|
|
|
|
This plugin relies on understanding the accurate video bitrate of your files. It's highly recommended to remux
|
|
|
|
It's not required to enable mkvpropedit but highly recommended to ensure accurate bitrates are used when
|
|
|
|
into MKV & enable "Run mkvpropedit on files before running plugins" under Tdarr>Options.`,
|
|
|
|
encoding your media.
|
|
|
|
Version: '1.3',
|
|
|
|
\n\n==NOTE== Intel ARC cards are reportedly working successfully with this plugin, however please bare in mind that
|
|
|
|
Tags: 'pre-processing,ffmpeg,video only,qsv,h265,hevc,configurable',
|
|
|
|
I've not officially tested with them yet and your results might vary. Don't just assume it will work and if it does
|
|
|
|
|
|
|
|
ensure you properly test your files & workflow!`,
|
|
|
|
|
|
|
|
Version: '1.2',
|
|
|
|
|
|
|
|
Tags: 'pre-processing,ffmpeg,video only,qsv,h265,hevc,mkvpropedit,configurable',
|
|
|
|
|
|
|
|
Inputs: [
|
|
|
|
Inputs: [
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: 'container',
|
|
|
|
name: 'container',
|
|
|
|
@ -161,7 +157,6 @@ const details = () => ({
|
|
|
|
https://ffmpeg.org/ffmpeg-codecs.html#toc-HEVC-Options-1
|
|
|
|
https://ffmpeg.org/ffmpeg-codecs.html#toc-HEVC-Options-1
|
|
|
|
\\n
|
|
|
|
\\n
|
|
|
|
==WARNING== \\n
|
|
|
|
==WARNING== \\n
|
|
|
|
Just because a cmd is mentioned doesn't mean your installed version of ffmpeg supports it...
|
|
|
|
|
|
|
|
Be certain to verify the cmds work before adding to your workflow. \\n
|
|
|
|
Be certain to verify the cmds work before adding to your workflow. \\n
|
|
|
|
Check Tdarr Help Tab. Enter ffmpeg cmd - "-h encoder=hevc_qsv". This will give a list of supported commands. \\n
|
|
|
|
Check Tdarr Help Tab. Enter ffmpeg cmd - "-h encoder=hevc_qsv". This will give a list of supported commands. \\n
|
|
|
|
MAC SPECIFIC - This option is ignored on Mac because videotoolbox is used rather than qsv.
|
|
|
|
MAC SPECIFIC - This option is ignored on Mac because videotoolbox is used rather than qsv.
|
|
|
|
@ -170,6 +165,7 @@ const details = () => ({
|
|
|
|
\\nDefault is empty but the first example below has a suggested value. If unsure just leave empty.
|
|
|
|
\\nDefault is empty but the first example below has a suggested value. If unsure just leave empty.
|
|
|
|
\\nEnsure to only use cmds valid to encoding QSV as the script handles other ffmpeg cmds relating to
|
|
|
|
\\nEnsure to only use cmds valid to encoding QSV as the script handles other ffmpeg cmds relating to
|
|
|
|
bitrate etc. Anything else entered here might be supported but could cause undesired results.
|
|
|
|
bitrate etc. Anything else entered here might be supported but could cause undesired results.
|
|
|
|
|
|
|
|
\\nIf you are using a "-vf" cmd, please put it at the end to avoid issues!
|
|
|
|
\\nExample:\\n
|
|
|
|
\\nExample:\\n
|
|
|
|
-look_ahead 1 -look_ahead_depth 100 -extbrc 1 -rdo 1 -mbbrc 1 -b_strategy 1 -adaptive_i 1 -adaptive_b 1
|
|
|
|
-look_ahead 1 -look_ahead_depth 100 -extbrc 1 -rdo 1 -mbbrc 1 -b_strategy 1 -adaptive_i 1 -adaptive_b 1
|
|
|
|
\\n Above enables look ahead, extended bitrate control, b-frames, etc.\\n
|
|
|
|
\\n Above enables look ahead, extended bitrate control, b-frames, etc.\\n
|
|
|
|
@ -330,15 +326,13 @@ let bitrateSettings = '';
|
|
|
|
let inflatedCutoff = 0;
|
|
|
|
let inflatedCutoff = 0;
|
|
|
|
let main10 = false;
|
|
|
|
let main10 = false;
|
|
|
|
let high10 = false;
|
|
|
|
let high10 = false;
|
|
|
|
|
|
|
|
let swDecode = false;
|
|
|
|
let videoBR = 0;
|
|
|
|
let videoBR = 0;
|
|
|
|
|
|
|
|
|
|
|
|
// Finds the first video stream and get video bitrate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
const lib = require('../methods/lib')();
|
|
|
|
const lib = require('../methods/lib')();
|
|
|
|
const os = require('os');
|
|
|
|
const os = require('os');
|
|
|
|
const proc = require('child_process');
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
|
|
|
|
inputs = lib.loadDefaultValues(inputs, details);
|
|
|
|
inputs = lib.loadDefaultValues(inputs, details);
|
|
|
|
const response = {
|
|
|
|
const response = {
|
|
|
|
@ -357,119 +351,64 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MKVPROPEDIT - Refresh video stats
|
|
|
|
|
|
|
|
const intStatsDays = 7; // Use 1 week threshold for new stats
|
|
|
|
|
|
|
|
let statsUptoDate = false;
|
|
|
|
|
|
|
|
const currentFileName = file._id;
|
|
|
|
|
|
|
|
let statsError = false;
|
|
|
|
|
|
|
|
let metadataEncode = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only process MKV files
|
|
|
|
|
|
|
|
if (file.container === 'mkv') {
|
|
|
|
|
|
|
|
let datStats = Date.parse(new Date(70, 1).toISOString()); // Placeholder date
|
|
|
|
|
|
|
|
metadataEncode = `-map_metadata:g -1 -metadata JBDONEDATE=${datStats}`;
|
|
|
|
|
|
|
|
if (file.mediaInfo.track[0].extra !== undefined && file.mediaInfo.track[0].extra.JBDONEDATE !== undefined) {
|
|
|
|
|
|
|
|
datStats = Date.parse(file.mediaInfo.track[0].extra.JBDONEDATE);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
|
|
file.mediaInfo.track[0].extra !== undefined
|
|
|
|
|
|
|
|
&& file.ffProbeData.streams[0].tags['_STATISTICS_WRITING_DATE_UTC-eng'] !== undefined
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
// Set stats date to match info inside file
|
|
|
|
|
|
|
|
datStats = Date.parse(`${file.ffProbeData.streams[0].tags['_STATISTICS_WRITING_DATE_UTC-eng']} GMT`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
// Catch error - Ignore & carry on - If check can bomb out if the tag doesn't exist...
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Threshold for stats date
|
|
|
|
|
|
|
|
const statsThres = Date.parse(new Date(new Date().setDate(new Date().getDate() - intStatsDays)).toISOString());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Strings for easy to read dates in info log
|
|
|
|
|
|
|
|
let statsThresString = new Date(statsThres);
|
|
|
|
|
|
|
|
statsThresString = statsThresString.toUTCString();
|
|
|
|
|
|
|
|
let datStatsString = new Date(datStats);
|
|
|
|
|
|
|
|
datStatsString = datStatsString.toUTCString();
|
|
|
|
|
|
|
|
response.infoLog += `Checking file stats - If stats are older than ${intStatsDays} days we'll grab new stats.\n
|
|
|
|
|
|
|
|
Stats threshold: ${statsThresString}\n
|
|
|
|
|
|
|
|
Current stats date: ${datStatsString}\n`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Are the stats out of date?
|
|
|
|
|
|
|
|
if (datStats >= statsThres) {
|
|
|
|
|
|
|
|
statsUptoDate = true;
|
|
|
|
|
|
|
|
response.infoLog += '☑ File stats are upto date! - Continuing...\n';
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
response.infoLog += '☒ File stats are out of date! - Will attempt to use mkvpropedit to refresh stats\n';
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (otherArguments.mkvpropeditPath !== '') { // Try to use mkvpropedit path if it is set
|
|
|
|
|
|
|
|
proc.execSync(`"${otherArguments.mkvpropeditPath}" --add-track-statistics-tags "${currentFileName}"`);
|
|
|
|
|
|
|
|
} else { // Otherwise just use standard mkvpropedit cmd
|
|
|
|
|
|
|
|
proc.execSync(`mkvpropedit --add-track-statistics-tags "${currentFileName}"`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
response.infoLog += '☒ Error updating file stats - Possible mkvpropedit failure or file issue - '
|
|
|
|
|
|
|
|
+ ' Ensure mkvpropedit is set correctly in the node settings & check the filename for unusual characters.\n'
|
|
|
|
|
|
|
|
+ ' Continuing but file stats will likely be inaccurate...\n';
|
|
|
|
|
|
|
|
statsError = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (statsError !== true) {
|
|
|
|
|
|
|
|
// File now updated with new stats
|
|
|
|
|
|
|
|
response.infoLog += 'Remuxing file to write in updated file stats! \n';
|
|
|
|
|
|
|
|
response.preset += `-fflags +genpts <io> -map 0 -c copy -max_muxing_queue_size 9999 -map_metadata:g -1
|
|
|
|
|
|
|
|
-metadata JBDONEDATE=${new Date().toISOString()}`;
|
|
|
|
|
|
|
|
response.processFile = true;
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
response.infoLog += 'Input file is not MKV so cannot use mkvpropedit to get new file stats. '
|
|
|
|
|
|
|
|
+ 'Continuing but file stats will likely be inaccurate...\n';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
|
|
|
|
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
|
|
|
|
const strstreamType = file.ffProbeData.streams[i].codec_type.toLowerCase();
|
|
|
|
const strstreamType = file.ffProbeData.streams[i].codec_type.toLowerCase();
|
|
|
|
videoIdx = -1;
|
|
|
|
|
|
|
|
// Check if stream is a video.
|
|
|
|
// Check if stream is a video.
|
|
|
|
if (videoIdx === -1 && strstreamType === 'video') {
|
|
|
|
if (strstreamType === 'video') {
|
|
|
|
videoIdx = i;
|
|
|
|
if (file.ffProbeData.streams[i].codec_name !== 'mjpeg'
|
|
|
|
|
|
|
|
&& file.ffProbeData.streams[i].codec_name !== 'png') {
|
|
|
|
|
|
|
|
if (videoBR <= 0) { // Process if videoBR is not yet valid
|
|
|
|
|
|
|
|
try { // Try checking file stats using Mediainfo first, then ffprobe.
|
|
|
|
videoBR = Number(file.mediaInfo.track[i + 1].BitRate) / 1000;
|
|
|
|
videoBR = Number(file.mediaInfo.track[i + 1].BitRate) / 1000;
|
|
|
|
|
|
|
|
if (videoBR <= 0 || Number.isNaN(videoBR)) {
|
|
|
|
// If MediaInfo fails somehow fallback to ffprobe - Try two types of tags that might exist
|
|
|
|
|
|
|
|
if (videoBR <= 0) {
|
|
|
|
|
|
|
|
if (Number(file.ffProbeData.streams[i].tags.BPS) > 0) {
|
|
|
|
if (Number(file.ffProbeData.streams[i].tags.BPS) > 0) {
|
|
|
|
videoBR = file.ffProbeData.streams[i].tags.BPS / 1000;
|
|
|
|
videoBR = file.ffProbeData.streams[i].tags.BPS / 1000;
|
|
|
|
} else {
|
|
|
|
} else if (Number(file.ffProbeData.streams[i].tags.BPS['-eng']) > 0) {
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (Number(file.ffProbeData.streams[i].tags.BPS['-eng']) > 0) {
|
|
|
|
|
|
|
|
videoBR = file.ffProbeData.streams[i].tags.BPS['-eng'] / 1000;
|
|
|
|
videoBR = file.ffProbeData.streams[i].tags.BPS['-eng'] / 1000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
|
|
|
|
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
|
|
|
|
|
|
|
|
videoBR = 0; // Set videoBR to 0 for safety
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (duration <= 0) { // Process if duration is not yet valid
|
|
|
|
|
|
|
|
try { // Attempt to get duration info
|
|
|
|
|
|
|
|
if (Number.isNaN(file.meta.Duration)) {
|
|
|
|
|
|
|
|
duration = file.meta.Duration;
|
|
|
|
|
|
|
|
duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60;
|
|
|
|
|
|
|
|
} else if (file.meta.Duration > 0) {
|
|
|
|
|
|
|
|
duration = file.meta.Duration / 60;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (duration <= 0 || Number.isNaN(duration)) {
|
|
|
|
|
|
|
|
if (typeof file.mediaInfo.track[i + 1].Duration !== 'undefined') {
|
|
|
|
|
|
|
|
duration = file.mediaInfo.track[i + 1].Duration;
|
|
|
|
|
|
|
|
duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60;
|
|
|
|
|
|
|
|
} else if (typeof file.ffProbeData.streams[i].tags.DURATION !== 'undefined') {
|
|
|
|
|
|
|
|
duration = file.ffProbeData.streams[i].tags.DURATION;
|
|
|
|
|
|
|
|
duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
// Check if duration info is filled, if so convert time format to minutes.
|
|
|
|
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
|
|
|
|
// If not filled then get duration of video stream and do the same.
|
|
|
|
duration = 0; // Set duration to 0 for safety
|
|
|
|
if (typeof file.meta.Duration !== 'undefined') {
|
|
|
|
}
|
|
|
|
duration = file.meta.Duration;
|
|
|
|
}
|
|
|
|
// Get seconds by using a Date & then convert to minutes
|
|
|
|
if ((videoBR <= 0 || Number.isNaN(videoBR)) || (duration <= 0 || Number.isNaN(duration))) {
|
|
|
|
duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60;
|
|
|
|
// videoBR or duration not yet valid so Loop
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
duration = file.ffProbeData.streams[videoIdx].tags.DURATION;
|
|
|
|
break;// Exit loop if both valid
|
|
|
|
duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (Number.isNaN(videoBR) || videoBR <= 0) {
|
|
|
|
if (Number.isNaN(videoBR) || videoBR <= 0) {
|
|
|
|
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
|
|
|
|
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
|
|
|
|
currentBitrate = Math.round(file.file_size / (duration * 0.0075));
|
|
|
|
currentBitrate = Math.round(file.file_size / (duration * 0.0075));
|
|
|
|
response.infoLog += '==WARNING== Failed to get an accurate video bitrate, ';
|
|
|
|
response.infoLog += '==WARNING== Failed to get an accurate video bitrate, '
|
|
|
|
response.infoLog += `falling back to old method to get OVERALL file bitrate of ${currentBitrate}kbps. `;
|
|
|
|
+ `falling back to old method to get OVERALL file bitrate of ${currentBitrate}kbps. `
|
|
|
|
response.infoLog += 'Bitrate calculations for video encode will likely be inaccurate... \n';
|
|
|
|
+ 'Bitrate calculations for video encode will likely be inaccurate...\n';
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
currentBitrate = Math.round(videoBR);
|
|
|
|
currentBitrate = Math.round(videoBR);
|
|
|
|
response.infoLog += `☑ It looks like the current video bitrate is ${currentBitrate}kbps.\n`;
|
|
|
|
response.infoLog += `☑ It looks like the current video bitrate is ${currentBitrate}kbps.\n`;
|
|
|
|
@ -486,8 +425,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// If targetBitrate or currentBitrate comes out as 0 then something
|
|
|
|
// If targetBitrate or currentBitrate comes out as 0 then something
|
|
|
|
// has gone wrong and bitrates could not be calculated.
|
|
|
|
// has gone wrong and bitrates could not be calculated.
|
|
|
|
// Cancel plugin completely.
|
|
|
|
// Cancel plugin completely.
|
|
|
|
if (targetBitrate <= 0 || currentBitrate <= 0) {
|
|
|
|
if (targetBitrate <= 0 || currentBitrate <= 0 || overallBitRate <= 0) {
|
|
|
|
response.infoLog += '☒ Target bitrate could not be calculated. Skipping this plugin. \n';
|
|
|
|
response.infoLog += '☒ Target bitrates could not be calculated. Skipping this plugin.\n';
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -495,17 +434,16 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// has gone wrong as that is not what we want.
|
|
|
|
// has gone wrong as that is not what we want.
|
|
|
|
// Cancel plugin completely.
|
|
|
|
// Cancel plugin completely.
|
|
|
|
if (targetBitrate >= currentBitrate) {
|
|
|
|
if (targetBitrate >= currentBitrate) {
|
|
|
|
response.infoLog += `☒ Target bitrate has been calculated as ${targetBitrate}kbps. This is equal or greater `;
|
|
|
|
response.infoLog += `☒ Target bitrate has been calculated as ${targetBitrate}kbps. This is equal or greater than `
|
|
|
|
response.infoLog += "than the current bitrate... Something has gone wrong and this shouldn't happen! "
|
|
|
|
+ "the current bitrate... Something has gone wrong and this shouldn't happen! Skipping this plugin.\n";
|
|
|
|
+ 'Skipping this plugin. \n';
|
|
|
|
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure that bitrate_cutoff is set if reconvert_hevc is true since we need some protection against a loop
|
|
|
|
// Ensure that bitrate_cutoff is set if reconvert_hevc is true since we need some protection against a loop
|
|
|
|
// Cancel the plugin
|
|
|
|
// Cancel the plugin
|
|
|
|
if (inputs.reconvert_hevc === true && inputs.bitrate_cutoff <= 0 && inputs.hevc_max_bitrate <= 0) {
|
|
|
|
if (inputs.reconvert_hevc === true && inputs.bitrate_cutoff <= 0 && inputs.hevc_max_bitrate <= 0) {
|
|
|
|
response.infoLog += `Reconvert HEVC is ${inputs.reconvert_hevc}, however there is no bitrate cutoff `;
|
|
|
|
response.infoLog += `Reconvert HEVC is ${inputs.reconvert_hevc}, however there is no bitrate cutoff or HEVC `
|
|
|
|
response.infoLog += 'or HEVC specific cutoff set so we have no way to know when to stop processing this file. \n'
|
|
|
|
+ 'specific cutoff set so we have no way to know when to stop processing this file.\n'
|
|
|
|
+ 'Either set reconvert_HEVC to false or set a bitrate cutoff and set a hevc_max_bitrate cutoff.\n'
|
|
|
|
+ 'Either set reconvert_HEVC to false or set a bitrate cutoff and set a hevc_max_bitrate cutoff.\n'
|
|
|
|
+ '☒ Skipping this plugin.\n';
|
|
|
|
+ '☒ Skipping this plugin.\n';
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
@ -531,8 +469,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// Checks if targetBitrate is above inputs.max_average_bitrate.
|
|
|
|
// Checks if targetBitrate is above inputs.max_average_bitrate.
|
|
|
|
// If so then clamp target bitrate
|
|
|
|
// If so then clamp target bitrate
|
|
|
|
if (targetBitrate > inputs.max_average_bitrate) {
|
|
|
|
if (targetBitrate > inputs.max_average_bitrate) {
|
|
|
|
response.infoLog += 'Our target bitrate is above the max_average_bitrate ';
|
|
|
|
response.infoLog += 'Our target bitrate is above the max_average_bitrate so clamping at max of '
|
|
|
|
response.infoLog += `so clamping at max of ${inputs.max_average_bitrate}kbps. \n`;
|
|
|
|
+ `${inputs.max_average_bitrate}kbps.\n`;
|
|
|
|
targetBitrate = Math.round(inputs.max_average_bitrate);
|
|
|
|
targetBitrate = Math.round(inputs.max_average_bitrate);
|
|
|
|
minimumBitrate = Math.round(targetBitrate * 0.75);
|
|
|
|
minimumBitrate = Math.round(targetBitrate * 0.75);
|
|
|
|
maximumBitrate = Math.round(targetBitrate * 1.25);
|
|
|
|
maximumBitrate = Math.round(targetBitrate * 1.25);
|
|
|
|
@ -544,8 +482,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
if (inputs.min_average_bitrate > 0) {
|
|
|
|
if (inputs.min_average_bitrate > 0) {
|
|
|
|
// Exit the plugin is the cutoff is less than the min average bitrate. Most likely user error
|
|
|
|
// Exit the plugin is the cutoff is less than the min average bitrate. Most likely user error
|
|
|
|
if (inputs.bitrate_cutoff < inputs.min_average_bitrate) {
|
|
|
|
if (inputs.bitrate_cutoff < inputs.min_average_bitrate) {
|
|
|
|
response.infoLog += `☒ Bitrate cutoff ${inputs.bitrate_cutoff}k is less than the set minimum
|
|
|
|
response.infoLog += `☒ Bitrate cutoff ${inputs.bitrate_cutoff}k is less than the set minimum `
|
|
|
|
average bitrate set of ${inputs.min_average_bitrate}kbps. We don't want this. Cancelling plugin. \n`;
|
|
|
|
+ `average bitrate set of ${inputs.min_average_bitrate}kbps. We don't want this. Cancelling plugin.\n`;
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Checks if inputs.bitrate_cutoff is below inputs.min_average_bitrate.
|
|
|
|
// Checks if inputs.bitrate_cutoff is below inputs.min_average_bitrate.
|
|
|
|
@ -610,16 +548,29 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// Check if codec of stream is mjpeg/png, if so then remove this "video" stream.
|
|
|
|
// Check if codec of stream is mjpeg/png, if so then remove this "video" stream.
|
|
|
|
// mjpeg/png are usually embedded pictures that can cause havoc with plugins.
|
|
|
|
// mjpeg/png are usually embedded pictures that can cause havoc with plugins.
|
|
|
|
if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png') {
|
|
|
|
if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png') {
|
|
|
|
extraArguments += `-map -v:${videoIdx} `;
|
|
|
|
extraArguments += `-map -0:v:${videoIdx} `;
|
|
|
|
|
|
|
|
} else { // Ensure to only do further checks if video stream is valid for use
|
|
|
|
|
|
|
|
// Check for HDR in files. Attempt to use same color
|
|
|
|
|
|
|
|
if ((file.ffProbeData.streams[i].color_space === 'bt2020nc'
|
|
|
|
|
|
|
|
|| file.ffProbeData.streams[i].color_space === 'bt2020n')
|
|
|
|
|
|
|
|
&& (file.ffProbeData.streams[i].color_transfer === 'smpte2084'
|
|
|
|
|
|
|
|
|| file.ffProbeData.streams[i].color_transfer === 'arib-std-b67')
|
|
|
|
|
|
|
|
&& file.ffProbeData.streams[i].color_primaries === 'bt2020') {
|
|
|
|
|
|
|
|
response.infoLog += '==WARNING== This looks to be a HDR file. HDR is supported but '
|
|
|
|
|
|
|
|
+ 'correct encoding is not guaranteed.\n';
|
|
|
|
|
|
|
|
extraArguments += `-color_primaries ${file.ffProbeData.streams[i].color_primaries} `
|
|
|
|
|
|
|
|
+ `-color_trc ${file.ffProbeData.streams[i].color_transfer} `
|
|
|
|
|
|
|
|
+ `-colorspace ${file.ffProbeData.streams[i].color_space} `;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check for HDR in files. If so exit plugin. We assume HDR files have bt2020 color spaces. HDR can be complicated
|
|
|
|
// Check if codec of stream is HEVC, Vp9 or AV1
|
|
|
|
// and some aspects are still unsupported in ffmpeg I believe. Likely we don't want to re-encode anything HDR.
|
|
|
|
// AND check if file.container does NOT match inputs.container. If so remux file.
|
|
|
|
if (file.ffProbeData.streams[i].color_space === 'bt2020nc'
|
|
|
|
if ((file.ffProbeData.streams[i].codec_name === 'hevc'
|
|
|
|
&& file.ffProbeData.streams[i].color_transfer === 'smpte2084'
|
|
|
|
|| file.ffProbeData.streams[i].codec_name === 'vp9'
|
|
|
|
&& file.ffProbeData.streams[i].color_primaries === 'bt2020') {
|
|
|
|
|| file.ffProbeData.streams[i].codec_name === 'av1') && file.container !== inputs.container) {
|
|
|
|
response.infoLog += '☒ This looks to be a HDR file. HDR files are unfortunately '
|
|
|
|
response.infoLog += `☒ File is HEVC, VP9 or AV1 but is not in ${inputs.container} container. Remuxing.\n`;
|
|
|
|
+ 'not supported by this plugin. Exiting plugin. \n\n';
|
|
|
|
response.preset = `<io> -map 0 -c copy ${extraArguments}`;
|
|
|
|
|
|
|
|
response.processFile = true;
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -634,39 +585,22 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Check if codec of stream is HEVC, Vp9 or AV1
|
|
|
|
|
|
|
|
// AND check if file.container does NOT match inputs.container.
|
|
|
|
|
|
|
|
// If so remux file.
|
|
|
|
|
|
|
|
if ((file.ffProbeData.streams[i].codec_name === 'hevc' || file.ffProbeData.streams[i].codec_name === 'vp9'
|
|
|
|
|
|
|
|
|| file.ffProbeData.streams[i].codec_name === 'av1') && file.container !== inputs.container) {
|
|
|
|
|
|
|
|
response.infoLog += `☒ File is HEVC, VP9 or AV1 but is not in ${inputs.container} container. Remuxing. \n`;
|
|
|
|
|
|
|
|
response.preset = `<io> -map 0 -c copy ${extraArguments}`;
|
|
|
|
|
|
|
|
response.processFile = true;
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// New logic for reprocessing HEVC. Mainly done for my own use.
|
|
|
|
// New logic for reprocessing HEVC. Mainly done for my own use.
|
|
|
|
// We attempt to get accurate stats earlier - If we can't we fall back onto overall bitrate
|
|
|
|
// We attempt to get accurate stats earlier - If we can't we fall back onto overall bitrate
|
|
|
|
// which can be inaccurate. We may inflate the current bitrate check so we don't keep looping this logic.
|
|
|
|
// which can be inaccurate. We may inflate the current bitrate check so we don't keep looping this logic.
|
|
|
|
} else if (inputs.reconvert_hevc === true && (file.ffProbeData.streams[i].codec_name === 'hevc'
|
|
|
|
} else if (inputs.reconvert_hevc === true && (file.ffProbeData.streams[i].codec_name === 'hevc'
|
|
|
|
|| file.ffProbeData.streams[i].codec_name === 'vp9' || file.ffProbeData.streams[i].codec_name === 'av1')) {
|
|
|
|
|| file.ffProbeData.streams[i].codec_name === 'vp9' || file.ffProbeData.streams[i].codec_name === 'av1')) {
|
|
|
|
if (statsUptoDate !== true) {
|
|
|
|
|
|
|
|
currentBitrate = overallBitRate; // User overall bitrate if we don't have upto date stats
|
|
|
|
|
|
|
|
response.infoLog += `☒ Unable to get accurate stats for HEVC so falling back to Overall file Bitrate.
|
|
|
|
|
|
|
|
Remux to MKV to allow generation of accurate video bitrate statistics.
|
|
|
|
|
|
|
|
File overall bitrate is ${overallBitRate}kbps.\n`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputs.hevc_max_bitrate > 0) {
|
|
|
|
if (inputs.hevc_max_bitrate > 0) {
|
|
|
|
if (currentBitrate > inputs.hevc_max_bitrate) {
|
|
|
|
if (currentBitrate > inputs.hevc_max_bitrate) {
|
|
|
|
// If bitrate is higher then hevc_max_bitrate then need to re-encode
|
|
|
|
// If bitrate is higher then hevc_max_bitrate then need to re-encode
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1. `
|
|
|
|
+ `VP9 or AV1. Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps. \n`;
|
|
|
|
+ `Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps.\n`
|
|
|
|
response.infoLog += '☒ The file is still above this new cutoff! Reconverting. \n';
|
|
|
|
+ '☒ The file is still above this new cutoff! Reconverting.\n';
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// Otherwise we're now below the hevc cutoff and we can exit
|
|
|
|
// Otherwise we're now below the hevc cutoff and we can exit
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1. `
|
|
|
|
+ `VP9 or AV1. Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps. \n`;
|
|
|
|
+ `Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps.\n`
|
|
|
|
response.infoLog += '☑ The file is NOT above this new cutoff. Exiting plugin. \n';
|
|
|
|
+ '☑ The file is NOT above this new cutoff. Exiting plugin.\n';
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -674,35 +608,33 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// looping this plugin. For maximum safety we simply multiply the cutoff by 2.
|
|
|
|
// looping this plugin. For maximum safety we simply multiply the cutoff by 2.
|
|
|
|
} else if (currentBitrate > (inputs.bitrate_cutoff * 2)) {
|
|
|
|
} else if (currentBitrate > (inputs.bitrate_cutoff * 2)) {
|
|
|
|
inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2);
|
|
|
|
inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2);
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `;
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1. `
|
|
|
|
response.infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, ';
|
|
|
|
+ `Will use Overall file Bitrate for HEVC files as safety, bitrate is ${overallBitRate}kbps.\n`
|
|
|
|
response.infoLog += `bitrate is ${overallBitRate}kbps. \n`;
|
|
|
|
+ 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety!\n'
|
|
|
|
response.infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n';
|
|
|
|
+ `Cutoff now temporarily ${inflatedCutoff}kbps.\n`
|
|
|
|
response.infoLog += `Cutoff now temporarily ${inflatedCutoff}kbps. \n`;
|
|
|
|
+ '☒ The file is still above this new cutoff! Reconverting.\n';
|
|
|
|
response.infoLog += '☒ The file is still above this new cutoff! Reconverting. \n';
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// File is below cutoff so we can exit
|
|
|
|
// File is below cutoff so we can exit
|
|
|
|
inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2);
|
|
|
|
inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2);
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `;
|
|
|
|
response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1. `
|
|
|
|
response.infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, ';
|
|
|
|
+ `Will use Overall file Bitrate for HEVC files as safety, bitrate is ${overallBitRate}kbps.\n`
|
|
|
|
response.infoLog += `bitrate is ${overallBitRate}kbps. \n`;
|
|
|
|
+ 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety!\n'
|
|
|
|
response.infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n';
|
|
|
|
+ `Cutoff now temporarily ${inflatedCutoff}kbps.\n`
|
|
|
|
response.infoLog += `Cutoff now temporarily ${inflatedCutoff}kbps. \n`;
|
|
|
|
+ '☑The file is NOT above this new cutoff. Exiting plugin.\n';
|
|
|
|
response.infoLog += '☑The file is NOT above this new cutoff. Exiting plugin. \n';
|
|
|
|
|
|
|
|
return response;
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// On testing I've found files in the High10 profile don't play nice with hw decoding so mark these
|
|
|
|
// Files in the High10 profile are not supported for HW Decode
|
|
|
|
if (file.ffProbeData.streams[i].profile === 'High 10') {
|
|
|
|
if (file.ffProbeData.streams[i].profile === 'High 10') {
|
|
|
|
high10 = true;
|
|
|
|
high10 = true;
|
|
|
|
response.infoLog += 'Input file is 10bit using High10. Disabling hardware decoding to avoid problems. \n';
|
|
|
|
main10 = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If files are 10 bit or the enable_10bit setting is used mark to enable Main10.
|
|
|
|
// If files are 10 bit or the enable_10bit setting is used mark to enable Main10.
|
|
|
|
if (file.ffProbeData.streams[i].profile === 'Main 10' || file.ffProbeData.streams[i].bits_per_raw_sample === '10'
|
|
|
|
} else if (file.ffProbeData.streams[i].profile === 'Main 10'
|
|
|
|
|| inputs.enable_10bit === true) {
|
|
|
|
|| file.ffProbeData.streams[i].bits_per_raw_sample === '10' || inputs.enable_10bit === true) {
|
|
|
|
main10 = true;
|
|
|
|
main10 = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Increment video index. Needed to keep track of video id in case there is more than one video track.
|
|
|
|
// Increment video index. Needed to keep track of video id in case there is more than one video track.
|
|
|
|
// (i.e png or mjpeg which we would remove at the start of the loop)
|
|
|
|
// (i.e png or mjpeg which we would remove at the start of the loop)
|
|
|
|
@ -710,78 +642,145 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Specify the output format
|
|
|
|
|
|
|
|
switch (inputs.container) {
|
|
|
|
|
|
|
|
case 'mkv':
|
|
|
|
|
|
|
|
extraArguments += '-f matroska ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mp4':
|
|
|
|
|
|
|
|
extraArguments += '-f mp4 ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Some video codecs don't support HW decode so mark these
|
|
|
|
|
|
|
|
// VC1 & VP8 are no longer supported on new HW, add cases here if your HW does support
|
|
|
|
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
|
|
|
|
case 'mpeg2':
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h264':
|
|
|
|
|
|
|
|
if (high10 === true) {
|
|
|
|
|
|
|
|
swDecode = true;
|
|
|
|
|
|
|
|
response.infoLog += 'Input file is h264 High10. Hardware Decode not supported.\n';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mjpeg':
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hevc':
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'vp9':// Should be supported by 8th Gen +
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'av1':// Should be supported by 11th gen +
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
swDecode = true;
|
|
|
|
|
|
|
|
response.infoLog += `Input file is ${file.video_codec_name}. Hardware Decode not supported.\n`;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Are we encoding to 10 bit? If so enable correct profile & pixel format.
|
|
|
|
// Are we encoding to 10 bit? If so enable correct profile & pixel format.
|
|
|
|
if (high10 === true) { // This is used if we have High10 files. SW decode and use standard -pix_fmt p010le
|
|
|
|
if (os.platform() !== 'darwin') {
|
|
|
|
|
|
|
|
if (swDecode === true && main10 === true) {
|
|
|
|
|
|
|
|
// This is used if we have High10 or Main10 is enabled & odd format files.
|
|
|
|
|
|
|
|
// SW decode and use standard -pix_fmt p010le
|
|
|
|
extraArguments += '-profile:v main10 -pix_fmt p010le ';
|
|
|
|
extraArguments += '-profile:v main10 -pix_fmt p010le ';
|
|
|
|
response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format\n';
|
|
|
|
response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format\n';
|
|
|
|
} else if (main10 === true) { // Pixel formate method when using HW decode
|
|
|
|
} else if (main10 === true) { // Pixel formate method when using HW decode
|
|
|
|
|
|
|
|
if (inputs.extra_qsv_options.search('-vf scale_qsv') >= 0) {
|
|
|
|
|
|
|
|
extraArguments += '-profile:v main10';
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',format=p010le'; // Only add on the pixel format to existing scale_qsv cmd
|
|
|
|
|
|
|
|
} else {
|
|
|
|
extraArguments += '-profile:v main10 -vf scale_qsv=format=p010le';
|
|
|
|
extraArguments += '-profile:v main10 -vf scale_qsv=format=p010le';
|
|
|
|
|
|
|
|
}
|
|
|
|
response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format\n';
|
|
|
|
response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Mac - Video toolbox profile & pixel format
|
|
|
|
|
|
|
|
extraArguments += '-profile:v 2 -pix_fmt yuv420p10le ';
|
|
|
|
|
|
|
|
response.infoLog += '10 bit encode enabled. Setting VideoToolBox Profile v2 & 10 bit pixel format\n';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Set bitrateSettings variable using bitrate information calculated earlier.
|
|
|
|
// Set bitrateSettings variable using bitrate information calculated earlier.
|
|
|
|
bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k `
|
|
|
|
bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k `
|
|
|
|
+ `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`;
|
|
|
|
+ `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`;
|
|
|
|
// Print to infoLog information around file & bitrate settings.
|
|
|
|
// Print to infoLog information around file & bitrate settings.
|
|
|
|
response.infoLog += `Container for output selected as ${inputs.container}. \n`;
|
|
|
|
response.infoLog += `Container for output selected as ${inputs.container}.\n`
|
|
|
|
response.infoLog += 'Encode variable bitrate settings: \n';
|
|
|
|
+ 'Encode variable bitrate settings:\n'
|
|
|
|
response.infoLog += `Target = ${targetBitrate}k \n`;
|
|
|
|
+ `Target = ${targetBitrate}k\n`
|
|
|
|
response.infoLog += `Minimum = ${minimumBitrate}k \n`;
|
|
|
|
+ `Minimum = ${minimumBitrate}k\n`
|
|
|
|
response.infoLog += `Maximum = ${maximumBitrate}k \n`;
|
|
|
|
+ `Maximum = ${maximumBitrate}k\n`;
|
|
|
|
|
|
|
|
|
|
|
|
// START PRESET
|
|
|
|
// START PRESET
|
|
|
|
// -fflags +genpts should regenerate timestamps if they end up missing...
|
|
|
|
// -fflags +genpts should regenerate timestamps if they end up missing...
|
|
|
|
response.preset = '-fflags +genpts ';
|
|
|
|
response.preset = '-fflags +genpts ';
|
|
|
|
|
|
|
|
|
|
|
|
// HW ACCEL FLAGS - I think these are good practice but are they necessary?
|
|
|
|
// HW ACCEL FLAGS
|
|
|
|
// Account for different OS
|
|
|
|
// Account for different OS
|
|
|
|
if (high10 === false) {
|
|
|
|
if (swDecode !== true) {
|
|
|
|
// Seems incoming High10 files don't play nice decoding so use software decode
|
|
|
|
// Only enable hw decode for accepted formats
|
|
|
|
switch (os.platform()) {
|
|
|
|
switch (os.platform()) {
|
|
|
|
case 'darwin': // Mac OS - Enable videotoolbox instead of QSV
|
|
|
|
case 'darwin': // Mac OS - Enable videotoolbox instead of QSV
|
|
|
|
response.preset += '-hwaccel videotoolbox';
|
|
|
|
response.preset += '-hwaccel videotoolbox';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'linux': // Linux - Full device, should fix child_device_type warnings
|
|
|
|
case 'linux': // Linux - Full device, should fix child_device_type warnings
|
|
|
|
response.preset += `-hwaccel qsv -hwaccel_output_format qsv
|
|
|
|
response.preset += '-hwaccel qsv -hwaccel_output_format qsv '
|
|
|
|
-init_hw_device qsv:hw_any,child_device_type=vaapi `;
|
|
|
|
+ '-init_hw_device qsv:hw_any,child_device_type=vaapi ';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'win32': // Windows - Full device, should fix child_device_type warnings
|
|
|
|
case 'win32': // Windows - Full device, should fix child_device_type warnings
|
|
|
|
response.preset += `-hwaccel qsv -hwaccel_output_format qsv
|
|
|
|
response.preset += '-hwaccel qsv -hwaccel_output_format qsv '
|
|
|
|
-init_hw_device qsv:hw_any,child_device_type=d3d11va `;
|
|
|
|
+ '-init_hw_device qsv:hw,child_device_type=d3d11va ';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
response.preset += '-hwaccel qsv -hwaccel_output_format qsv -init_hw_device qsv:hw_any ';
|
|
|
|
response.preset += '-hwaccel qsv -hwaccel_output_format qsv -init_hw_device qsv:hw_any ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
switch (os.platform()) {
|
|
|
|
|
|
|
|
case 'darwin': // Mac OS - Enable videotoolbox instead of QSV
|
|
|
|
|
|
|
|
response.preset += '-hwaccel videotoolbox';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'linux': // Linux - Full device, should fix child_device_type warnings
|
|
|
|
|
|
|
|
response.preset += '-hwaccel_output_format qsv '
|
|
|
|
|
|
|
|
+ '-init_hw_device qsv:hw_any,child_device_type=vaapi ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'win32': // Windows - Full device, should fix child_device_type warnings
|
|
|
|
|
|
|
|
response.preset += '-hwaccel_output_format qsv '
|
|
|
|
|
|
|
|
+ '-init_hw_device qsv:hw,child_device_type=d3d11va ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
// Default to enabling hwaccel for output only
|
|
|
|
|
|
|
|
response.preset += '-hwaccel_output_format qsv -init_hw_device qsv:hw_any ';
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DECODE FLAGS
|
|
|
|
// DECODE FLAGS
|
|
|
|
|
|
|
|
// VC1 & VP8 are no longer supported on new HW, add cases here if your HW does support
|
|
|
|
if (os.platform() !== 'darwin') {
|
|
|
|
if (os.platform() !== 'darwin') {
|
|
|
|
if (high10 === false) { // Don't enable for High10
|
|
|
|
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
case 'mpeg2':
|
|
|
|
case 'mpeg2':
|
|
|
|
response.preset += '-c:v mpeg2_qsv';
|
|
|
|
response.preset += '-c:v mpeg2_qsv';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'h264':
|
|
|
|
case 'h264':
|
|
|
|
|
|
|
|
if (high10 !== true) { // Don't enable for High10
|
|
|
|
response.preset += '-c:v h264_qsv';
|
|
|
|
response.preset += '-c:v h264_qsv';
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
case 'vc1':
|
|
|
|
response.preset += `-c:v ${file.video_codec_name}`;
|
|
|
|
response.preset += '-c:v vc1_qsv';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'mjpeg':
|
|
|
|
case 'mjpeg':
|
|
|
|
response.preset += '-c:v mjpeg_qsv';
|
|
|
|
response.preset += '-c:v mjpeg_qsv';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'vp8':
|
|
|
|
|
|
|
|
response.preset += '-c:v vp8_qsv';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hevc':
|
|
|
|
case 'hevc':
|
|
|
|
response.preset += '-c:v hevc_qsv';
|
|
|
|
response.preset += '-c:v hevc_qsv';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'vp9': // Should be supported by 8th Gen +
|
|
|
|
case 'vp9': // Should be supported by 8th Gen +
|
|
|
|
response.preset += '-c:v vp9_qsv';
|
|
|
|
response.preset += '-c:v vp9_qsv';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'av1': // Should be supported by 11th gen +
|
|
|
|
|
|
|
|
response.preset += '-c:v av1_qsv';
|
|
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
response.preset += '';
|
|
|
|
// Use incoming format for software decode
|
|
|
|
}
|
|
|
|
response.preset += `-c:v ${file.video_codec_name}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -798,13 +797,94 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
response.preset += 'hevc_qsv';
|
|
|
|
response.preset += 'hevc_qsv';
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
case 'win32':
|
|
|
|
case 'win32':
|
|
|
|
response.preset += 'hevc_qsv -load_plugin hevc_hw';
|
|
|
|
response.preset += 'hevc_qsv';
|
|
|
|
// Windows needs the additional -load_plugin. Tested working on a Win 10 - i5-10505
|
|
|
|
// Tested working on a Win 10 - i5-10505
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
default:
|
|
|
|
response.preset += 'hevc_qsv'; // Default to QSV
|
|
|
|
response.preset += 'hevc_qsv'; // Default to QSV
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only add on for HW decoded formats
|
|
|
|
|
|
|
|
// VC1 & VP8 are no longer supported on new HW, add cases here if your HW does support
|
|
|
|
|
|
|
|
if (swDecode !== true && os.platform() !== 'darwin') {
|
|
|
|
|
|
|
|
// Check if -vf cmd has already been used on user input
|
|
|
|
|
|
|
|
if (inputs.extra_qsv_options.search('-vf scale_qsv') >= 0) {
|
|
|
|
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
|
|
|
|
case 'mpeg2':
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h264':
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mjpeg':
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hevc':
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'vp9': // Should be supported by 8th Gen +
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'av1': // Should be supported by 11th gen +
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
|
|
|
inputs.extra_qsv_options += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (extraArguments.search('-vf') === -1) {
|
|
|
|
|
|
|
|
// Check if -vf cmd has been used on the other var instead, if not add it & rest of cmd
|
|
|
|
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
|
|
|
|
case 'mpeg2':
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h264':
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mjpeg':
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hevc':
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'vp9': // Should be supported by 8th Gen +
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'av1': // Should be supported by 11th gen +
|
|
|
|
|
|
|
|
extraArguments += '-vf hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// Otherwise add the cmd onto the end
|
|
|
|
|
|
|
|
switch (file.video_codec_name) {
|
|
|
|
|
|
|
|
case 'mpeg2':
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h264':
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'mjpeg':
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hevc':
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'vp9': // Should be supported by 8th Gen +
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'av1': // Should be supported by 11th gen +
|
|
|
|
|
|
|
|
extraArguments += ',hwupload=extra_hw_frames=64,format=qsv ';
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Add the rest of the ffmpeg command
|
|
|
|
// Add the rest of the ffmpeg command
|
|
|
|
switch (os.platform()) {
|
|
|
|
switch (os.platform()) {
|
|
|
|
case 'darwin':
|
|
|
|
case 'darwin':
|
|
|
|
@ -818,7 +898,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
|
|
|
|
// Normal behavior
|
|
|
|
// Normal behavior
|
|
|
|
response.preset += ` ${bitrateSettings} `
|
|
|
|
response.preset += ` ${bitrateSettings} `
|
|
|
|
+ `-preset ${inputs.encoder_speedpreset} ${inputs.extra_qsv_options} `
|
|
|
|
+ `-preset ${inputs.encoder_speedpreset} ${inputs.extra_qsv_options} `
|
|
|
|
+ `-c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments} ${metadataEncode}`;
|
|
|
|
+ `-c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
response.processFile = true;
|
|
|
|
response.processFile = true;
|
|
|
|
|