Merge branch 'master' into JB69Plugin

make-only-subtitle-default
HaveAGitGat 4 years ago committed by GitHub
commit 0b3a53bfec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,76 +8,77 @@ function details() {
Operation: "Transcode",
Description:
"In a single pass ensures all files are in MKV containers and where possible encoded in h265 (Tiered bitrate based on resolution), removes audio and subtitles that are not in the configured language or marked as commentary.",
Tags: "pre-processing,ffmpeg,nvenc h265",
Version: "2.0",
Tags: "pre-processing,ffmpeg,nvenc h265",
Inputs: [
{
name: "target_bitrate_480p576p",
tooltip: `Specify the target bitrate for 480p and 576p files, if current bitrate exceeds the target. Otherwise target_pct_reduction will be used.
\\nExample 1 Mbps:\\n
1000000`,
\\nExample 1 Mbps:\\n
1000000`,
},
{
name: "target_bitrate_720p",
tooltip: `Specify the target bitrate for 720p files, if current bitrate exceeds the target. Otherwise target_pct_reduction will be used.
\\nExample 2 Mbps:\\n
2000000`,
\\nExample 2 Mbps:\\n
2000000`,
},
{
name: "target_bitrate_1080p",
tooltip: `Specify the target bitrate for 1080p files, if current bitrate exceeds the target. Otherwise target_pct_reduction will be used.
\\nExample 2.5 Mbps:\\n
2500000`,
\\nExample 2.5 Mbps:\\n
2500000`,
},
{
name: "target_bitrate_4KUHD",
tooltip: `Specify the target bitrate for 4KUHD files, if current bitrate exceeds the target. Otherwise target_pct_reduction will be used.
\\nExample 14 Mbps:\\n
14000000`,
\\nExample 14 Mbps:\\n
14000000`,
},
{
{
name: "target_pct_reduction",
tooltip: `Specify the target reduction of bitrate, if current bitrate is less than resolution targets.
\\nExample 60%:\\n
.60`,
\\nExample 50%:\\n
.50`,
},
{
name: "audio_language",
tooltip: `Specify language tag/s here for the audio tracks you'd like to keep, recommended to keep "und" as this stands for undertermined, some files may not have the language specified. Must follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
eng
\\nExample:\\n
eng,und
\\nExample:\\n
eng,und
\\nExample:\\n
eng,und,jap`,
\\nExample:\\n
eng,und,jap`,
},
{
name: "audio_commentary",
tooltip: `Specify if audio tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
true
\\nExample:\\n
false`,
\\nExample:\\n
false`,
},
{
name: "subtitle_language",
tooltip: `Specify language tag/s here for the subtitle tracks you'd like to keep. Must follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
eng
\\nExample:\\n
eng,jap`,
\\nExample:\\n
eng,jap`,
},
{
name: "subtitle_commentary",
tooltip: `Specify if subtitle tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
true
\\nExample:\\n
false`,
\\nExample:\\n
false`,
},
],
};
@ -196,21 +197,23 @@ function loopOverStreamsOfType(file, type, method) {
/**
* Removes audio tracks that aren't in the allowed languages or labeled as Commentary tracks.
* Transcode audio if specified.
*/
function buildAudioConfiguration(inputs, file, logger) {
var configuration = new Configurator(["-c:a copy"]);
var stream_count = 0;
var streams_removing = 0;
var languages = inputs.audio_language.split(",");
loopOverStreamsOfType(file, "audio", function (stream, id) {
stream_count++;
function audioProcess(stream, id) {
stream_count++;
if ("tags" in stream && "title" in stream.tags && inputs.audio_commentary.toLowerCase() == "true") {
if (
stream.tags.title.toLowerCase().includes("commentary") ||
stream.tags.title.toLowerCase().includes("description") ||
stream.tags.title.toLowerCase().includes("sdh")
) {
streams_removing++;
streams_removing++;
configuration.AddOutputSetting(`-map -0:a:${id}`);
logger.AddError(
`Removing Commentary or Description audio track: ${stream.tags.title}`
@ -222,24 +225,28 @@ function buildAudioConfiguration(inputs, file, logger) {
if ("language" in stream.tags) {
if (languages.indexOf(stream.tags.language.toLowerCase()) === -1) {
configuration.AddOutputSetting(`-map -0:a:${id}`);
streams_removing++;
streams_removing++;
logger.AddError(
`Removing audio track in language ${stream.tags.language}`
);
}
}
}});
}
}
loopOverStreamsOfType(file, "audio", audioProcess);
if (stream_count == streams_removing) {
logger.AddError(
`*** All audio tracks would have been removed. Defaulting to keeping all tracks for this file.`
);
configuration.ResetOutputSetting(["-c:a copy"]);
logger.AddError(
`*** All audio tracks would have been removed. Defaulting to keeping all tracks for this file.`
);
configuration.ResetOutputSetting(["-c:a copy"]);
}
return configuration;
}
/**
* Removes subtitles that aren't in the allowed languages or labeled as Commentary tracks.
*/
@ -250,25 +257,39 @@ function buildSubtitleConfiguration(inputs, file, logger) {
if (languages.length === 0) return configuration;
loopOverStreamsOfType(file, "subtitle", function (stream, id) {
if (stream.codec_name === "eia_608") {
if ((stream.codec_name === "eia_608") ||
(stream.codec_tag_string === "mp4s")) {
// unsupported subtitle codec?
configuration.AddOutputSetting(`-map -0:s:${id}`);
return;
logger.AddError(
`Removing unsupported subtitle`
);
return;
}
// Remove unknown sub streams
if (!("codec_name" in stream)) {
configuration.AddOutputSetting(`-map -0:s:${id}`);
logger.AddError(
`Removing unknown subtitle`
);
return;
}
if ("tags" in stream) {
// Remove unwated languages
// Remove unwanted languages
if ("language" in stream.tags) {
if (languages.indexOf(stream.tags.language.toLowerCase()) === -1) {
configuration.AddOutputSetting(`-map -0:s:${id}`);
logger.AddError(
`Removing subtitle in language ${stream.tags.language}`
);
return;
}
}
// Remove commentary subtitles
if ("title" in stream.tags && (inputs.subtitle_commentary.toLowerCase() == "true")) {
if ("title" in stream.tags && (inputs.subtitle_commentary.toLowerCase() == "true")) {
if (
stream.tags.title.toLowerCase().includes("commentary") ||
stream.tags.title.toLowerCase().includes("description") ||
@ -278,6 +299,7 @@ function buildSubtitleConfiguration(inputs, file, logger) {
logger.AddError(
`Removing Commentary or Description subtitle: ${stream.tags.title}`
);
return;
}
}
}
@ -292,118 +314,104 @@ function buildSubtitleConfiguration(inputs, file, logger) {
/**
* Attempts to ensure that video streams are h265 encoded and inside an
* MKV container. Will use CPU, Intel Quick Sync or NVidia NVENC encoding
* as configured in the plugin inputs.
* MKV container.
*/
function buildVideoConfiguration(inputs, file, logger) {
var configuration = new Configurator(["-map 0", "-map -0:d", "-c:v copy"]);
loopOverStreamsOfType(file, "video", function (stream, id) {
if (stream.codec_name === "mjpeg") {
configuration.AddOutputSetting(`-map -v:${id}`);
return;
}
if (stream.codec_name === "hevc" && file.container === "mkv") {
logger.AddSuccess("File is in HEVC codec and in MKV");
return;
}
// Check if should Remux.
if (stream.codec_name === "hevc" && file.container !== "mkv") {
configuration.AddOutputSetting("-c:v copy");
logger.AddError("File is in HEVC codec but not MKV. Will remux");
}
// Check if should Transcode.
if (stream.codec_name !== "hevc") {
var bitrateprobe = calculateBitrate(file);
var bitratetarget = 0;
var bitratemax = 0;
var cq = 0;
var bitratecheck = 0;
/**
* NVENC Configuration
*/
/* Determine tiered bitrate variables */
if (file.video_resolution === "480p" || file.video_resolution === "576p" ) {
bitratecheck = parseInt(inputs.target_bitrate_480p576p);
if(bitrateprobe !== null && bitrateprobe < bitratecheck) {
bitratetarget = parseInt((bitrateprobe * inputs.target_pct_reduction) / 1000); // Lower Bitrate to 60% of original and convert to KB
bitratemax = bitratetarget + 500; // Set max bitrate to 0.5MB Higher
cq = 29;
} else {
bitratetarget = parseInt(inputs.target_bitrate_480p576p / 1000);
bitratemax = bitratetarget + 500;
cq = 29;
}
var configuration = new Configurator(["-map 0", "-map -0:d", "-c:v copy"]);
var tiered = {
"480p" : {"bitrate" : inputs.target_bitrate_480p576p,
"max_increase" : 500,
"cq" : 29
},
"576p" : {"bitrate" : inputs.target_bitrate_480p576p,
"max_increase" : 500,
"cq" : 29
},
"720p" : {"bitrate" : inputs.target_bitrate_720p,
"max_increase" : 2000,
"cq" : 30
},
"1080p" : {"bitrate" : inputs.target_bitrate_1080p,
"max_increase" : 2500,
"cq" : 31
},
"4KUHD" : {"bitrate" : inputs.target_bitrate_4KUHD,
"max_increase" : 6000,
"cq" : 31
},
"Other" : {"bitrate" : inputs.target_bitrate_1080p,
"max_increase" : 2500,
"cq" : 31
}
};
var inputSettings = {
"h263" : "-c:v h263_cuvid",
"h264" : "",
"mjpeg" : "c:v mjpeg_cuvid",
"mpeg1" : "-c:v mpeg1_cuvid",
"mpeg2" : "-c:v mpeg2_cuvid",
"vc1" : "-c:v vc1_cuvid",
"vp8" : "-c:v vp8_cuvid",
"vp9" : "-c:v vp9_cuvid"
}
if (file.video_resolution === "720p") {
bitratecheck = parseInt(inputs.target_bitrate_720p);
if(bitrateprobe !== null && bitrateprobe < bitratecheck) {
bitratetarget = parseInt((bitrateprobe * inputs.target_pct_reduction) / 1000); // Lower Bitrate to 60% of original and convert to KB
bitratemax = bitratetarget + 2000; // Set max bitrate to 2MB Higher
cq = 30;
} else {
bitratetarget = parseInt(inputs.target_bitrate_720p / 1000);
bitratemax = bitratetarget + 2000;
cq = 30;
function videoProcess(stream, id) {
if (stream.codec_name === "mjpeg") {
configuration.AddOutputSetting(`-map -v:${id}`);
return;
}
}
if (file.video_resolution === "1080p") {
bitratecheck = parseInt(inputs.target_bitrate_1080p);
if(bitrateprobe !== null && bitrateprobe < bitratecheck) {
bitratetarget = parseInt((bitrateprobe * inputs.target_pct_reduction) / 1000); // Lower Bitrate to 60% of original and convert to KB
bitratemax = bitratetarget + 2500; // Set max bitrate to 2.5MB Higher
cq = 31;
} else {
bitratetarget = parseInt(inputs.target_bitrate_1080p / 1000);
bitratemax = bitratetarget + 2500;
cq = 31;
if ((stream.codec_name === "hevc" || stream.codec_name === "vp9") && file.container === "mkv") {
logger.AddSuccess("File is in HEVC codec and in MKV");
return;
}
}
if (file.video_resolution === "4KUHD") {
bitratecheck = parseInt(inputs.target_bitrate_4KUHD);
if(bitrateprobe !== null && bitrateprobe < bitratecheck) {
bitratetarget = parseInt((bitrateprobe * inputs.target_pct_reduction) / 1000); // Lower Bitrate to 60% of original and convert to KB
bitratemax = bitratetarget + 6000; // Set max bitrate to 6MB Higher
cq = 31;
} else {
bitratetarget = parseInt(inputs.target_bitrate_4KUHD / 1000);
bitratemax = bitratetarget + 6000;
cq = 31;
// Check if should Remux.
if ((stream.codec_name === "hevc" || stream.codec_name === "vp9") && file.container !== "mkv") {
configuration.AddOutputSetting("-c:v copy");
logger.AddError("File is in HEVC codec but not MKV. Will remux");
}
}
// remove png streams.
if (stream.codec_name === "png") {
configuration.AddOutputSetting(`-map -0:v:${id}`);
} else if (stream.codec_name !== "hevc" && stream.codec_name !== "vp9") { // Check if should Transcode.
var bitrateprobe = calculateBitrate(file);
var bitratetarget = 0;
var bitratemax = 0;
var cq = 0;
var bitratecheck = 0;
/* Determine tiered bitrate variables */
var tier = tiered[file.video_resolution];
bitratecheck = parseInt(tier["bitrate"]);
if (bitrateprobe !== null && bitrateprobe < bitratecheck) {
bitratetarget = parseInt((bitrateprobe * inputs.target_pct_reduction) / 1000);
} else {
bitratetarget = parseInt(tier["bitrate"] / 1000);
}
bitratemax = bitratetarget + tier["max_increase"];
cq = tier["cq"];
configuration.RemoveOutputSetting("-c:v copy");
configuration.AddOutputSetting(
`-c:v hevc_nvenc -rc:v vbr_hq -qmin 0 -cq:v ${cq} -b:v ${bitratetarget}k -maxrate:v ${bitratemax}k -preset medium -rc-lookahead 32 -spatial_aq:v 1 -aq-strength:v 8`
);
configuration.AddInputSetting(inputSettings[file.video_codec_name]);
if (file.video_codec_name === "h264" && file.ffProbeData.streams[0].profile !== "High 10") {
configuration.AddInputSetting("-c:v h264_cuvid");
}
logger.AddError("Transcoding to HEVC using NVidia NVENC");
}
}
configuration.RemoveOutputSetting("-c:v copy");
configuration.AddOutputSetting(
`-c:v hevc_nvenc -rc:v vbr_hq -qmin 0 -cq:v ${cq} -b:v ${bitratetarget}k -maxrate:v ${bitratemax}k -preset medium -rc-lookahead 32 -spatial_aq:v 1 -aq-strength:v 8`
);
if (file.video_codec_name === "h263") {
configuration.AddInputSetting("-c:v h263_cuvid");
} else if (file.video_codec_name === "h264") {
if (file.ffProbeData.streams[0].profile !== "High 10") {
configuration.AddInputSetting("-c:v h264_cuvid");
} else if (file.video_codec_name === "mjpeg") {
configuration.AddInputSetting("c:v mjpeg_cuvid");
} else if (file.video_codec_name == "mpeg1") {
configuration.AddInputSetting("-c:v mpeg1_cuvid");
} else if (file.video_codec_name == "mpeg2") {
configuration.AddInputSetting("-c:v mpeg2_cuvid");
} else if (file.video_codec_name == "vc1") {
configuration.AddInputSetting("-c:v vc1_cuvid");
} else if (file.video_codec_name == "vp8") {
configuration.AddInputSetting("-c:v vp8_cuvid");
} else if (file.video_codec_name == "vp9") {
configuration.AddInputSetting("-c:v vp9_cuvid");
}
}
logger.AddError("Transcoding to HEVC using NVidia NVENC");
}
});
loopOverStreamsOfType(file, "video", videoProcess);
if (!configuration.shouldProcess) {
logger.AddSuccess("No video processing necessary");
@ -430,7 +438,26 @@ function plugin(file, _librarySettings, inputs) {
var videoSettings = buildVideoConfiguration(inputs, file, logger);
var subtitleSettings = buildSubtitleConfiguration(inputs, file, logger);
response.preset = `${videoSettings.GetInputSettings()},${videoSettings.GetOutputSettings()} ${audioSettings.GetOutputSettings()} ${subtitleSettings.GetOutputSettings()} -max_muxing_queue_size 4096`;
response.preset = `${videoSettings.GetInputSettings()},${videoSettings.GetOutputSettings()}`
response.preset += ` ${audioSettings.GetOutputSettings()}`
response.preset += ` ${subtitleSettings.GetOutputSettings()}`
response.preset += ` -max_muxing_queue_size 9999`;
// Extra parameters
var id = 0;
var badTypes = ['mov_text', 'eia_608', 'timed_id3', 'mp4s'];
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
if (badTypes.includes(file.ffProbeData.streams[i].codec_name)) {
response.preset += ` -map -0:${i}`;
};
id++;
}
// b frames argument
response.preset += ` -bf 5`;
// fix probe size errors
response.preset += ` -analyzeduration 2147483647 -probesize 2147483647`;
response.processFile =
audioSettings.shouldProcess ||
videoSettings.shouldProcess ||
@ -445,4 +472,4 @@ function plugin(file, _librarySettings, inputs) {
}
module.exports.details = details;
module.exports.plugin = plugin;
module.exports.plugin = plugin;

@ -1,394 +0,0 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: 'Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac)',
Stage: 'Pre-processing',
Name: 'Transcode Using QSV or VT & FFMPEG',
Type: 'Video',
Operation: 'Transcode',
Description: `Files not in H265 will be transcoded into H265 using hw with ffmpeg, assuming mkv container. Plugin uses QS if the node runs on a PC, or Videotoolbox if run on a Mac.
Much thanks to Migz for bulk of the important code.
Quality is controlled via bitrate adjustments - H264 to H265 assumes 0.5x bitrate. Resolution change from 1080p to 720p assumes 0.7x bitrate.
Audio conversion is either 2 channel ac3 or 6 channel ac3, for maximal compatibility and small file size. All subtitles removed.
The idea is to homogenize your collection to 1080p or higher movies with 5.1 audio, or 720p TV shows with 2.0 audio.`,
Tags: 'pre-processing,ffmpeg,video only,configurable,h265',
Inputs: [{
name: 'audio_channels',
tooltip: `Specify whether to modify audio channels.
\\n Leave empty to disable.
\\nExample:\\n
2 - produces single 2.0 channel ac3 audio file, in English, unless not possible.
\\nExample:\\n
6 - produces single 5.1 channel ac3 file, in English, unless not possible.`,
},
{
name: 'resize',
tooltip: `Specify if output file should be reduced to 720p from 1080p. Default is false.
\\nExample:\\n
yes
\\nExample:\\n
no`,
},
{
name: 'bitrate_cutoff',
tooltip: `Specify bitrate cutoff, files with a current bitrate lower then this will not be transcoded.
\\n Rate is in kbps.
\\n Leave empty to disable.
\\nExample:\\n
6000
\\nExample:\\n
4000`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
const response = {
container: '.mkv',
processFile: false,
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
};
let duration = '';
let convertAudio = false;
let convertVideo = false;
let extraArguments = '';
// Check if inputs.container has been configured. If it hasn't then exit plugin.
if (inputs.container === '') {
response.infoLog += 'Plugin has not been configured, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += 'File is not a video. \n';
return response;
}
const os = require('os');
// VIDEO SECTION
let bitRateMultiplier = 1.00;
let videoIdx = -1;
let willBeResized = false;
let videoOptions = `-map 0:v -c:v copy `;
// video options
// hevc, 1080, false - do nothing
// hevc, not 1080 - do nothing
// hevc, 1080, true - resize, mult 0.5
// not hevc, 1080, true - resize, mult 0.25
// not hevc, 1080, false - no resize, mult 0.5
// not hevc, not 1080 - no resize, mult 0.5
// Go through each stream in the file.
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Check if stream is a video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
// 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.
if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png' ) {
extraArguments += `-map -v:${videoIdx} `;
convertVideo = true; }
/* // no video conversion if: hevc, 1080, false OR hevc, not 1080
if (file.ffProbeData.streams[i].codec_name === 'hevc'
&& ((file.video_resolution === '1080p' && inputs.resize === 'no' ) || (file.video_resolution !== '1080p' ))) {
convertVideo = false; } */
// no video conversion if: hevc, 1080, false
if (file.ffProbeData.streams[i].codec_name === 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'no' ) {
convertVideo = false; }
// no video conversion if: hevc, not 1080
if (file.ffProbeData.streams[i].codec_name === 'hevc' && (file.ffProbeData.streams[i].width < 1800 || file.ffProbeData.streams[i].width > 2000)) {
convertVideo = false; }
// resize video if: hevc, 1080, true
if (file.ffProbeData.streams[i].codec_name === 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'yes' ) {
convertVideo = true;
willBeResized = true;
bitRateMultiplier = 0.7; }
// resize video if: not hevc, 1080, true
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'yes' ) {
convertVideo = true;
willBeResized = true;
bitRateMultiplier = 0.4; }
// no resize video if: not hevc, 1080, false
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'no' ) {
convertVideo = true;
bitRateMultiplier = 0.5; }
// no resize video if: not hevc, not 1080
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width < 1800 ) {
convertVideo = true;
bitRateMultiplier = 0.5; }
}
// Increment videoIdx.
videoIdx += 1;
}
// figure out final bitrate
// Check if duration info is filled, if so times it by 0.0166667 to get time in minutes.
// If not filled then get duration of stream 0 and do the same.
if (typeof file.meta.Duration !== 'undefined') {
duration = file.meta.Duration * 0.0166667;
} else {
duration = file.ffProbeData.streams[0].duration * 0.0166667;
}
let bitrateSettings = '';
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
// Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/
// eslint-disable-next-line no-bitwise
const currentBitrate = ~~(file.file_size / (duration * 0.0075));
// Use the same calculation used for currentBitrate but divide it in half to get targetBitrate.
// Logic of h265 can be half the bitrate as h264 without losing quality.
// eslint-disable-next-line no-bitwise
const targetBitrate = ~~(file.file_size / (duration * 0.0075) * bitRateMultiplier);
// Allow some leeway under and over the targetBitrate.
const minimumBitrate = ~~(targetBitrate * 0.7);
const maximumBitrate = ~~(targetBitrate * 1.3);
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculcated.
if (targetBitrate === 0) {
response.processFile = false;
response.infoLog += 'Target bitrate could not be calculated. Skipping this plugin. \n';
return response;
}
// Check if inputs.bitrate cutoff has something entered.
// (Entered means user actually wants something to happen, empty would disable this).
if (inputs.bitrate_cutoff !== '') {
// Checks if currentBitrate is below inputs.bitrate_cutoff
// If so then don't convert video.
if (currentBitrate <= inputs.bitrate_cutoff) {
convertVideo = false; }
}
// AUDIO SECTION
// Set up required variables.
let audioOptions = `-map 0:a -c:a copy `;
let audioIdx = 0;
let numberofAudioChannels = 0;
let has2Channels = false;
let has6Channels = false;
let has8Channels = false;
let lang2Channels = '';
let lang6Channels = '';
let lang8Channels = '';
let type2Channels = '';
let type6Channels = '';
let type8Channels = '';
let keepAudioIdx = -1;
let keepIGuessAudioIdx = -1;
let encodeAudioIdx = -1;
let keepAudioStream = -1;
let encodeAudioStream = -1;
let originalAudio = '';
// Go through each stream in the file.
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Go through all audio streams and check if 2,6 & 8 channel tracks exist or not.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
numberofAudioChannels += 1;
if (file.ffProbeData.streams[i].channels === 2 && has2Channels === false) {
has2Channels = true;
lang2Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type2Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
if (file.ffProbeData.streams[i].channels === 6 && has6Channels === false) {
has6Channels = true;
lang6Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type6Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
if (file.ffProbeData.streams[i].channels === 8 && has8Channels === false) {
has8Channels = true;
lang8Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type8Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
}
} catch (err) {
// Error
}
}
// Are we processing for 6 channels?
if (inputs.audio_channels == 6) {
audioIdx = -1;
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
audioIdx += 1;
if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') {
if (file.ffProbeData.streams[i].channels == 6 ) {
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') {
//response.infoLog += `Found 6 channel audio in proper language and codec, audio stream ${audioIdx}\n`;
if (keepAudioIdx === -1) {
keepAudioIdx = audioIdx;
keepAudioStream = i;}
} else {
//response.infoLog += `Found 6 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
if (encodeAudioIdx === -1) {
encodeAudioIdx = audioIdx;
encodeAudioStream = i;}
}}
if (file.ffProbeData.streams[i].channels > 6 ) {
//response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
if (encodeAudioIdx === -1) {
encodeAudioIdx = audioIdx;
encodeAudioStream = i;}
}
}
}
} catch (err) {
// Error
}
}
if (keepAudioIdx === -1 && encodeAudioIdx === -1) { // didn't find any 5.1 or better audio streams in proper language, defaulting to using 2 channels
inputs.audio_channels = '2';}
}
// Are we processing for 2 channels?
if (inputs.audio_channels == 2) {
audioIdx = -1;
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
audioIdx += 1;
if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') {
if (file.ffProbeData.streams[i].channels == 2 ) {
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'aac' || file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') {
//response.infoLog += `Found 2 channel audio in proper language and codec, audio stream ${audioIdx}\n`;
if (keepAudioIdx === -1) {
keepAudioIdx = audioIdx;
keepAudioStream = i;}
} else {
//response.infoLog += `Found 2 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
if (encodeAudioIdx === -1) {
encodeAudioIdx = audioIdx;
encodeAudioStream = i;}
}
} else {
//response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
if (encodeAudioIdx === -1) {
encodeAudioIdx = audioIdx;
encodeAudioStream = i;}
}
}
// response.infoLog += `a ${audioIdx}. k ${keepAudioIdx}. e ${encodeAudioIdx}\n `;
}
} catch (err) {
// Error
}
}
}
let audioMessage = '';
// selecting channels to keep, only if 2 or 6 channels processed
if (keepAudioIdx !== -1) {
//keep audio, exclude everything else
if (numberofAudioChannels !== 1) {
convertAudio = true;
audioMessage += `keeping audio stream ${keepAudioIdx}.`;
audioOptions = `-map 0:a:${keepAudioIdx} -c:a copy `;
originalAudio += `${file.ffProbeData.streams[keepAudioStream].channels} channel ${file.ffProbeData.streams[keepAudioStream].codec_name} --> ${inputs.audio_channels} channel ac3`;}
} else {
if (encodeAudioIdx !== -1) {
// encode this audio
convertAudio = true;
audioMessage += `encoding audio stream ${encodeAudioIdx}. `;
audioOptions = `-map 0:a:${encodeAudioIdx} -c:a ac3 -ac ${inputs.audio_channels} `; // 2 or 6 channels encoding
originalAudio += `${file.ffProbeData.streams[encodeAudioStream].channels} channel ${file.ffProbeData.streams[encodeAudioStream].codec_name} --> ${inputs.audio_channels} channel ac3`;
} else {
// do not encode audio
convertAudio = false;
audioMessage += `no audio to encode.`;
}
}
// test for whether the file needs to be processed - separate for video and audio convertAudio, convertVideo
if (convertAudio === false && convertVideo === false) { // if nothing to do, exit
response.infoLog += `File is processed already, nothing to do`;
response.processFile = false;
return response; }
// Generate ffmpeg command line arguments in total
// few defaults
response.preset = `, -sn `;
if (convertVideo === true) {
// Set bitrateSettings variable using bitrate information calculated earlier.
bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k `
+ `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`;
if (willBeResized === true) {
extraArguments += `-filter:v scale=1280:-1 `; }
if (os.platform() === 'darwin') {
videoOptions = `-map 0:v -c:v hevc_videotoolbox -profile main `;
}
if (os.platform() === 'win32') {
videoOptions = `-map 0:v -c:v hevc_qsv -load_plugin hevc_hw `;
}
}
response.preset += `${videoOptions} ${bitrateSettings} ${extraArguments} ${audioOptions} `;
let outputResolution = file.video_resolution;
if (willBeResized === true) {
outputResolution = '720p';}
if (convertVideo === false) {
response.infoLog += `NOT converting video ${file.video_resolution}, ${file.video_codec_name}, bitrate = ${currentBitrate} \n`;
} else {
response.infoLog += `Converting video, `;
if (willBeResized === false ) { response.infoLog += `NOT `; }
response.infoLog += `resizing. ${file.video_resolution}, ${file.video_codec_name} --> ${outputResolution}, hevc. bitrate = ${currentBitrate} --> ${targetBitrate}, multiplier ${bitRateMultiplier}. \n`;
}
if (convertAudio === true) {
response.infoLog += `Converting audio, ${audioMessage} ${originalAudio}. \n`;
} else {
response.infoLog += `Not converting audio. \n`;}
response.infoLog += `2 channels - ${lang2Channels} ${type2Channels} \n`;
response.infoLog += `6 channels - ${lang6Channels} ${type6Channels} \n`;
response.infoLog += `8 channels - ${lang8Channels} ${type8Channels} `;
response.processFile = true;
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -0,0 +1,400 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
/* eslint max-len: 0 */
/* eslint no-bitwise: 0 */
/* eslint no-mixed-operators: 0 */
const os = require('os');
function details() {
return {
id: 'Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac)',
Stage: 'Pre-processing',
Name: 'Transcode Using QSV or VT & FFMPEG',
Type: 'Video',
Operation: 'Transcode',
Description: `Files not in H265 will be transcoded into H265 using hw with ffmpeg, assuming mkv container. Plugin uses QS if the node runs on a PC, or Videotoolbox if run on a Mac.
Much thanks to Migz for bulk of the important code.
Quality is controlled via bitrate adjustments - H264 to H265 assumes 0.5x bitrate. Resolution change from 1080p to 720p assumes 0.7x bitrate.
Audio conversion is either 2 channel ac3 or 6 channel ac3, for maximal compatibility and small file size. All subtitles removed.
The idea is to homogenize your collection to 1080p or higher movies with 5.1 audio, or 720p TV shows with 2.0 audio.`,
Tags: 'pre-processing,ffmpeg,video only,configurable,h265',
Inputs: [{
name: 'audio_channels',
tooltip: `Specify whether to modify audio channels.
\\n Leave empty to disable.
\\nExample:\\n
2 - produces single 2.0 channel ac3 audio file, in English, unless not possible.
\\nExample:\\n
6 - produces single 5.1 channel ac3 file, in English, unless not possible.`,
},
{
name: 'resize',
tooltip: `Specify if output file should be reduced to 720p from 1080p. Default is false.
\\nExample:\\n
yes
\\nExample:\\n
no`,
},
{
name: 'bitrate_cutoff',
tooltip: `Specify bitrate cutoff, files with a current bitrate lower then this will not be transcoded.
\\n Rate is in kbps.
\\n Leave empty to disable.
\\nExample:\\n
6000
\\nExample:\\n
4000`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
const response = {
container: '.mkv',
processFile: false,
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
};
let duration = '';
let convertAudio = false;
let convertVideo = false;
let extraArguments = '';
// Check if inputs.container has been configured. If it hasn't then exit plugin.
if (inputs.container === '') {
response.infoLog += 'Plugin has not been configured, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += 'File is not a video. \n';
return response;
}
// VIDEO SECTION
let bitRateMultiplier = 1.00;
let videoIdx = -1;
let willBeResized = false;
let videoOptions = '-map 0:v -c:v copy ';
// video options
// hevc, 1080, false - do nothing
// hevc, not 1080 - do nothing
// hevc, 1080, true - resize, mult 0.5
// not hevc, 1080, true - resize, mult 0.25
// not hevc, 1080, false - no resize, mult 0.5
// not hevc, not 1080 - no resize, mult 0.5
// Go through each stream in the file.
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Check if stream is a video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
// 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.
if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png') {
extraArguments += `-map -v:${videoIdx} `;
convertVideo = true;
}
/* // no video conversion if: hevc, 1080, false OR hevc, not 1080
if (file.ffProbeData.streams[i].codec_name === 'hevc'
&& ((file.video_resolution === '1080p' && inputs.resize === 'no' ) || (file.video_resolution !== '1080p' ))) {
convertVideo = false; } */
// no video conversion if: hevc, 1080, false
if (file.ffProbeData.streams[i].codec_name === 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'no') {
convertVideo = false;
}
// no video conversion if: hevc, not 1080
if (file.ffProbeData.streams[i].codec_name === 'hevc' && (file.ffProbeData.streams[i].width < 1800 || file.ffProbeData.streams[i].width > 2000)) {
convertVideo = false;
}
// resize video if: hevc, 1080, true
if (file.ffProbeData.streams[i].codec_name === 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'yes') {
convertVideo = true;
willBeResized = true;
bitRateMultiplier = 0.7;
}
// resize video if: not hevc, 1080, true
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'yes') {
convertVideo = true;
willBeResized = true;
bitRateMultiplier = 0.4;
}
// no resize video if: not hevc, 1080, false
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width > 1800 && file.ffProbeData.streams[i].width < 2000 && inputs.resize === 'no') {
convertVideo = true;
bitRateMultiplier = 0.5;
}
// no resize video if: not hevc, not 1080
if (file.ffProbeData.streams[i].codec_name !== 'hevc' && file.ffProbeData.streams[i].width < 1800) {
convertVideo = true;
bitRateMultiplier = 0.5;
}
}
// Increment videoIdx.
videoIdx += 1;
}
// figure out final bitrate
// Check if duration info is filled, if so times it by 0.0166667 to get time in minutes.
// If not filled then get duration of stream 0 and do the same.
if (typeof file.meta.Duration !== 'undefined') {
duration = file.meta.Duration * 0.0166667;
} else {
duration = file.ffProbeData.streams[0].duration * 0.0166667;
}
let bitrateSettings = '';
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
// Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/
// eslint-disable-next-line no-bitwise
const currentBitrate = ~~(file.file_size / (duration * 0.0075));
// Use the same calculation used for currentBitrate but divide it in half to get targetBitrate.
// Logic of h265 can be half the bitrate as h264 without losing quality.
// eslint-disable-next-line no-bitwise
const targetBitrate = ~~(file.file_size / (duration * 0.0075) * bitRateMultiplier);
// Allow some leeway under and over the targetBitrate.
const minimumBitrate = ~~(targetBitrate * 0.7);
const maximumBitrate = ~~(targetBitrate * 1.3);
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculcated.
if (targetBitrate === 0) {
response.processFile = false;
response.infoLog += 'Target bitrate could not be calculated. Skipping this plugin. \n';
return response;
}
// Check if inputs.bitrate cutoff has something entered.
// (Entered means user actually wants something to happen, empty would disable this).
if (inputs.bitrate_cutoff !== '') {
// Checks if currentBitrate is below inputs.bitrate_cutoff
// If so then don't convert video.
if (currentBitrate <= inputs.bitrate_cutoff) {
convertVideo = false;
}
}
// AUDIO SECTION
// Set up required variables.
let audioOptions = '-map 0:a -c:a copy ';
let audioIdx = 0;
let numberofAudioChannels = 0;
let has2Channels = false;
let has6Channels = false;
let has8Channels = false;
let lang2Channels = '';
let lang6Channels = '';
let lang8Channels = '';
let type2Channels = '';
let type6Channels = '';
let type8Channels = '';
let keepAudioIdx = -1;
// const keepIGuessAudioIdx = -1;
let encodeAudioIdx = -1;
let keepAudioStream = -1;
let encodeAudioStream = -1;
let originalAudio = '';
// Go through each stream in the file.
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Go through all audio streams and check if 2,6 & 8 channel tracks exist or not.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
numberofAudioChannels += 1;
if (file.ffProbeData.streams[i].channels === 2 && has2Channels === false) {
has2Channels = true;
lang2Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type2Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
if (file.ffProbeData.streams[i].channels === 6 && has6Channels === false) {
has6Channels = true;
lang6Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type6Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
if (file.ffProbeData.streams[i].channels === 8 && has8Channels === false) {
has8Channels = true;
lang8Channels = file.ffProbeData.streams[i].tags.language.toLowerCase();
type8Channels = file.ffProbeData.streams[i].codec_name.toLowerCase();
}
}
} catch (err) {
// Error
}
}
// Are we processing for 6 channels?
if (inputs.audio_channels === 6) {
audioIdx = -1;
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
audioIdx += 1;
if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') {
if (file.ffProbeData.streams[i].channels === 6) {
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') {
// response.infoLog += `Found 6 channel audio in proper language and codec, audio stream ${audioIdx}\n`;
if (keepAudioIdx === -1) {
keepAudioIdx = audioIdx;
keepAudioStream = i;
}
} else if (encodeAudioIdx === -1) {
// response.infoLog += `Found 6 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
encodeAudioIdx = audioIdx;
encodeAudioStream = i;
}
}
if (file.ffProbeData.streams[i].channels > 6) {
// response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
if (encodeAudioIdx === -1) {
encodeAudioIdx = audioIdx;
encodeAudioStream = i;
}
}
}
}
} catch (err) {
// Error
}
}
if (keepAudioIdx === -1 && encodeAudioIdx === -1) { // didn't find any 5.1 or better audio streams in proper language, defaulting to using 2 channels
// eslint-disable-next-line no-param-reassign
inputs.audio_channels = '2';
}
}
// Are we processing for 2 channels?
if (inputs.audio_channels === 2) {
audioIdx = -1;
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
audioIdx += 1;
if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') {
if (file.ffProbeData.streams[i].channels === 2) {
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'aac' || file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') {
// response.infoLog += `Found 2 channel audio in proper language and codec, audio stream ${audioIdx}\n`;
if (keepAudioIdx === -1) {
keepAudioIdx = audioIdx;
keepAudioStream = i;
}
} else if (encodeAudioIdx === -1) {
// response.infoLog += `Found 2 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
encodeAudioIdx = audioIdx;
encodeAudioStream = i;
}
} else if (encodeAudioIdx === -1) {
// response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`;
encodeAudioIdx = audioIdx;
encodeAudioStream = i;
}
}
// response.infoLog += `a ${audioIdx}. k ${keepAudioIdx}. e ${encodeAudioIdx}\n `;
}
} catch (err) {
// Error
}
}
}
let audioMessage = '';
// selecting channels to keep, only if 2 or 6 channels processed
if (keepAudioIdx !== -1) {
// keep audio, exclude everything else
if (numberofAudioChannels !== 1) {
convertAudio = true;
audioMessage += `keeping audio stream ${keepAudioIdx}.`;
audioOptions = `-map 0:a:${keepAudioIdx} -c:a copy `;
originalAudio += `${file.ffProbeData.streams[keepAudioStream].channels} channel ${file.ffProbeData.streams[keepAudioStream].codec_name} --> ${inputs.audio_channels} channel ac3`;
}
} else if (encodeAudioIdx !== -1) {
// encode this audio
convertAudio = true;
audioMessage += `encoding audio stream ${encodeAudioIdx}. `;
audioOptions = `-map 0:a:${encodeAudioIdx} -c:a ac3 -ac ${inputs.audio_channels} `; // 2 or 6 channels encoding
originalAudio += `${file.ffProbeData.streams[encodeAudioStream].channels} channel ${file.ffProbeData.streams[encodeAudioStream].codec_name} --> ${inputs.audio_channels} channel ac3`;
} else {
// do not encode audio
convertAudio = false;
audioMessage += 'no audio to encode.';
}
// test for whether the file needs to be processed - separate for video and audio convertAudio, convertVideo
if (convertAudio === false && convertVideo === false) { // if nothing to do, exit
response.infoLog += 'File is processed already, nothing to do';
response.processFile = false;
return response;
}
// Generate ffmpeg command line arguments in total
// few defaults
response.preset = ', -sn ';
if (convertVideo === true) {
// Set bitrateSettings variable using bitrate information calculated earlier.
bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k `
+ `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`;
if (willBeResized === true) {
extraArguments += '-filter:v scale=1280:-1 ';
}
if (os.platform() === 'darwin') {
videoOptions = '-map 0:v -c:v hevc_videotoolbox -profile main ';
}
if (os.platform() === 'win32') {
videoOptions = '-map 0:v -c:v hevc_qsv -load_plugin hevc_hw ';
}
}
response.preset += `${videoOptions} ${bitrateSettings} ${extraArguments} ${audioOptions} `;
let outputResolution = file.video_resolution;
if (willBeResized === true) {
outputResolution = '720p';
}
if (convertVideo === false) {
response.infoLog += `NOT converting video ${file.video_resolution}, ${file.video_codec_name}, bitrate = ${currentBitrate} \n`;
} else {
response.infoLog += 'Converting video, ';
if (willBeResized === false) { response.infoLog += 'NOT '; }
response.infoLog += `resizing. ${file.video_resolution}, ${file.video_codec_name} --> ${outputResolution}, hevc. bitrate = ${currentBitrate} --> ${targetBitrate}, multiplier ${bitRateMultiplier}. \n`;
}
if (convertAudio === true) {
response.infoLog += `Converting audio, ${audioMessage} ${originalAudio}. \n`;
} else {
response.infoLog += 'Not converting audio. \n';
}
response.infoLog += `2 channels - ${lang2Channels} ${type2Channels} \n`;
response.infoLog += `6 channels - ${lang6Channels} ${type6Channels} \n`;
response.infoLog += `8 channels - ${lang8Channels} ${type8Channels} `;
response.processFile = true;
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -1,3 +1,4 @@
/* eslint-disable */
//////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Author: JarBinks, Zachg99, Jeff47
@ -33,7 +34,7 @@
// If the source video is less than this rate the script will either:
// Copy the existing stream, if the codec is hevc
// Transcode the stream to hevc using 80% of the original streams bitrate
// It could probably be less but if the source is of low bitrate we don<EFBFBD>t want to compromise too much on the transcode
// It could probably be less but if the source is of low bitrate we don't want to compromise too much on the transcode
//
// If the source media bitrate is close, within 10%, of the target bitrate and the codec is hevc, it will copy instead of transcode to preserve quality
//
@ -52,7 +53,7 @@
// If the source audio is less than this rate the script will either:
// Copy the existing stream, if the codec is aac
// Transcode the stream to aac using 100% of the original streams bitrate
// It could probably be less but if the source is of low bitrate but, we don<EFBFBD>t want to compromise too much on the transcode
// It could probably be less but if the source is of low bitrate but, we don't want to compromise too much on the transcode
//
// Subtitles:
// All are removed?? (TODO: ensure this is correct and mention the flag to keep them if desired)
@ -760,9 +761,9 @@ function findMediaInfoItem(file, index) {
currMIOrder = file.mediaInfo.track[i].ID - 1;
}
if (currMIOrder == index|| currMIOrder == "0-" + index) {
return i;
}
if (currMIOrder == index|| currMIOrder == "0-" + index) {
return i;
}
}
return -1;
}

@ -23,7 +23,7 @@ function details() {
eng,und
\\nExample:\\n
eng,und,jap`,
eng,und,jpn`,
},
{
name: 'commentary',

@ -0,0 +1,220 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
// This is almost a line for line copy of Migz1FFMPEG
// https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js
// Seriously, all I did was make it work for converting things to h264 instead of hevc
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264',
Stage: 'Pre-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed.
Name: 'Smoove-Transcode to H264 using FFMPEG and NVENC ',
Type: 'Video',
Operation: 'Transcode',
Description: `Files not in H264 will be transcoded into H264 using Nvidia GPU with ffmpeg.
Settings are dependant on file bitrate
NVDEC & NVENC compatable GPU required.`,
Version: '1.00',
Link: `https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/
Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js`,
Tags: 'pre-processing,ffmpeg,video only,nvenc h264,configurable',
// Provide tags to categorise your plugin in the plugin browser.Tag options: h265,hevc,h264,nvenc h265,
// nvenc h264,video only,audio only,subtitle only,handbrake,ffmpeg
// radarr,sonarr,pre-processing,post-processing,configurable
Inputs: [
{
name: 'container',
tooltip: `Specify output container of file
\\n Ensure that all stream types you may have are supported by your chosen container.
\\n mkv is recommended.
\\nExample:\\n
mkv
\\nExample:\\n
mp4`,
},
{
name: 'force_conform',
tooltip: `Make the file conform to output containers requirements.
\\n Drop hdmv_pgs_subtitle/eia_608/subrip/timed_id3 for MP4.
\\n Drop data streams/mov_text/eia_608/timed_id3 for MKV.
\\n Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
],
};
};
module.exports.plugin = function plugin(file, librarySettings, inputs) {
const response = {
processFile: false,
infoLog: '',
handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding
FFmpegMode: true,
reQueueAfter: true,
// Leave as true. File will be re-qeued afterwards and pass through the plugin
// filter again to make sure it meets conditions.
};
// Check that inputs.container has been configured, else dump out
if (inputs.container === '') {
response.infoLog += 'Plugin has not been configured, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
response.container = `.${inputs.container}`;
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += 'File is not a video. \n';
return response;
}
let duration = '';
// Get duration of stream 0 and times it by 0.0166667 to get time in minutes
duration = file.ffProbeData.streams[0].duration * 0.0166667;
// Set up required variables.
let videoIdx = 0;
let extraArguments = '';
let bitrateSettings = '';
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
// Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/
// eslint-disable-next-line no-bitwise
const currentBitrate = ~~(file.file_size / (duration * 0.0075));
// For h.264, the target bitrate matches the current bitrate, since we're not reducing quality, just changing codec
// eslint-disable-next-line no-bitwise
const targetBitrate = ~~(file.file_size / (duration * 0.0075));
// Allow some leeway under and over the targetBitrate.
// eslint-disable-next-line no-bitwise
const minimumBitrate = ~~(targetBitrate * 0.7);
// eslint-disable-next-line no-bitwise
const maximumBitrate = ~~(targetBitrate * 1.3);
// This shouldn't be 0, for any reason, and if it is, you should get outta there.
if (targetBitrate === 0) {
response.processFile = false;
response.infoLog += 'Target bitrate could not be calculated. Skipping this plugin. \n';
return response;
}
// Check if force_conform option is checked.
// If so then check streams and add any extra parameters required to make file conform with output format.
if (inputs.force_conform === 'true') {
if (inputs.container.toLowerCase() === 'mkv') {
extraArguments += '-map -0:d ';
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (
file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'mov_text'
|| file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'eia_608'
|| file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'timed_id3'
) {
extraArguments += `-map -0:${i} `;
}
} catch (err) {
// Error
}
}
}
if (inputs.container.toLowerCase() === 'mp4') {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (
file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'hdmv_pgs_subtitle'
|| file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'eia_608'
|| file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'subrip'
|| file.ffProbeData.streams[i].codec_name
.toLowerCase() === 'timed_id3'
) {
extraArguments += `-map -0:${i} `;
}
} catch (err) {
// Error
}
}
}
}
// Go through each stream in the file
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Check if stream is video
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
// Check if the video stream is mjpeg/png, and removes it.
// These are embedded image streams which ffmpeg doesn't like to work with as a video stream
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'mjpeg'
|| file.ffProbeData.streams[i].codec_name.toLowerCase() === 'png') {
response.infoLog += 'File Contains mjpeg / png video streams, removing.';
extraArguments += `-map -v:${videoIdx} `;
}
// If video is h264, and container matches desired container, we don't need to do anything
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'h264' && file.container === inputs.container) {
response.processFile = false;
response.infoLog += `File is already H264 and in ${inputs.container} \n`;
return response;
}
// if video is h264, but container does NOT match desired container, do a remux
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'h264' && file.container !== inputs.container) {
response.processFile = true;
response.infoLog += `File is already H264 but file is not in ${inputs.container}. Remuxing \n`;
response.preset = `, -map 0 -c copy ${extraArguments}`;
return response;
}
// Increment videoIdx.
videoIdx += 1;
}
}
// Set bitrateSettings variable using bitrate information calulcated earlier.
bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k `
+ `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`;
// Print to infoLog information around file & bitrate settings.
response.infoLog += `Container for output selected as ${inputs.container}. \n`;
response.infoLog += `Current bitrate = ${currentBitrate} \n`;
response.infoLog += 'Bitrate settings: \n';
response.infoLog += `Target = ${targetBitrate} \n`;
response.infoLog += `Minimum = ${minimumBitrate} \n`;
response.infoLog += `Maximum = ${maximumBitrate} \n`;
// Codec will be checked so it can be transcoded correctly
if (file.video_codec_name === 'h263') {
response.preset = '-c:v h263_cuvid';
} else if (file.video_codec_name === 'hevc') {
response.preset = '';
} else if (file.video_codec_name === 'av1') {
response.preset = '';
} else if (file.video_codec_name === 'vp9') {
response.preset = '';
} else if (file.video_codec_name === 'mjpeg') {
response.preset = '-c:v mjpeg_cuvid';
} else if (file.video_codec_name === 'mpeg1') {
response.preset = '-c:v mpeg1_cuvid';
} else if (file.video_codec_name === 'mpeg2') {
response.preset = '-c:v mpeg2_cuvid';
} else if (file.video_codec_name === 'vc1') {
response.preset = '-c:v vc1_cuvid';
} else if (file.video_codec_name === 'vp8') {
response.preset = '-c:v vp8_cuvid';
}
response.preset += `,-map 0 -c:v h264_nvenc -preset fast -crf 23 -tune film ${bitrateSettings} `
+ `-c:a copy -c:s copy -max_muxing_queue_size 9999 -pix_fmt yuv420p ${extraArguments}`;
response.processFile = true;
response.infoLog += 'File is not h264. Transcoding. \n';
return response;
};

@ -0,0 +1,478 @@
/* eslint max-classes-per-file: ["error", 2] */
function details() {
return {
id: 'Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass',
Stage: 'Pre-processing',
Name: 'VP9 Encoding Match Bitrate 1 Pass System',
Type: 'Video',
Operation: 'Transcode',
Description: `Will run through linvpx-vp9 and follow the contrained quality contraints. Will also encode audio to
opus using libopus. Allows user-input on the desired constrained quality amount for each video resolution with
defaults if none are given.`,
Version: '1.00',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js',
Tags: 'pre-processing,ffmpeg,vp9',
Inputs: [
{
name: 'CQ_240p',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 32',
},
{
name: 'CG_360p',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 31',
},
{
name: 'CQ_480p',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 28',
},
{
name: 'CQ_720p',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 27',
},
{
name: 'CQ_1080p',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 26',
},
{
name: 'CQ_4KUHD',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 15',
},
{
name: 'CQ_8KUHD',
tooltip:
'The CQ number (recommended 15-35) for this resolution, default 15',
},
{
name: 'audio_language',
tooltip: `
Specify language tag/s here for the audio tracks you'd like to keep, recommended to keep "und" as this\\n
stands for undertermined, some files may not have the language specified. Must follow ISO-639-2 3 letter\\n
format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
eng,und
\\nExample:\\n
eng,und,jap`,
},
{
name: 'audio_commentary',
tooltip: `Specify if audio tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'subtitle_language',
tooltip: `Specify language tag/s here for the subtitle tracks you'd like to keep. Must follow ISO-639-2 3 \\n
letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
eng,jap`,
},
{
name: 'subtitle_commentary',
tooltip: `Specify if subtitle tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'remove_mjpeg',
tooltip: `Specify if mjpeg codecs should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
],
};
}
// #region Helper Classes/Modules
/**
* Handles logging in a standardised way.
*/
class Log {
constructor() {
this.entries = [];
}
/**
*
* @param {String} entry the log entry string
*/
Add(entry) {
this.entries.push(entry);
}
/**
*
* @param {String} entry the log entry string
*/
AddSuccess(entry) {
this.entries.push(`${entry}`);
}
/**
*
* @param {String} entry the log entry string
*/
AddError(entry) {
this.entries.push(`${entry}`);
}
/**
* Returns the log lines separated by new line delimiter.
*/
GetLogData() {
return this.entries.join('\n');
}
}
/**
* Handles the storage of FFmpeg configuration.
*/
class Configurator {
constructor(defaultOutputSettings = null) {
this.shouldProcess = false;
this.outputSettings = defaultOutputSettings || [];
this.inputSettings = [];
}
AddInputSetting(configuration) {
this.inputSettings.push(configuration);
}
AddOutputSetting(configuration) {
this.shouldProcess = true;
this.outputSettings.push(configuration);
}
ResetOutputSetting(configuration) {
this.shouldProcess = false;
this.outputSettings = configuration;
}
RemoveOutputSetting(configuration) {
const index = this.outputSettings.indexOf(configuration);
if (index === -1) return;
this.outputSettings.splice(index, 1);
}
RemoveAllConfigurationsBySearchString(search_string) {
for (let i = this.outputSettings.length - 1; i >= 0; i -= 1) {
if (this.outputSettings[i].includes(search_string)) {
this.outputSettings.splice(i, 1);
}
}
}
GetOutputSettings() {
return this.outputSettings.join(' ');
}
GetInputSettings() {
return this.inputSettings.join(' ');
}
}
/**
* Loops over the file streams and executes the given method on
* each stream when the matching codec_type is found.
* @param {Object} file the file.
* @param {string} type the typeo of stream.
* @param {function} method the method to call.
*/
function loopOverStreamsOfType(file, type, method) {
let id = 0;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === type) {
method(file.ffProbeData.streams[i], id);
id += 1;
}
}
}
function buildAudioConfiguration(inputs, file, logger) {
const configuration = new Configurator(['-c:a copy']);
let stream_count = 0;
let streams_removing = 0;
const languages = inputs.audio_language.split(',');
let opusFormat = false;
let mappingFamily = false;
loopOverStreamsOfType(file, 'audio', (stream, id) => {
stream_count += 1;
if (stream.codec_name !== 'opus' && !opusFormat) {
logger.AddError('Audio is not in proper codec, will format');
configuration.RemoveOutputSetting('-c:a copy');
configuration.AddOutputSetting('-c:a libopus');
opusFormat = true;
}
if (
(stream.channel_layout === '5.1(side)' || (stream.codec_name === 'eac3' && stream.channels === 6)) && opusFormat
) {
logger.AddSuccess(
`Determined audio to be ${stream.channel_layout}, adding mapping configuration for proper conversion`,
);
configuration.AddOutputSetting(
`-filter_complex "[0:a:${id}]channelmap=channel_layout=5.1"`,
);
if (!mappingFamily) {
configuration.AddOutputSetting('-mapping_family 1');
mappingFamily = true;
}
}
if (stream.channel_layout === '6.1(back)' && opusFormat) {
logger.AddSuccess(
`Determined audio to be ${stream.channel_layout}, adding mapping configuration for proper conversion`,
);
configuration.AddOutputSetting(
`-filter_complex "[0:a:${id}]channelmap=channel_layout=6.1"`,
);
if (!mappingFamily) {
configuration.AddOutputSetting('-mapping_family 1');
mappingFamily = true;
}
}
if (
'tags' in stream && 'title' in stream.tags && inputs.audio_commentary.toLowerCase() === 'true'
) {
if (
stream.tags.title.toLowerCase().includes('commentary')
|| stream.tags.title.toLowerCase().includes('description')
|| stream.tags.title.toLowerCase().includes('sdh')
) {
streams_removing += 1;
configuration.AddOutputSetting(`-map -0:a:${id}`);
logger.AddError(
`Removing Commentary or Description audio track: ${stream.tags.title}`,
);
return;
}
}
if ('tags' in stream) {
// Remove unwanted languages
if ('language' in stream.tags) {
if (languages.indexOf(stream.tags.language.toLowerCase()) === -1) {
configuration.AddOutputSetting(`-map -0:a:${id}`);
streams_removing += 1;
logger.AddError(
`Removing audio track in language ${stream.tags.language}`,
);
}
}
}
});
if (stream_count === streams_removing) {
logger.AddError(
'*** All audio tracks would have been removed, removing all delete entries',
);
configuration.RemoveAllConfigurationsBySearchString('-map -0');
}
if (!configuration.shouldProcess) {
logger.AddSuccess('No audio processing necessary');
}
return configuration;
}
function buildVideoConfiguration(inputs, file, logger) {
const configuration = new Configurator(['-map 0', '-map -0:d', '-c:v copy']);
loopOverStreamsOfType(file, 'video', (stream, id) => {
if (stream.codec_name === 'mjpeg') {
if (inputs.remove_mjpeg.toLowerCase() === 'true') {
logger.AddError('Removing mjpeg');
configuration.AddOutputSetting(`-map -0:v:${id}`);
} else {
configuration.AddOutputSetting(`-map -v:${id}`);
}
return;
}
if (stream.codec_name === 'vp9' && file.container === 'webm') {
logger.AddSuccess('File is in proper video format');
return;
}
if (stream.codec_name === 'vp9' && file.container !== 'webm') {
configuration.AddOutputSetting('-c:v copy');
logger.AddError(
'File is in proper codec but not write container. Will remux',
);
}
let speed = 1;
let targetQuality = 32;
let tileColumns = 0;
const threadCount = 64;
if (file.video_resolution === '240p') {
targetQuality = inputs.CQ_240p || 32;
tileColumns = 0;
speed = 1;
} else if (file.video_resolution === '360p' || file.video_resolution === '576p') {
targetQuality = inputs.CQ_360p || 31;
tileColumns = 1;
speed = 1;
} else if (file.video_resolution === '480p') {
targetQuality = inputs.CQ_480p || 28;
tileColumns = 1;
speed = 1;
} else if (file.video_resolution === '720p') {
targetQuality = inputs.CQ_720p || 27;
tileColumns = 2;
speed = 2;
} else if (file.video_resolution === '1080p') {
targetQuality = inputs.CQ_1080p || 26;
tileColumns = 2;
speed = 2;
} else if (
file.video_resolution === '1440p' || file.video_resolution === '2560p' || file.video_resolution === '4KUHD'
) {
targetQuality = inputs.CQ_4KUHD || 15;
tileColumns = 3;
speed = 2;
} else if (file.video_resolution === '8KUHD') {
targetQuality = inputs.CQ_8KUHD || 15;
tileColumns = 3;
speed = 2;
}
configuration.RemoveOutputSetting('-c:v copy');
configuration.AddOutputSetting(
`-pix_fmt yuv420p10le -c:v libvpx-vp9 -b:v 0 -crf ${targetQuality} -threads ${threadCount} -speed ${speed}
-quality good -static-thresh 0 -tile-columns ${tileColumns} -tile-rows 0 -frame-parallel 0 -row-mt 1
-aq-mode 0 -g 240`,
);
logger.AddError('Transcoding file to VP9');
});
if (!configuration.shouldProcess) {
logger.AddSuccess('No video processing necessary');
}
return configuration;
}
function buildSubtitleConfiguration(inputs, file, logger) {
const configuration = new Configurator(['-c:s copy']);
// webvtt
const languages = inputs.subtitle_language.split(',');
let webvttFormat = false;
// if (languages.length === 0) return configuration;
loopOverStreamsOfType(file, 'subtitle', (stream, id) => {
if (
stream.codec_name === 'hdmv_pgs_subtitle'
|| stream.codec_name === 'eia_608'
|| stream.codec_name === 'dvd_subtitle'
) {
logger.AddError(
`Removing subtitle in invalid codec ${stream.codec_name}`,
);
configuration.AddOutputSetting(`-map -0:s:${id}`);
return;
}
if ('tags' in stream) {
// Remove unwated languages
if ('language' in stream.tags) {
if (languages.indexOf(stream.tags.language.toLowerCase()) === -1) {
configuration.AddOutputSetting(`-map -0:s:${id}`);
logger.AddError(
`Removing subtitle in language ${stream.tags.language}`,
);
return;
}
}
// Remove commentary subtitles
if (
'title' in stream.tags
&& inputs.subtitle_commentary.toLowerCase() === 'true'
) {
if (
stream.tags.title.toLowerCase().includes('commentary')
|| stream.tags.title.toLowerCase().includes('description')
|| stream.tags.title.toLowerCase().includes('sdh')
) {
configuration.AddOutputSetting(`-map -0:s:${id}`);
logger.AddError(
`Removing Commentary or Description subtitle: ${stream.tags.title}`,
);
return;
}
}
}
if (stream.codec_name !== 'webvtt' && !webvttFormat) {
logger.AddError('Formatting subtitles to webvtt format');
configuration.RemoveOutputSetting('-c:s copy');
configuration.AddOutputSetting('-c:s webvtt');
webvttFormat = true;
}
});
if (!configuration.shouldProcess) {
logger.AddSuccess('No subtitle processing necessary');
}
return configuration;
}
function plugin(file, librarySettings, inputs) {
// Must return this object
const response = {
container: '.webm',
FFmpegMode: true,
handBrakeMode: false,
infoLog: '',
processFile: false,
preset: '',
reQueueAfter: true,
};
const logger = new Log();
const audioSettings = buildAudioConfiguration(inputs, file, logger);
const videoSettings = buildVideoConfiguration(inputs, file, logger);
const subtitleSettings = buildSubtitleConfiguration(inputs, file, logger);
response.processFile = audioSettings.shouldProcess
|| videoSettings.shouldProcess
|| subtitleSettings.shouldProcess;
if (!response.processFile) {
logger.AddSuccess('No need to process file');
}
response.preset = `${videoSettings.GetInputSettings()},${videoSettings.GetOutputSettings()}
${audioSettings.GetOutputSettings()} ${subtitleSettings.GetOutputSettings()}`;
response.infoLog += logger.GetLogData();
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -48,7 +48,7 @@ function plugin(file) {
try {
if (
file.ffProbeData.streams[i].channels >= 6 &&
file.ffProbeData.streams[i].channels == 6 &&
file.ffProbeData.streams[i].codec_name !== "ac3" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio"
) {
@ -67,10 +67,10 @@ function plugin(file) {
response.handBrakeMode = false;
response.FFmpegMode = true;
response.reQueueAfter = true;
response.infoLog += "☒ File has surround audio which is not in ac3! \n";
response.infoLog += "☒ File has surround audio which is NOT in ac3! \n";
return response;
} else {
response.infoLog += "☑ All surround audio streams are in aac! \n";
response.infoLog += "☑ All surround audio streams are in ac3! \n";
}
response.infoLog += "☑File meets conditions! \n";

@ -123,7 +123,7 @@ function plugin(file, librarySettings, inputs) {
response.preset =
", -map_metadata -1 -map 0:V " +
subMap +
" -map 0:a -c:v libx264 -preset medium -c:a aac -strict -2 " +
" -map 0:a -c:v libx264 -preset " + preset + " -c:a aac -strict -2 " +
subType;
response.reQueueAfter = true;
response.processFile = true;

@ -64,7 +64,7 @@ function plugin(file, librarySettings, inputs) {
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
const currStream = file.ffProbeData.streams[i];
if (currStream.tags.COPYRIGHT) {
if (currStream.codec_type.toLowerCase() === 'audio' && currStream.codec_name === inputs.output_codec) {
if (currStream.tags.COPYRIGHT === 'henk_asac') {
killPlugin = true;
}

@ -0,0 +1,272 @@
/* eslint-disable no-await-in-loop */
module.exports.dependencies = ['axios', '@cospired/i18n-iso-languages', 'path'];
const details = () => ({
id: 'Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng',
Stage: 'Pre-processing',
Name: 'Remove all langs except native and English',
Type: 'Audio',
Operation: 'Transcode',
Description: `This plugin will remove all language audio tracks except the 'native'
(requires TMDB api key) and English.
'Native' languages are the ones that are listed on imdb. It does an API call to
Radarr, Sonarr to check if the movie/series exists and grabs the IMDB id. As a last resort it
falls back to the IMDB id in the filename.`,
Version: '1.00',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js',
Tags: 'pre-processing,configurable',
Inputs: [
{
name: 'user_langs',
tooltip: 'Input a comma separated list of ISO-639-2 languages. It will still keep English and undefined tracks.'
+ '(https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 639-2 column)'
+ '\\nExample:\\n'
+ 'nld,nor',
},
{
name: 'priority',
tooltip: 'Priority for either Radarr or Sonarr. Leaving it empty defaults to Radarr first.'
+ '\\nExample:\\n'
+ 'sonarr',
},
{
name: 'api_key',
tooltip: 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)',
},
{
name: 'radarr_api_key',
tooltip: 'Input your Radarr api key here.',
},
{
name: 'radarr_url',
tooltip: 'Input your Radarr url here. (Without http://). Do include the port.'
+ '\\nExample:\\n'
+ '192.168.1.2:7878',
},
{
name: 'sonarr_api_key',
tooltip: 'Input your Sonarr api key here.',
},
{
name: 'sonarr_url',
tooltip: 'Input your Sonarr url here. (Without http://). Do include the port.'
+ '\\nExample:\\n'
+ '192.168.1.2:8989',
},
],
});
const response = {
processFile: false,
preset: ', -map 0 ',
container: '.',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: '',
};
const processStreams = (result, file, user_langs) => {
// eslint-disable-next-line global-require,import/no-unresolved
const languages = require('@cospired/i18n-iso-languages');
const tracks = {
keep: [],
remove: [],
remLangs: '',
};
let streamIndex = 0;
const langsTemp = result.original_language;
let langs = [];
if (Array.isArray(langsTemp)) {
// For loop because I thought some imdb stuff returns multiple languages
// Translates 'en' to 'eng', because imdb uses a different format compared to ffmpeg
for (let i = 0; i < langsTemp.length; i += 1) {
langs.push(languages.alpha2ToAlpha3B(langsTemp));
}
} else {
langs.push(languages.alpha2ToAlpha3B(langsTemp));
}
if (user_langs) {
langs = langs.concat(user_langs);
}
if (!langs.includes('eng')) langs.push('eng');
if (!langs.includes('und')) langs.push('und');
response.infoLog += 'Keeping languages: ';
// Print languages to UI
langs.forEach((l) => {
response.infoLog += `${languages.getName(l, 'en')}, `;
});
response.infoLog = `${response.infoLog.slice(0, -2)}\n`;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
const stream = file.ffProbeData.streams[i];
if (stream.codec_type === 'audio') {
if (!stream.tags) {
response.infoLog += `☒No tags found on audio track ${streamIndex}. Keeping it. \n`;
tracks.keep.push(streamIndex);
streamIndex += 1;
// eslint-disable-next-line no-continue
continue;
}
if (stream.tags.language) {
if (langs.includes(stream.tags.language)) {
tracks.keep.push(streamIndex);
} else {
tracks.remove.push(streamIndex);
response.preset += `-map -0:a:${streamIndex} `;
tracks.remLangs += `${languages.getName(stream.tags.language, 'en')}, `;
}
streamIndex += 1;
} else {
response.infoLog += `☒No language tag found on audio track ${streamIndex}. Keeping it. \n`;
}
}
}
response.preset += ' -c copy -max_muxing_queue_size 9999';
return tracks;
};
const tmdbApi = async (filename, api_key, axios) => {
let fileName;
// If filename begins with tt, it's already an imdb id
if (filename) {
if (filename.substr(0, 2) === 'tt') {
fileName = filename;
} else {
const idRegex = /(tt\d{7,8})/;
const fileMatch = filename.match(idRegex);
// eslint-disable-next-line prefer-destructuring
if (fileMatch) fileName = fileMatch[1];
}
}
if (fileName) {
const result = await axios.get(`https://api.themoviedb.org/3/find/${fileName}?api_key=`
+ `${api_key}&language=en-US&external_source=imdb_id`)
.then((resp) => (resp.data.movie_results.length > 0 ? resp.data.movie_results[0] : resp.data.tv_results[0]));
if (!result) {
response.infoLog += '☒No IMDB result was found. \n';
}
return result;
}
return null;
};
// eslint-disable-next-line consistent-return
const parseArrResponse = async (body, filePath, arr) => {
// eslint-disable-next-line default-case
switch (arr) {
case 'radarr':
// filePath = file
for (let i = 0; i < body.length; i += 1) {
if (body[i].movieFile) {
if (body[i].movieFile.relativePath) {
if (body[i].movieFile.relativePath === filePath) {
return body[i];
}
}
}
}
break;
case 'sonarr':
// filePath = directory the file is in
// eslint-disable-next-line global-require,import/no-unresolved
const path = require('path');
for (let i = 0; i < body.length; i += 1) {
if (body[i].path) {
if (path.basename(body[i].path) === path.basename(path.dirname(filePath))) {
return body[i];
}
}
}
}
};
const plugin = async (file, librarySettings, inputs) => {
// eslint-disable-next-line global-require,import/no-unresolved
const axios = require('axios').default;
response.container = `.${file.container}`;
let prio = ['radarr', 'sonarr'];
let radarrResult = null;
let sonarrResult = null;
let tmdbResult = null;
if (inputs.priority) {
if (inputs.priority === 'sonarr') {
prio = ['sonarr', 'radarr'];
}
}
for (let i = 0; i < prio.length; i += 1) {
let imdbId;
// eslint-disable-next-line default-case
switch (prio[i]) {
case 'radarr':
if (tmdbResult) break;
if (inputs.radarr_api_key) {
radarrResult = await parseArrResponse(
await axios.get(`http://${inputs.radarr_url}/api/v3/movie?apiKey=${inputs.radarr_api_key}`)
.then((resp) => resp.data),
file.meta.FileName, 'radarr',
);
if (radarrResult) {
imdbId = radarrResult.imdbId;
response.infoLog += `Grabbed ID (${imdbId}) from Radarr \n`;
} else {
response.infoLog += 'Couldn\'t grab ID from Radarr/Sonarr, grabbing it from file name \n';
imdbId = file.meta.FileName;
}
tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios);
}
break;
case 'sonarr':
if (tmdbResult) break;
if (inputs.sonarr_api_key) {
sonarrResult = await parseArrResponse(
await axios.get(`http://${inputs.sonarr_url}/api/series?apikey=${inputs.sonarr_api_key}`)
.then((resp) => resp.data),
file.meta.Directory, 'sonarr',
);
if (sonarrResult) {
imdbId = sonarrResult.imdbId;
response.infoLog += `Grabbed ID (${imdbId}) from Sonarr \n`;
} else {
response.infoLog += 'Couldn\'t grab ID from Radarr/Sonarr, grabbing it from file name \n';
imdbId = file.meta.FileName;
}
tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios);
}
}
}
if (tmdbResult) {
const tracks = processStreams(tmdbResult, file, inputs.user_langs ? inputs.user_langs.split(',') : '');
if (tracks.remove.length > 0) {
if (tracks.keep.length > 0) {
response.infoLog += `☑Removing tracks with languages: ${tracks.remLangs.slice(0, -2)}. \n`;
response.processFile = true;
response.infoLog += '\n';
} else {
response.infoLog += '☒Cancelling plugin otherwise all audio tracks would be removed. \n';
}
} else {
response.infoLog += '☒No audio tracks to be removed. \n';
}
} else {
response.infoLog += '☒Couldn\'t find the IMDB id of this file. Skipping. \n';
}
return response;
};
module.exports.details = details;
module.exports.plugin = plugin;

@ -61,7 +61,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs, otherArgu
}
response.infoLog += 'Found subs to extract!\n';
let command = '-y,';
let command = '-y <io>';
for (let i = 0; i < subsArr.length; i += 1) {
const subStream = subsArr[i];
let lang = '';
@ -102,7 +102,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs, otherArgu
}
}
if (command === '-y,') {
if (command === '-y <io>') {
response.infoLog += 'All subs already extracted!\n';
if (inputs.remove_subs === 'no') {
response.processFile = false;

@ -0,0 +1,190 @@
function details() {
return {
id: 'Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable',
Stage: 'Pre-processing',
Name: 'Tiered FFMPEG CPU CRF Based Configurable',
Type: 'Video',
Operation: 'Transcode',
Description: `[Contains built-in filter] This plugin uses different CRF values depending on resolution,
the CRF value is configurable per resolution.
FFmpeg Preset can be configured, uses slow by default.
If files are not in hevc they will be transcoded.
The output container is mkv. \n\n`,
Version: '1.00',
Link:
'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/'
+ ' Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js',
Tags: 'pre-processing,ffmpeg,video only,h265,configurable',
Inputs: [
{
name: 'sdCRF',
tooltip: `Enter the CRF value you want for 480p and 576p content.
\n Defaults to 20 (0-51, lower = higher quality, bigger file)
\\nExample:\\n
19`,
},
{
name: 'hdCRF',
tooltip: `Enter the CRF value you want for 720p content.
\n Defaults to 22 (0-51, lower = higher quality, bigger file)
\\nExample:\\n
21`,
},
{
name: 'fullhdCRF',
tooltip: `Enter the CRF value you want for 1080p content.
\n Defaults to 24 (0-51, lower = higher quality, bigger file)
\\nExample:\\n
23`,
},
{
name: 'uhdCRF',
tooltip: `Enter the CRF value you want for 4K/UHD/2160p content.
\n Defaults to 28 (0-51, lower = higher quality, bigger file)
\\nExample:\\n
26`,
},
{
name: 'bframe',
tooltip: `Specify amount of b-frames to use, 0-16, defaults to 8.
\\nExample:\\n
8`,
},
{
name: 'ffmpegPreset',
tooltip: `Enter the ffmpeg preset you want, leave blank for default (slow)
\\nExample:\\n
slow
\\nExample:\\n
medium
\\nExample:\\n
fast
\\nExample:\\n
veryfast`,
},
{
name: 'sdDisabled',
tooltip: `Input "true" if you want to skip SD (480p and 576p) files
\\nExample:\\n
true`,
},
{
name: 'uhdDisabled',
tooltip: `Input "true" if you want to skip 4k (UHD) files
\\nExample:\\n
true`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
let crf;
// default values that will be returned
const response = {
processFile: true,
preset: '',
container: '.mkv',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
};
// check if the file is a video, if not the function will be stopped immediately
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += '☒File is not a video! \n';
return response;
}
response.infoLog += '☑File is a video! \n';
// check if the file is SD and sdDisable is enabled
// skip this plugin if so
if (['480p', '576p'].includes(file.video_resolution) && inputs.sdDisabled) {
response.processFile = false;
response.infoLog += '☒File is SD, not processing\n';
return response;
}
// check if the file is 4k and 4kDisable is enabled
// skip this plugin if so
if (file.video_resolution === '4KUHD' && inputs.uhdDisabled) {
response.processFile = false;
response.infoLog += '☒File is 4k/UHD, not processing\n';
return response;
}
// check if the file is already hevc
// it will not be transcoded if true and the plugin will be stopped immediately
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'hevc') {
response.processFile = false;
response.infoLog += '☑File is already in hevc! \n';
return response;
}
}
// if we made it to this point it is safe to assume there is no hevc stream
response.infoLog += '☒File is not hevc!\n';
// set sane input defaults if not configured
const sdCRF = inputs.sdCRF ? inputs.sdCRF : 20;
const hdCRF = inputs.hdCRF ? inputs.hdCRF : 22;
const fullhdCRF = inputs.fullhdCRF ? inputs.fullhdCRF : 24;
const uhdCRF = inputs.uhdCRF ? inputs.uhdCRF : 28;
const bframe = inputs.bframe ? inputs.bframe : 8;
// set preset to slow if not configured
let ffmpegPreset = 'slow';
if (!inputs.ffmpegPreset) {
response.infoLog += '☑Preset not set, defaulting to slow\n';
} else {
ffmpegPreset = `${inputs.ffmpegPreset}`;
response.infoLog += `☑Preset set as ${inputs.ffmpegPreset}\n`;
}
// set crf by resolution
switch (file.video_resolution) {
case '480p':
case '576p':
crf = sdCRF;
break;
case '720p':
crf = hdCRF;
break;
case '1080p':
crf = fullhdCRF;
break;
case '4KUHD':
crf = uhdCRF;
break;
default:
response.infoLog += 'Could for some reason not detect resolution, plugin will not proceed. \n';
response.processFile = false;
return response;
}
// encoding settings
response.preset += `,-map 0 -dn -c:v libx265 -preset ${ffmpegPreset}`
+ ` -x265-params crf=${crf}:bframes=${bframe}:rc-lookahead=32:ref=6:b-intra=1:aq-mode=3`
+ ' -a53cc 0 -c:a copy -c:s copy -max_muxing_queue_size 9999';
response.infoLog += `☑File is ${file.video_resolution}, using CRF value of ${crf}!\n`;
response.infoLog += 'File is being transcoded!\n';
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -186,7 +186,7 @@ module.exports.plugin = function plugin (file, librarySettings, inputs) {
//codec will be checked so it can be transcoded correctly
if (file.video_resolution === '480p' || file.video_resolution === '576p') {
cqvinuse = `${inputs.sdCQV}`
response.preset += `,${map} -dn -c:v hevc_nvenc -pix_fmt p010le -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.sdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
response.preset += `,${map} -dn -c:v hevc_nvenc-rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.sdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
transcode = 1
}
@ -194,21 +194,21 @@ module.exports.plugin = function plugin (file, librarySettings, inputs) {
//codec will be checked so it can be transcoded correctly
if (file.video_resolution === '720p') {
cqvinuse = `${inputs.hdCQV}`
response.preset += `,${map} -dn -c:v hevc_nvenc -pix_fmt p010le -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.hdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
response.preset += `,${map} -dn -c:v hevc_nvenc -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.hdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
transcode = 1
}
//file will be encoded if the resolution is 1080p
//codec will be checked so it can be transcoded correctly
if (file.video_resolution === '1080p') {
cqvinuse = `${inputs.fullhdCQV}`
response.preset += `,${map} -dn -c:v hevc_nvenc -pix_fmt p010le -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.fullhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
response.preset += `,${map} -dn -c:v hevc_nvenc -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.fullhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
transcode = 1
}
//file will be encoded if the resolution is 4K
//codec will be checked so it can be transcoded correctly
if (file.video_resolution === '4KUHD') {
cqvinuse = `${inputs.uhdCQV}`
response.preset += `,${map} -dn -c:v hevc_nvenc -pix_fmt p010le -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.uhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
response.preset += `,${map} -dn -c:v hevc_nvenc -rc vbr_hq -b:v 0 -preset ${ffmpeg_preset} -cq ${inputs.uhdCQV} -rc-lookahead 32 -bf ${inputs.bframe} -a53cc 0 -c:a copy ${subcli}${maxmux}`
transcode = 1
}
//check if the file is eligible for transcoding

@ -1,62 +1,51 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_x7ac_Remove_Closed_Captions",
Stage: "Pre-processing",
Name: "Remove closed captions",
Type: "Video",
Operation: "Remux",
id: 'Tdarr_Plugin_x7ac_Remove_Closed_Captions',
Stage: 'Pre-processing',
Name: 'Remove burned closed captions',
Type: 'Video',
Operation: 'Remux',
Description:
"[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed.",
Version: "1.00",
'[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed from streams.',
Version: '1.01',
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js",
Tags: "pre-processing,ffmpeg,subtitle only",
'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js',
Tags: 'pre-processing,ffmpeg,subtitle only',
};
}
function plugin(file) {
//Must return this object
var response = {
const response = {
processFile: false,
preset: "",
container: ".mp4",
// eslint-disable-next-line no-useless-escape
preset: ',-map 0 -codec copy -bsf:v \"filter_units=remove_types=6\"',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "",
infoLog: '',
};
if (file.fileMedium !== "video") {
console.log("File is not video");
response.infoLog += "☒File is not video \n";
response.processFile = false;
if (file.fileMedium !== 'video') {
response.infoLog += '☒File is not video \n';
return response;
} else {
if (file.hasClosedCaptions === true) {
response = {
processFile: true,
preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"',
container: "." + file.container,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "☒This file has closed captions \n",
};
return response;
} else {
response.infoLog +=
"☑Closed captions have not been detected on this file \n";
response.processFile = false;
return response;
}
}
}
// Check if Closed Captions are set at file level
if (file.hasClosedCaptions) {
response.processFile = true;
response.infoLog += '☒This file has closed captions \n';
return response;
}
// If not, check for Closed Captions in the streams
const { streams } = file.ffProbeData;
streams.forEach((stream) => {
if (stream.closed_captions) {
response.processFile = true;
}
});
response.infoLog += response.processFile ? '☒This file has burnt closed captions \n'
: '☑Closed captions have not been detected on this file \n';
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -1,9 +1,5 @@
/* eslint-disable */
module.exports.dependencies = [
'fs-extra',
];
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_z18s_rename_files_based_on_codec",
@ -11,7 +7,7 @@ module.exports.details = function details() {
Name: "Rename based on codec",
Type: "Video",
Operation: "",
Description: `[Contains built-in filter]This plugin renames 264 files to 265 or vice versa depending on codec. \n\n`,
Description: `[Contains built-in filter] If the filename contains '264' or '265', this plugin renames 264 files to 265 or vice versa depending on codec. \n\n`,
Version: "1.00",
Link: "",
Tags: "post-processing",
@ -21,15 +17,6 @@ module.exports.details = function details() {
module.exports.plugin = function plugin(file, librarySettings, inputs) {
try {
var fs = require("fs");
var path = require("path");
if (fs.existsSync(path.join(process.cwd(), "/npm"))) {
var rootModules = path.join(process.cwd(), "/npm/node_modules/");
} else {
var rootModules = "";
}
var fsextra = require(rootModules + "fs-extra");
var fileNameOld = file._id;
if (
@ -39,6 +26,15 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
file._id = file._id.replace("264", "265");
file.file = file.file.replace("264", "265");
}
//added handling for files with AVC in the name instead of h264/x264
if (
file.ffProbeData.streams[0].codec_name == "hevc" &&
file._id.includes("AVC")
) {
file._id = file._id.replace("AVC", "HEVC");
file.file = file.file.replace("AVC", "HEVC");
}
if (
file.ffProbeData.streams[0].codec_name == "h264" &&
@ -57,7 +53,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
}
if (fileNameOld != file._id) {
fsextra.moveSync(fileNameOld, file._id, {
fs.renameSync(fileNameOld, file._id, {
overwrite: true,
});

@ -1,7 +1,3 @@
module.exports.dependencies = [
'fs-extra',
];
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution',
@ -20,17 +16,6 @@ module.exports.plugin = function plugin(file) {
try {
// eslint-disable-next-line global-require
const fs = require('fs');
// eslint-disable-next-line global-require
const path = require('path');
let rootModules;
if (fs.existsSync(path.join(process.cwd(), '/npm'))) {
rootModules = path.join(process.cwd(), '/npm/node_modules/');
} else {
rootModules = '';
}
// eslint-disable-next-line global-require,import/no-dynamic-require
const fsextra = require(`${rootModules}fs-extra`);
const fileNameOld = file._id;
const resolutions = {
@ -133,7 +118,7 @@ module.exports.plugin = function plugin(file) {
file.file = fileName;
if (fileNameOld !== file._id) {
fsextra.moveSync(fileNameOld, file._id, {
fs.renameSync(fileNameOld, file._id, {
overwrite: true,
});

@ -6,7 +6,7 @@ module.exports.dependencies = [
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_aaaa_Pre_Proc_Example',
Stage: 'Pre-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed.
Stage: 'Pre-processing', // Pre-processing or Post-processing. Determines when the plugin will be executed.
Name: 'No title meta data ',
Type: 'Video',
Operation: 'Transcode',

@ -0,0 +1,37 @@
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_a9he_New_file_size_check',
Stage: 'Pre-processing',
Name: 'New file size check',
Type: 'Video',
Operation: 'Transcode',
Description: 'Give an error if new file is larger than the original \n\n',
Version: '1.00',
Link: '',
Tags: '',
};
};
module.exports.plugin = function plugin(file, librarySettings, inputs, otherArguments) {
// Must return this object at some point in the function else plugin will fail.
const response = {
processFile: false,
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
};
const newSize = file.file_size;
const oldSize = otherArguments.originalLibraryFile.file_size;
if (newSize > oldSize) {
// Item will be errored in UI
throw new Error(`Error! New file has size ${newSize} which is larger than original file ${oldSize}`);
} else if (newSize < oldSize) {
response.infoLog += `New file has size ${newSize} which is smaller than original file ${oldSize}`;
}
// if file sizes are exactly the same then file has not been transcoded yet
return response;
};

@ -0,0 +1,35 @@
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_bbbc_Filter_Example',
Stage: 'Pre-processing',
Name: 'Filter resolutions',
Type: 'Video',
Operation: 'Filter',
Description: 'This plugin prevents processing files with specified resolutions \n\n',
Version: '1.00',
Link: '',
Tags: '',
};
};
module.exports.plugin = function plugin(file) {
const response = {
processFile: true,
infoLog: '',
};
const resolutionsToSkip = [
'1080p',
'4KUHD',
];
for (let i = 0; i < resolutionsToSkip.length; i += 1) {
if (file.video_resolution === resolutionsToSkip[i]) {
response.processFile = false;
response.infoLog += `Filter preventing processing. File has resolution ${resolutionsToSkip[i]}`;
break;
}
}
return response;
};

24
package-lock.json generated

@ -816,9 +816,9 @@
}
},
"glob-parent": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
@ -861,9 +861,9 @@
"dev": true
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"ignore": {
@ -1089,9 +1089,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lru-cache": {
@ -1282,9 +1282,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"path-type": {

@ -12,8 +12,8 @@
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint Community methods --ext js",
"lint:fix": "eslint Community methods --ext js --fix"
"lint": "eslint Community methods examples --ext js",
"lint:fix": "eslint Community methods examples --ext js --fix"
},
"repository": {
"type": "git",

Loading…
Cancel
Save