Merge branch 'master' into JB69Plugin

make-only-subtitle-default
Zach Gelnett 5 years ago committed by GitHub
commit 2fce8abb1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,30 @@
{
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": [
"airbnb-base"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"class-methods-use-this": 0,
"no-case-declarations": 0,
"camelcase": 0,
"jsx-a11y/click-events-have-key-events": 0,
"no-underscore-dangle": ["error", { "allow": ["_id"] }],
"max-len": [
"error",
{
"code": 120
}
]
}
}

@ -0,0 +1,25 @@
name: Node.js CI
on:
push:
branches: ['**']
pull_request:
branches: ['**']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm i
- run: npm run lint

10
.gitignore vendored

@ -0,0 +1,10 @@
/node_modules
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### VS Code ###
.vscode/

@ -1,4 +0,0 @@
node_modules/
.github/
# npx prettier@2.0.5 . --write

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_075a_FFMPEG_HEVC_Generic",
@ -41,7 +42,7 @@ function plugin(file) {
}
response.processFile = true;
response.preset = `,-map 0:v -map 0:a -map 0:s? -map 0:d? -c copy -c:v:0 libx265 `;
response.preset = `,-map 0:v -map 0:a -map 0:s? -map 0:d? -c copy -c:v:0 libx265 -max_muxing_queue_size 9999`;
response.container = ".mkv";
response.handBrakeMode = false;
response.FFmpegMode = true;

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_075a_Transcode_Customisable",
@ -5,7 +6,7 @@ module.exports.details = function details() {
Name: "Video Transcode Customisable",
Type: "",
Operation: "Transcode",
Description: `[TESTING][Contains built-in filter] Specify codec filter and transcode arguments for HandBrake or FFmpeg \n\n`,
Description: `[Contains built-in filter] Specify codec filter and transcode arguments for HandBrake or FFmpeg \n\n`,
Version: "1.00",
Link: "",
Tags: "pre-processing,handbrake,ffmpeg,configurable",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only",
@ -41,7 +42,7 @@ function plugin(file) {
}
response.processFile = true;
response.preset = `,-map 0:v -map 0:a -c copy -c:v:0 libx265`;
response.preset = `,-map 0:v -map 0:a -c copy -c:v:0 libx265 -max_muxing_queue_size 9999`;
response.container = ".mkv";
response.handBrakeMode = false;
response.FFmpegMode = true;

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20",

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_076a_re_order_audio_streams",
@ -5,7 +6,7 @@ module.exports.details = function details() {
Name: "Re-order audio streams",
Type: "",
Operation: "Transcode",
Description: `[TESTING][Contains built-in filter] Specify a language tag for Tdarr to try and put as 1st audio track \n\n`,
Description: `[Contains built-in filter] Specify a language tag for Tdarr to try and put as 1st audio track \n\n`,
Version: "1.00",
Link: "",
Tags: "pre-processing,audio only,ffmpeg,configurable",

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_076b_re_order_subtitle_streams",
@ -5,7 +6,7 @@ module.exports.details = function details() {
Name: "Re-order subtitle streams",
Type: "",
Operation: "Transcode",
Description: `[TESTING][Contains built-in filter] Specify a language tag for Tdarr to try and put as 1st subtitle track \n\n`,
Description: `[Contains built-in filter] Specify a language tag for Tdarr to try and put as 1st subtitle track \n\n`,
Version: "1.00",
Link: "",
Tags: "pre-processing,subtitle only,ffmpeg,configurable",

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable",

@ -0,0 +1,78 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_078d_Output_embedded_subs_to_SRT_and_remove",
Stage: "Pre-processing",
Name: "Output embedded subs to SRT and remove",
Type: "Video",
Operation: "Transcode",
Description: `This plugin outputs embedded subs to SRT and then removes them \n\n`,
Version: "1.00",
Link: "",
Tags: "ffmpeg",
};
};
module.exports.plugin = function plugin(file, librarySettings, inputs, otherArguments) {
//Must return this object at some point in the function else plugin will fail.
let response = {
processFile: false,
preset: "",
container: "",
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: true,
infoLog: "",
};
const ffmpegPath = otherArguments.ffmpegPath
const exec = require("child_process").exec;
let subsArr = file.ffProbeData.streams.filter(row => row.codec_name === 'subrip')
if (subsArr.length === 0) {
response.infoLog += "No subs in file to extract!";
return response
}
let subStream = subsArr[0]
let lang = ''
if (subStream.tags) {
lang = subStream.tags.language
}
const { originalLibraryFile } = otherArguments;
let subsFile = '';
// for Tdarr V2 (2.00.05+)
if (originalLibraryFile && originalLibraryFile.file) {
subsFile = originalLibraryFile.file;
} else {
// for Tdarr V1
subsFile = file.file;
}
subsFile = subsFile.split('.');
subsFile[subsFile.length - 2] += `.${lang}`;
subsFile[subsFile.length - 1] = 'srt';
subsFile = subsFile.join('.');
let index = subStream.index
let command = `${ffmpegPath} -i "${file.file}" -map 0:${index} "${subsFile}"`
exec(command);
response = {
processFile: true,
preset: `, -map 0 -map -0:${index} -c copy`,
container: "." + file.container,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "Found sub to extract!",
};
return response;
};

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_43az_add_to_radarr",
@ -5,7 +6,7 @@ module.exports.details = function details() {
Name: "Add movie to Radarr after processing",
Type: "Video",
Operation: "",
Description: `[TESTING]Add movie to Radarr after processing \n\n`,
Description: `Add movie to Radarr after processing \n\n`,
Version: "1.00",
Link: "",
Tags: "3rd party,post-processing,configurable",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only",
@ -140,7 +141,6 @@ function plugin(file,librarySettings,inputs,otherArguments) {
return response;
}
// How much does HVEC compress the raw stream?
var compressionFactor = 0.07;
if ( ! isNaN(Number(inputs.compressionFactor)) ) {
@ -176,7 +176,7 @@ function plugin(file,librarySettings,inputs,otherArguments) {
// -------------------------------- METADATA UPDATES --------------------------------
// If there is no _STATISTICS_WRITING_DATE_UTC-eng field, then we need to run mkvpropedit and
// rerun mediainfo to load the stats.
if (file.ffProbeData.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] === undefined ) {
if (file.ffProbeData.streams[0].tags === undefined || file.ffProbeData.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] === undefined ) {
response.infoLog += "☑Track statistics are missing.\n";
updateTrackStats(file);
getMediaInfo(file);
@ -200,20 +200,18 @@ function plugin(file,librarySettings,inputs,otherArguments) {
updateTrackStats(file);
getMediaInfo(file);
if ( isNaN(MediaInfo.videoBR) || isNaN(MediaInfo.videoBitDepth) ) {
response.infoLog += "videoBR or videoBitDepth still NaN, giving up.\n";
throw ("MediaInfo.videoBR or videoBitDepth still NaN, giving up.");
response.infoLog += "videoBR or videoBitDepth still NaN, using default.\n";
}
}
// If the overall bitrate is less than the videoBR, then something is wacky.
if ( MediaInfo.videoBR > MediaInfo.overallBR ) {
response.infoLog += `videoBR (${MediaInfo.videoBR} was greater than overallBR (${MediaInfo.overallBR}),
response.infoLog += `videoBR (${MediaInfo.videoBR} was greater than overallBR (${MediaInfo.overallBR}),
which is impossible. Updating stats.\n`;
updateTrackStats(file);
getMediaInfo(file);
if ( MediaInfo.videoBR > MediaInfo.overallBR ) {
response.infoLog += `videoBR and overallBR still inconsistent, giving up.\n`;
throw (`videoBR (${MediaInfo.videoBR}) and overallBR (${MediaInfo.overallBR}) still inconsistent, giving up.`);
response.infoLog += `videoBR and overallBR still inconsistent, using default.\n`;
}
}
@ -263,10 +261,13 @@ response.infoLog += `Video details: ${file.ffProbeData.streams[0].codec_name}-${
var maxBitrate = Math.round(targetBitrate*1.3);
var minBitrate = Math.round(targetBitrate*0.7);
var bufsize = Math.round(MediaInfo.videoBR);
if ( isNaN(MediaInfo.videoBR) ) {
var bufsize = targetBitrate;
} else {
var bufsize = Math.round(MediaInfo.videoBR);
}
response.preset += `,-map 0:v -map 0:a -map 0:s? -map -:d? -c copy -c:v:0 hevc_nvenc -rc:v vbr_hq -preset medium -profile:v main -rc-lookahead 32 -spatial_aq:v 1 -aq-strength:v 8 -max_muxing_queue_size 4096 `;
response.preset += `,-map 0:v -map 0:a -map 0:s? -map -:d? -c copy -c:v:0 hevc_nvenc -rc:v vbr_hq -preset medium -profile:v main10 -rc-lookahead 32 -spatial_aq:v 1 -aq-strength:v 8 -max_muxing_queue_size 4096 `;
response.infoLog += `Video bitrate is ${Math.round(MediaInfo.videoBR/1000)}Kbps, overall is ${Math.round(MediaInfo.overallBR/1000)}Kbps. `;
response.infoLog += `Calculated target is ${Math.round(targetBitrate/1000)}Kbps.\n`;
@ -274,7 +275,13 @@ response.infoLog += `Calculated target is ${Math.round(targetBitrate/1000)}Kbps.
// Adjust target bitrates by codec and bitrate
switch (file.ffProbeData.streams[0].codec_name) {
case "hevc":
if ( (MediaInfo.videoBR > targetBitrate*1.5) || file.forceProcessing === true ) {
if ( isNaN(MediaInfo.videoBR) ) {
response.processFile = true;
targetBitrate = Math.min(MediaInfo.overallBR, targetBitrate);
response.preset +=` -b:v ${targetBitrate} -maxrate ${maxBitrate} -minrate ${minBitrate} -bufsize ${bufsize} `;
response.infoLog += `☒HEVC Bitrate for ${file.video_resolution} could not be determined,
using sensible default of ${Math.round(targetBitrate/1000)}Kbps.\n`;
} else if ( (MediaInfo.videoBR > targetBitrate*1.5) || file.forceProcessing === true ) {
response.processFile = true;
response.preset +=` -b:v ${targetBitrate} -maxrate ${maxBitrate} -minrate ${minBitrate} -bufsize ${bufsize} `;
response.infoLog += `☒HEVC Bitrate for ${file.video_resolution} exceeds ${Math.round(targetBitrate*1.5/1000)}Kbps,
@ -286,18 +293,22 @@ response.infoLog += `Calculated target is ${Math.round(targetBitrate/1000)}Kbps.
case "h264":
response.processFile = true;
// We want the new bitrate to be 70% the h264 bitrate, but not higher than our target.
if ( isNaN(MediaInfo.videoBR) ) {
new_bitrate = Math.min(MediaInfo.overallBR*0.7,targetBitrate);
} else {
new_bitrate = Math.min(Math.round(MediaInfo.videoBR*0.7),targetBitrate);
// New bitrate should not be lower than our 60% of our target.
new_bitrate = Math.max( new_bitrate, Math.min(MediaInfo.videoBR, targetBitrate*0.6) );
}
response.preset +=` -b:v ${new_bitrate} -maxrate ${Math.round(new_bitrate*1.3)} -minrate ${Math.round(new_bitrate*0.7)} -bufsize ${bufsize}`;
response.infoLog += `☒H264 Resolution is ${file.video_resolution}, bitrate was ${Math.round(MediaInfo.videoBR/1000)}Kbps.
HEVC target bitrate will be ${Math.round(new_bitrate/1000)}Kbps.\n`;
break; // case "h264"
default:
response.processFile = true;
response.preset +=` -b:v ${targetBitrate} -maxrate ${maxBitrate} -minrate ${minBitrate}K -bufsize ${bufsize} `;
response.preset +=` -b:v ${targetBitrate} -maxrate ${maxBitrate} -minrate ${minBitrate} -bufsize ${bufsize} `;
response.infoLog += `${file.ffProbeData.streams[0].codec_name} resolution is ${file.video_resolution},
bitrate was ${Math.round(MediaInfo.videoBR/1000)}Kbps. HEVC target bitrate will be ${Math.round(new_bitrate/1000)}Kbps.\n`;
bitrate was ${Math.round(MediaInfo.videoBR/1000)}Kbps. HEVC target bitrate will be ${Math.round(targetBitrate/1000)}Kbps.\n`;
break; // default
} // switch (file.ffProbeData.streams[0].codec_name)

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll",
@ -194,7 +195,7 @@ function loopOverStreamsOfType(file, type, method) {
}
/**
* Converts all multi channel audio streams to AC3.
* Removes audio tracks that aren't in the allowed languages or labeled as Commentary tracks.
*/
function buildAudioConfiguration(inputs, file, logger) {
var configuration = new Configurator(["-c:a copy"]);
@ -202,7 +203,7 @@ function buildAudioConfiguration(inputs, file, logger) {
var streams_removing = 0;
var languages = inputs.audio_language.split(",");
loopOverStreamsOfType(file, "audio", function (stream, id) {
stream_count++;
stream_count++;
if ("tags" in stream && "title" in stream.tags && inputs.audio_commentary.toLowerCase() == "true") {
if (
stream.tags.title.toLowerCase().includes("commentary") ||
@ -216,12 +217,12 @@ function buildAudioConfiguration(inputs, file, logger) {
);
}
}
if ("tags" in stream) {
// Remove unwated languages
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++;
streams_removing++;
logger.AddError(
`Removing audio track in language ${stream.tags.language}`
);

@ -0,0 +1,394 @@
/* 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;

@ -33,7 +33,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 dont want to compromise too much on the transcode
// 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
//
// 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 +52,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 dont want to compromise too much on the transcode
// 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
//
// Subtitles:
// All are removed?? (TODO: ensure this is correct and mention the flag to keep them if desired)

@ -1,3 +1,4 @@
/* eslint-disable */
//////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Author: JarBinks, Zachg99, Jeff47

@ -1,233 +1,292 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz1FFMPEG",
Stage: "Pre-processing",
Name: "Migz-Transcode Using Nvidia GPU & FFMPEG",
Type: "Video",
Operation: "Transcode",
Description: `Files not in H265 will be transcoded into H265 using Nvidia GPU with ffmpeg, settings are dependant on file bitrate, working by the logic that H265 can support the same ammount of data at half the bitrate of H264. NVDEC & NVENC compatable GPU required. \n\n`,
Version: "2.5",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js",
Tags: "pre-processing,ffmpeg,video only,nvenc h265,configurable",
Inputs: [
{
name: "container",
tooltip: `Specify output container of file, ensure that all stream types you may have are supported by your chosen container. mkv is recommended.
\\nExample:\\n
mkv
\\nExample:\\n
mp4`,
},
{
name: "bitrate_cutoff",
tooltip: `Specify bitrate cutoff, files with a current bitrate lower then this will not be transcoded. Rate is in kbps. Leave empty to disable.
\\nExample:\\n
6000
\\nExample:\\n
4000`,
},
{
name: "enable_10bit",
tooltip: `Specify if output file should be 10bit. Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "force_conform",
tooltip: `Make the file conform to output containers requirements.
\\n Drop hdmv_pgs_subtitle/eia_608/subrip subtitles for MP4.
\\n Drop data streams and mov_text/eia_608 subtitles for MKV.
id: 'Tdarr_Plugin_MC93_Migz1FFMPEG',
Stage: 'Pre-processing',
Name: 'Migz-Transcode Using Nvidia GPU & FFMPEG',
Type: 'Video',
Operation: 'Transcode',
Description: `Files not in H265 will be transcoded into H265 using Nvidia GPU with ffmpeg.
Settings are dependant on file bitrate
Working by the logic that H265 can support the same ammount of data at half the bitrate of H264.
NVDEC & NVENC compatable GPU required.
This plugin will skip any files that are in the VP9 codec.`,
Version: '3.0',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js',
Tags: 'pre-processing,ffmpeg,video only,nvenc h265,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: '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`,
},
{
name: 'enable_10bit',
tooltip: `Specify if output file should be 10bit. Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'enable_bframes',
tooltip: `Specify if b frames should be used.
\\n Using B frames should decrease file sizes but are only supported on newer GPUs.
\\n Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
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
true
\\nExample:\\n
false`,
},
\\nExample:\\n
false`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
preset: "",
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "",
infoLog: '',
};
let duration = '';
// Check if inputs.container has been configured. If it hasn't then exit plugin.
if (inputs.container == "") {
response.infoLog +=
"☒Container has not been configured within plugin settings, please configure required options. Skipping this plugin. \n";
if (inputs.container === '') {
response.infoLog += 'Plugin has not been configured, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
} else {
response.container = "." + inputs.container;
}
response.container = `.${inputs.container}`;
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += "☒File is not a video. \n";
response.infoLog += 'File is not a video. \n';
return response;
}
// 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") {
var duration = file.meta.Duration * 0.0166667;
// 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 {
var duration = file.ffProbeData.streams[0].duration * 0.0166667;
duration = file.ffProbeData.streams[0].duration * 0.0166667;
}
// Set up required variables.
var videoIdx = 0;
var CPU10 = false;
var extraArguments = "";
var 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/
var 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.
var targetBitrate = ~~(file.file_size / (duration * 0.0075) / 2);
let videoIdx = 0;
let CPU10 = false;
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));
// 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) / 2);
// Allow some leeway under and over the targetBitrate.
var minimumBitrate = ~~(targetBitrate * 0.7);
var maximumBitrate = ~~(targetBitrate * 1.3);
// eslint-disable-next-line no-bitwise
const minimumBitrate = ~~(targetBitrate * 0.7);
// eslint-disable-next-line no-bitwise
const maximumBitrate = ~~(targetBitrate * 1.3);
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculcated. Cancel plugin completely.
if (targetBitrate == "0") {
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculated.
// Cancel plugin completely.
if (targetBitrate === 0) {
response.processFile = false;
response.infoLog +=
"☒Target bitrate could not be calculated. Skipping this plugin. \n";
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 cancel plugin without touching original files.
// 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 cancel plugin without touching original files.
if (currentBitrate <= inputs.bitrate_cutoff) {
response.processFile = false;
response.infoLog += `☑Current bitrate is below configured bitrate cutoff of ${inputs.bitrate_cutoff}. Nothing to do, cancelling plugin. \n`;
response.infoLog += `Current bitrate is below set cutoff of ${inputs.bitrate_cutoff}. Cancelling 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 (var i = 0; i < file.ffProbeData.streams.length; i++) {
if (
file.ffProbeData.streams[i].codec_name
.toLowerCase() == "mov_text" ||
file.ffProbeData.streams[i].codec_name
.toLowerCase() == "eia_608"
) {
extraArguments += `-map -0:${i} `;
}
// 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 (var i = 0; i < file.ffProbeData.streams.length; i++) {
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"
) {
extraArguments += `-map -0:${i} `;
}
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
}
}
}
}
}
// Check if 10bit variable is true.
if (inputs.enable_10bit == "true") {
if (inputs.enable_10bit === 'true') {
// If set to true then add 10bit argument
extraArguments += `-pix_fmt p010le `;
extraArguments += '-pix_fmt p010le ';
}
// Check if b frame variable is true.
if (inputs.enable_bframes === 'true') {
// If set to true then add b frames argument
extraArguments += '-bf 5 ';
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
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") {
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} `;
}
// Check if codec of stream is hevc AND check if file.container matches inputs.container. If so nothing for plugin to do.
// Check if codec of stream is hevc or vp9 AND check if file.container matches inputs.container.
// If so nothing for plugin to do.
if (
file.ffProbeData.streams[i].codec_name == "hevc" &&
file.container == inputs.container
(
file.ffProbeData.streams[i].codec_name === 'hevc'
|| file.ffProbeData.streams[i].codec_name === 'vp9'
)
&& file.container === inputs.container
) {
response.processFile = false;
response.infoLog += `☑File is already hevc & in ${inputs.container}. \n`;
response.infoLog += `File is already hevc or vp9 & in ${inputs.container}. \n`;
return response;
}
// Check if codec of stream is hevc AND check if file.container does NOT match inputs.container. If so remux file.
// Check if codec of stream is hevc or vp9
// AND check if file.container does NOT match inputs.container.
// If so remux file.
if (
file.ffProbeData.streams[i].codec_name == "hevc" &&
file.container != "${inputs.container}"
(
file.ffProbeData.streams[i].codec_name === 'hevc'
|| file.ffProbeData.streams[i].codec_name === 'vp9'
)
&& file.container !== inputs.container
) {
response.infoLog += `☒File is hevc but is not in ${inputs.container} container. Remuxing. \n`;
response.infoLog += `File is hevc or vp9 but is not in ${inputs.container} container. Remuxing. \n`;
response.preset = `, -map 0 -c copy ${extraArguments}`;
response.processFile = true;
return response;
}
// Check if video stream is HDR or 10bit
if (file.ffProbeData.streams[i].profile == "High 10" || file.ffProbeData.streams[i].bits_per_raw_sample == "10" ) {
CPU10 = true
// Check if video stream is HDR or 10bit
if (
file.ffProbeData.streams[i].profile === 'High 10'
|| file.ffProbeData.streams[i].bits_per_raw_sample === '10'
) {
CPU10 = true;
}
// Increment videoIdx.
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`;
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 Current bitrate = ${~~(
file.file_size /
(duration * 0.0075)
)} \n Bitrate settings: \nTarget = ${targetBitrate} \nMinimum = ${minimumBitrate} \nMaximum = ${maximumBitrate} \n`;
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 == "h264") {
if (CPU10 == false) {
response.preset = `-c:v h264_cuvid`;
if (file.video_codec_name === 'h263') {
response.preset = '-c:v h263_cuvid';
} else if (file.video_codec_name === 'h264') {
if (CPU10 === false) {
response.preset = '-c:v h264_cuvid';
}
} 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`;
} else if (file.video_codec_name == "vp9") {
response.preset = `-c:v vp9_cuvid`;
} 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 hevc_nvenc -rc:v vbr_hq -cq:v 19 ${bitrateSettings} -spatial_aq:v 1 -rc-lookahead:v 32 -c:a copy -c:s copy -max_muxing_queue_size 4096 ${extraArguments}`;
response.preset += `,-map 0 -c:v hevc_nvenc -rc:v vbr_hq -cq:v 19 ${bitrateSettings} `
+ `-spatial_aq:v 1 -rc-lookahead:v 32 -c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments}`;
response.processFile = true;
response.infoLog += `☒File is not hevc. Transcoding. \n`;
response.infoLog += 'File is not hevc or vp9. Transcoding. \n';
return response;
}
module.exports.details = details;

@ -1,205 +1,242 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz1FFMPEG_CPU",
Stage: "Pre-processing",
Name: "Migz-Transcode Using CPU & FFMPEG",
Type: "Video",
Operation: "Transcode",
Description: `Files not in H265 will be transcoded into H265 using CPU with ffmpeg, settings are dependant on file bitrate, working by the logic that H265 can support the same ammount of data at half the bitrate of H264. \n\n`,
Version: "1.4",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js",
Tags: "pre-processing,ffmpeg,video only,configurable,h265",
Inputs: [
{
name: "container",
tooltip: `Specify output container of file, ensure that all stream types you may have are supported by your chosen container. mkv is recommended.
\\nExample:\\n
mkv
\\nExample:\\n
mp4`,
},
{
name: "bitrate_cutoff",
tooltip: `Specify bitrate cutoff, files with a current bitrate lower then this will not be transcoded. Rate is in kbps. Leave empty to disable.
\\nExample:\\n
6000
\\nExample:\\n
4000`,
},
{
name: "enable_10bit",
tooltip: `Specify if output file should be 10bit. Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "force_conform",
tooltip: `Make the file conform to output containers requirements.
\\n Drop hdmv_pgs_subtitle/eia_608/subrip subtitles for MP4.
\\n Drop data streams and mov_text/eia_608 subtitles for MKV.
id: 'Tdarr_Plugin_MC93_Migz1FFMPEG_CPU',
Stage: 'Pre-processing',
Name: 'Migz-Transcode Using CPU & FFMPEG',
Type: 'Video',
Operation: 'Transcode',
Description: `Files not in H265 will be transcoded into H265 using CPU with ffmpeg.
Settings are dependant on file bitrate
Working by the logic that H265 can support the same ammount of data at half the bitrate of H264.
This plugin will skip any files that are in the VP9 codec.`,
Version: '1.9',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js',
Tags: 'pre-processing,ffmpeg,video only,configurable,h265',
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: '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`,
},
{
name: 'enable_10bit',
tooltip: `Specify if output file should be 10bit. Default is false.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
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
true
\\nExample:\\n
false`,
},
\\nExample:\\n
false`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
preset: "",
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "",
infoLog: '',
};
let duration = '';
// Check if inputs.container has been configured. If it hasn't then exit plugin.
if (!inputs || inputs.container == "") {
response.infoLog +=
"☒Container has not been configured within plugin settings, please configure required options. Skipping this plugin. \n";
if (inputs.container === '') {
response.infoLog += 'Plugin has not been configured, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
} else {
response.container = "." + inputs.container;
}
response.container = `.${inputs.container}`;
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += "☒File is not a video. \n";
response.infoLog += 'File is not a video. \n';
return response;
}
// 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") {
var duration = file.meta.Duration * 0.0166667;
// 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 {
var duration = file.ffProbeData.streams[0].duration * 0.0166667;
duration = file.ffProbeData.streams[0].duration * 0.0166667;
}
// Set up required variables.
var videoIdx = -1;
var extraArguments = "";
var 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/
var 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.
var targetBitrate = ~~(file.file_size / (duration * 0.0075) / 2);
let videoIdx = -1;
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));
// 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) / 2);
// Allow some leeway under and over the targetBitrate.
var minimumBitrate = ~~(targetBitrate * 0.7);
var maximumBitrate = ~~(targetBitrate * 1.3);
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculcated. Cancel plugin completely.
if (targetBitrate == "0") {
// eslint-disable-next-line no-bitwise
const minimumBitrate = ~~(targetBitrate * 0.7);
// eslint-disable-next-line no-bitwise
const maximumBitrate = ~~(targetBitrate * 1.3);
// If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculcated.
// Cancel plugin completely.
if (targetBitrate === 0) {
response.processFile = false;
response.infoLog +=
"☒Target bitrate could not be calculated. Skipping this plugin. \n";
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 cancel plugin without touching original files.
// 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 cancel plugin without touching original files.
if (currentBitrate <= inputs.bitrate_cutoff) {
response.processFile = false;
response.infoLog += `☑Current bitrate is below configured bitrate cutoff of ${inputs.bitrate_cutoff}. Nothing to do, cancelling plugin. \n`;
response.infoLog += 'Current bitrate is below set bitrate cutoff '
+ `of ${inputs.bitrate_cutoff}. Nothing to do, cancelling 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 (var i = 0; i < file.ffProbeData.streams.length; i++) {
if (
file.ffProbeData.streams[i].codec_name
.toLowerCase() == "mov_text" ||
file.ffProbeData.streams[i].codec_name
.toLowerCase() == "eia_608"
) {
extraArguments += `-map -0:${i} `;
}
// 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 (var i = 0; i < file.ffProbeData.streams.length; i++) {
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"
) {
extraArguments += `-map -0:${i} `;
}
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
}
}
}
}
}
// Check if 10bit variable is true.
if (inputs.enable_10bit == "true") {
if (inputs.enable_10bit === 'true') {
// If set to true then add 10bit argument
extraArguments += `-pix_fmt p010le `;
extraArguments += '-pix_fmt p010le ';
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
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} `;
}
// Check if codec of stream is hevc AND check if file.container matches inputs.container. If so nothing for plugin to do.
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} `;
}
// Check if codec of stream is hevc or vp9
// AND check if file.container matches inputs.container.
// If so nothing for plugin to do.
if (
file.ffProbeData.streams[i].codec_name == "hevc" &&
file.container == inputs.container
(file.ffProbeData.streams[i].codec_name === 'hevc' || file.ffProbeData.streams[i].codec_name === 'vp9')
&& file.container === `${inputs.container}`
) {
response.processFile = false;
response.infoLog += `File is already hevc & in ${inputs.container}. \n`;
response.infoLog += `File is already hevc or vp9 & in ${inputs.container}. \n`;
return response;
}
// Check if codec of stream is hevc AND check if file.container does NOT match inputs.container. If so remux file.
// Check if codec of stream is hevc or vp9
// AND check if file.container does NOT match inputs.container.
// If so remux file.
if (
file.ffProbeData.streams[i].codec_name == "hevc" &&
file.container != "${inputs.container}"
(file.ffProbeData.streams[i].codec_name === 'hevc' || file.ffProbeData.streams[i].codec_name === 'vp9')
&& file.container !== `${inputs.container}`
) {
response.infoLog += `File is hevc but is not in ${inputs.container} container. Remuxing. \n`;
response.infoLog += `File is hevc or vp9 but is not in ${inputs.container} container. Remuxing. \n`;
response.preset = `, -map 0 -c copy ${extraArguments}`;
response.processFile = true;
return response;
}
// Increment videoIdx.
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`;
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 Current bitrate = ${~~(
file.file_size /
(duration * 0.0075)
)} \n Bitrate settings: \nTarget = ${targetBitrate} \nMinimum = ${minimumBitrate} \nMaximum = ${maximumBitrate} \n`;
response.preset += `,-map 0 -c:v libx265 ${bitrateSettings} -c:a copy -c:s copy -max_muxing_queue_size 4096 ${extraArguments}`;
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`;
response.preset += `,-map 0 -c:v libx265 ${bitrateSettings} `
+ `-c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments}`;
response.processFile = true;
response.infoLog += `☒File is not hevc. Transcoding. \n`;
response.infoLog += 'File is not hevc or vp9. Transcoding. \n';
return response;
}
module.exports.details = details;

@ -0,0 +1,132 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: 'Tdarr_Plugin_MC93_Migz1Remux',
Stage: 'Pre-processing',
Name: 'Migz-Remux container',
Type: 'Video',
Operation: 'Remux',
Description: 'Files will be remuxed into either mkv or mp4. \n\n',
Version: '1.1',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz1Remux.js',
Tags: 'pre-processing,ffmpeg,video only,configurable',
Inputs: [{
name: 'container',
tooltip: `Specify output container of file
\\nEnsure that all stream types you may have are supported by your chosen container.
\\nmkv 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`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
const response = {
processFile: false,
preset: '',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
};
// Check if inputs.container has been configured. If it hasn't then exit plugin.
if (inputs.container === '') {
response.infoLog
+= '☒Container 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;
}
// Set up required variables.
let extraArguments = '';
let convert = false;
// 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
}
}
}
}
// Check if file.container does NOT match inputs.container. If so remux file.
if (file.container !== inputs.container) {
response.infoLog += `☒File is ${file.container} but requested to be ${inputs.container} container. Remuxing. \n`;
convert = true;
} else if (file.container === inputs.container) {
response.infoLog += `☑File is already in ${inputs.container} container. \n`;
return response;
}
if (convert === true) {
response.preset += `, -map 0 -c copy -max_muxing_queue_size 9999 ${extraArguments}`;
response.processFile = true;
return response;
}
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -1,123 +1,217 @@
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz2CleanTitle",
Stage: "Pre-processing",
Name: "Migz-Clean title metadata",
Type: "Video",
Operation: "Clean",
Description: `This plugin removes title metadata from video/audio/subtitles, if it exists. Video checking is mandatory, audio and subtitles are optional.\n\n`,
Version: "1.2",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js",
Tags: "pre-processing,ffmpeg,configurable",
Inputs: [
{
name: "clean_audio",
tooltip: `Specify if audio titles should be checked & cleaned. Optional.
\\nExample:\\n
true
id: 'Tdarr_Plugin_MC93_Migz2CleanTitle',
Stage: 'Pre-processing',
Name: 'Migz-Clean title metadata',
Type: 'Video',
Operation: 'Clean',
Description: 'This plugin removes title metadata from video/audio/subtitles.\n\n',
Version: '1.9',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js',
Tags: 'pre-processing,ffmpeg,configurable',
Inputs: [{
name: 'clean_audio',
tooltip: `Specify if audio titles should be checked & cleaned. Optional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "clean_subtitles",
tooltip: `Specify if subtitle titles should be checked & cleaned. Optional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'clean_subtitles',
tooltip: `Specify if subtitle titles should be checked & cleaned. Optional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
\\nExample:\\n
false`,
},
{
name: 'custom_title_matching',
tooltip: `If you enable audio or subtitle cleaning the plugin only looks for titles with more then 3 full stops.
//nThis is one way to identify junk metadata without removing real metadata that you might want.
//nHere you can specify your own text for it to also search for to match and remove.
//nComma separated. Optional.
\\nExample:\\n
MiNX - Small HD episodes
\\nExample:\\n
MiNX - Small HD episodes,GalaxyTV - small excellence!`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
preset: "",
container: "." + file.container,
preset: '',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
// Set up required variables.
var ffmpegCommandInsert = "";
var videoIdx = 0;
var audioIdx = 0;
var subtitleIdx = 0;
var convert = false;
let ffmpegCommandInsert = '';
let videoIdx = 0;
let audioIdx = 0;
let subtitleIdx = 0;
let convert = false;
let custom_title_matching = '';
// Check if inputs.custom_title_matching has been configured. If it has then set variable
if (typeof inputs.custom_title_matching !== 'undefined') {
custom_title_matching = inputs.custom_title_matching.toLowerCase().split(',');
}
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
console.log("File is not video");
response.infoLog += "☒File is not video \n";
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
}
// Check if overall file metadata title is not empty, if it's not empty set to "".
if (typeof file.meta.Title != "undefined")
if (
!(
typeof file.meta.Title === 'undefined'
|| file.meta.Title === '""'
|| file.meta.Title === ''
)
) {
try {
ffmpegCommandInsert += ` -metadata title="" `;
ffmpegCommandInsert += ' -metadata title="" ';
convert = true;
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++)
try {
// Check if stream is a video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "video") {
// Check if stream title is not empty, if it's nto empty set to "".
if (typeof file.ffProbeData.streams[i].tags.title != "undefined") {
response.infoLog += `☒Video stream title is not empty, most likely junk metadata. Removing title from stream ${i} \n`;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
// Check if stream is a video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
try {
// Check if stream title is not empty, if it's not empty set to "".
if (
!(
typeof file.ffProbeData.streams[i].tags.title === 'undefined'
|| file.ffProbeData.streams[i].tags.title === '""'
|| file.ffProbeData.streams[i].tags.title === ''
)
) {
response.infoLog += `☒Video stream title is not empty. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:v:${videoIdx} title="" `;
convert = true;
}
// Increment videoIdx.
videoIdx++;
videoIdx += 1;
} catch (err) {
// Error
}
}
// Check if title metadata of audio stream has more then 3 full stops. If so then it's likely to be junk metadata so remove.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
inputs.clean_audio.toLowerCase() == "true"
) {
if (file.ffProbeData.streams[i].tags.title.split(".").length - 1 > 3) {
response.infoLog += `☒More then 3 full stops detected in audio title, likely to be junk metadata. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:a:${audioIdx} title="" `;
convert = true;
// Check if title metadata of audio stream has more then 3 full stops.
// If so then it's likely to be junk metadata so remove.
// Then check if any audio streams match with user input custom_title_matching variable, if so then remove.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& inputs.clean_audio.toLowerCase() === 'true'
) {
try {
if (
!(
typeof file.ffProbeData.streams[i].tags.title === 'undefined'
|| file.ffProbeData.streams[i].tags.title === '""'
|| file.ffProbeData.streams[i].tags.title === ''
)
) {
if (file.ffProbeData.streams[i].tags.title.split('.').length - 1 > 3) {
try {
response.infoLog += `☒More then 3 full stops in audio title. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:a:${audioIdx} title="" `;
convert = true;
} catch (err) {
// Error
}
}
if (typeof inputs.custom_title_matching !== 'undefined') {
try {
if (custom_title_matching.indexOf(file.ffProbeData.streams[i].tags.title.toLowerCase()) !== -1) {
response.infoLog += `☒Audio matched custom input. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:a:${audioIdx} title="" `;
convert = true;
}
} catch (err) {
// Error
}
}
}
// Increment audioIdx.
audioIdx++;
audioIdx += 1;
} catch (err) {
// Error
}
}
// Check if title metadata of subtitle stream has more then 3 full stops. If so then it's likely to be junk metadata so remove.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle" &&
inputs.clean_subtitles.toLowerCase() == "true"
) {
if (file.ffProbeData.streams[i].tags.title.split(".").length - 1 > 3) {
response.infoLog += `☒More then 3 full stops detected in subtitle title, likely to be junk metadata. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:s:${subtitleIdx} title="" `;
convert = true;
// Check if title metadata of subtitle stream has more then 3 full stops.
// If so then it's likely to be junk metadata so remove.
// Then check if any streams match with user input custom_title_matching variable, if so then remove.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle'
&& inputs.clean_subtitles.toLowerCase() === 'true'
) {
try {
if (
!(
typeof file.ffProbeData.streams[i].tags.title === 'undefined'
|| file.ffProbeData.streams[i].tags.title === '""'
|| file.ffProbeData.streams[i].tags.title === ''
)
) {
if (file.ffProbeData.streams[i].tags.title.split('.').length - 1 > 3) {
try {
response.infoLog += `☒More then 3 full stops in subtitle title. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:s:${subtitleIdx} title="" `;
convert = true;
} catch (err) {
// Error
}
}
if (typeof inputs.custom_title_matching !== 'undefined') {
try {
if (custom_title_matching.indexOf(file.ffProbeData.streams[i].tags.title.toLowerCase()) !== -1) {
response.infoLog += `☒Subtitle matched custom input. Removing title from stream ${i} \n`;
ffmpegCommandInsert += ` -metadata:s:s:${subtitleIdx} title="" `;
convert = true;
}
} catch (err) {
// Error
}
}
}
// Increment subtitleIdx.
subtitleIdx++;
subtitleIdx += 1;
} catch (err) {
// Error
}
} catch (err) {}
}
}
// Convert file if convert variable is set to true.
if (convert == true) {
response.infoLog += "☒File has title metadata. Removing \n";
response.preset = `,${ffmpegCommandInsert} -c copy -map 0 -max_muxing_queue_size 4096`;
if (convert === true) {
response.infoLog += '☒File has title metadata. Removing \n';
response.preset = `,${ffmpegCommandInsert} -c copy -map 0 -max_muxing_queue_size 9999`;
response.reQueueAfter = true;
response.processFile = true;
} else {
response.infoLog += "☑File has no title metadata \n";
response.infoLog += '☑File has no title metadata \n';
}
return response;
}

@ -1,140 +1,154 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz3CleanAudio",
Stage: "Pre-processing",
Name: "Migz-Clean audio streams",
Type: "Audio",
Operation: "Clean",
Description: `This plugin keeps only specified language audio tracks & can tags those that have an unknown language. \n\n`,
Version: "2.2",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js",
Tags: "pre-processing,ffmpeg,audio only,configurable",
Inputs: [
{
name: "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,und
\\nExample:\\n
eng,und,jap`,
},
{
name: "commentary",
tooltip: `Specify if audio tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "tag_language",
tooltip: `Specify a single language for audio tracks with no language or unknown language to be tagged with, leave empty to disable, you must have "und" in your list of languages to keep for this to function. Must follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
por`,
},
{
name: "tag_title",
tooltip: `Specify audio tracks with no title to be tagged with the number of channels they contain. Do NOT use this with mp4, as mp4 does not support title tags.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
id: 'Tdarr_Plugin_MC93_Migz3CleanAudio',
Stage: 'Pre-processing',
Name: 'Migz-Clean audio streams',
Type: 'Audio',
Operation: 'Clean',
Description: 'This plugin keeps only specified language tracks & can tags tracks with an unknown language. \n\n',
Version: '2.4',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js',
Tags: 'pre-processing,ffmpeg,audio only,configurable',
Inputs: [{
name: 'language',
tooltip: `Specify language tag/s here for the audio tracks you'd like to keep
\\nRecommended to keep "und" as this stands for undertermined
\\nSome files may not have the language specified.
\\nMust follow ISO-639-2 3 letter 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: 'commentary',
tooltip: `Specify if audio tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'tag_language',
tooltip: `Specify a single language for audio tracks with no language or unknown language to be tagged with.
\\nYou must have "und" in your list of languages to keep for this to function.
\\nMust follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nLeave empty to disable.
\\nExample:\\n
eng
\\nExample:\\n
por`,
},
{
name: 'tag_title',
tooltip: `Specify audio tracks with no title to be tagged with the number of channels they contain.
\\nDo NOT use this with mp4, as mp4 does not support title tags.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
preset: "",
container: "." + file.container,
preset: '',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
console.log("File is not video");
response.infoLog += "☒File is not video \n";
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
}
// Check if inputs.language has been configured. If it hasn't then exit plugin.
if (inputs.language == "") {
response.infoLog +=
"☒Language/s keep have not been configured within plugin settings, please configure required options. Skipping this plugin. \n";
if (inputs.language === '') {
response.infoLog += '☒Language/s options not set, please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
// Set up required variables.
var language = inputs.language.split(",");
var ffmpegCommandInsert = "";
var convert = false;
var audioIdx = 0;
var audioStreamsRemoved = 0;
var audioStreamCount = file.ffProbeData.streams.filter(
(row) => row.codec_type.toLowerCase() == "audio"
const language = inputs.language.split(',');
let ffmpegCommandInsert = '';
let convert = false;
let audioIdx = 0;
let audioStreamsRemoved = 0;
const audioStreamCount = file.ffProbeData.streams.filter(
(row) => row.codec_type.toLowerCase() === 'audio',
).length;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Catch error here incase the language metadata is completely missing.
try {
// Check if stream is audio AND checks if the tracks language code does not match any of the languages entered in inputs.language.
// Check if stream is audio
// AND checks if the tracks language code does not match any of the languages entered in inputs.language.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
language.indexOf(
file.ffProbeData.streams[i].tags.language.toLowerCase()
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& language.indexOf(
file.ffProbeData.streams[i].tags.language.toLowerCase(),
) === -1
) {
audioStreamsRemoved++;
audioStreamsRemoved += 1;
ffmpegCommandInsert += `-map -0:a:${audioIdx} `;
response.infoLog += `☒Audio stream detected as being an unwanted language, removing. Audio stream 0:a:${audioIdx} - ${file.ffProbeData.streams[
i
].tags.language.toLowerCase()} \n`;
response.infoLog += `☒Audio stream detected as being unwanted, removing. Audio stream 0:a:${audioIdx} \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Catch error here incase the title metadata is completely missing.
try {
// Check if inputs.commentary is set to true AND if stream is audio AND then checks for stream titles with the following "commentary, description, sdh". Removing any streams that are applicable.
// Check if inputs.commentary is set to true
// AND if stream is audio
// AND then checks for stream titles with the following "commentary, description, sdh".
// Removing any streams that are applicable.
if (
inputs.commentary.toLowerCase() == "true" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
(file.ffProbeData.streams[i].tags.title
inputs.commentary.toLowerCase() === 'true'
&& file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& (file.ffProbeData.streams[i].tags.title
.toLowerCase()
.includes("commentary") ||
file.ffProbeData.streams[i].tags.title
.includes('commentary')
|| file.ffProbeData.streams[i].tags.title
.toLowerCase()
.includes("description") ||
file.ffProbeData.streams[i].tags.title.toLowerCase().includes("sdh"))
.includes('description')
|| file.ffProbeData.streams[i].tags.title.toLowerCase().includes('sdh'))
) {
audioStreamsRemoved++;
audioStreamsRemoved += 1;
ffmpegCommandInsert += `-map -0:a:${audioIdx} `;
response.infoLog += `☒Audio stream detected as being Commentary or Description, removing. Audio stream 0:a:${audioIdx} - ${file.ffProbeData.streams[i].tags.title}. \n`;
response.infoLog += `☒Audio stream detected as being descriptive, removing. Stream 0:a:${audioIdx} \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Check if inputs.tag_language has something entered (Entered means user actually wants something to happen, empty would disable this) AND checks that stream is audio.
// Check if inputs.tag_language has something entered
// (Entered means user actually wants something to happen, empty would disable this)
// AND checks that stream is audio.
if (
inputs.tag_language != "" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio"
inputs.tag_language !== ''
&& file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
) {
// Catch error here incase the metadata is completely missing.
try {
@ -142,64 +156,69 @@ function plugin(file, librarySettings, inputs) {
if (
file.ffProbeData.streams[i].tags.language
.toLowerCase()
.includes("und")
.includes('und')
) {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Audio stream detected as having unknown language tagged, tagging as ${inputs.tag_language}. \n`;
response.infoLog += `☒Audio stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Checks if the tags metadata is completely missing, if so this would cause playback to show language as "undefined". No catch error here otherwise it would never detect the metadata as missing.
if (typeof file.ffProbeData.streams[i].tags == "undefined") {
// Checks if the tags metadata is completely missing.
// If so this would cause playback to show language as "undefined".
// No catch error here otherwise it would never detect the metadata as missing.
if (typeof file.ffProbeData.streams[i].tags === 'undefined') {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Audio stream detected as having no language tagged, tagging as ${inputs.tag_language}. \n`;
response.infoLog += `☒Audio stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
} else if (typeof file.ffProbeData.streams[i].tags.language === 'undefined') {
// Checks if the tags.language metadata is completely missing.
// If so this would cause playback to show language as "undefined".
// No catch error here otherwise it would never detect the metadata as missing.
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Audio stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
// Checks if the tags.language metadata is completely missing, if so this would cause playback to show language as "undefined". No catch error here otherwise it would never detect the metadata as missing.
else {
if (typeof file.ffProbeData.streams[i].tags.language == "undefined") {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Audio stream detected as having no language tagged, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
}
}
try {
// Check if title metadata is missing from any streams AND inputs.tag_title set to true AND if stream type is audio. Add title to any applicable streams.
// Check if title metadata is missing from any streams
// AND inputs.tag_title set to true AND if stream type is audio. Add title to any applicable streams.
if (
typeof file.ffProbeData.streams[i].tags.title == "undefined" &&
inputs.tag_title.toLowerCase() == "true" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio"
typeof file.ffProbeData.streams[i].tags.title === 'undefined'
&& inputs.tag_title.toLowerCase() === 'true'
&& file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
) {
if (file.ffProbeData.streams[i].channels == "8") {
if (file.ffProbeData.streams[i].channels === 8) {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} title="7.1" `;
response.infoLog += `☒Audio stream detected as 8 channel audio track with no title, tagging title. Audio stream 0:a:${audioIdx} tagged as "7.1" \n`;
response.infoLog += `☒Audio stream detected as 8 channel with no title, tagging. Stream 0:a:${audioIdx} \n`;
convert = true;
}
if (file.ffProbeData.streams[i].channels == "6") {
if (file.ffProbeData.streams[i].channels === 6) {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} title="5.1" `;
response.infoLog += `☒Audio stream detected as 6 channel audio track with no title, tagging title. Audio stream 0:a:${audioIdx} tagged as "5.1" \n`;
response.infoLog += `☒Audio stream detected as 6 channel with no title, tagging. Stream 0:a:${audioIdx} \n`;
convert = true;
}
if (file.ffProbeData.streams[i].channels == "2") {
if (file.ffProbeData.streams[i].channels === 2) {
ffmpegCommandInsert += `-metadata:s:a:${audioIdx} title="2.0" `;
response.infoLog += `☒Audio stream detected as 2 channel audio track with no title, tagging title. Audio stream 0:a:${audioIdx} tagged as "2.0" \n`;
response.infoLog += `☒Audio stream detected as 2 channel with no title, tagging. Stream 0:a:${audioIdx} \n`;
convert = true;
}
}
} catch (err) {}
} catch (err) {
// Error
}
// Check if stream type is audio and increment audioIdx if true.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") {
audioIdx++;
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
audioIdx += 1;
}
}
// Failsafe to cancel processing if all streams would be removed following this plugin. We don't want no audio.
if (audioStreamsRemoved == audioStreamCount) {
response.infoLog +=
"☒Cancelling plugin otherwise all audio tracks would be removed. \n";
if (audioStreamsRemoved === audioStreamCount) {
response.infoLog += '☒Cancelling plugin otherwise all audio tracks would be removed. \n';
response.processFile = false;
return response;
}
@ -207,13 +226,12 @@ function plugin(file, librarySettings, inputs) {
// Convert file if convert variable is set to true.
if (convert === true) {
response.processFile = true;
response.preset = `, -map 0 ${ffmpegCommandInsert} -c copy -max_muxing_queue_size 4096`;
response.container = "." + file.container;
response.preset = `, -map 0 ${ffmpegCommandInsert} -c copy -max_muxing_queue_size 9999`;
response.container = `.${file.container}`;
response.reQueueAfter = true;
} else {
response.processFile = false;
response.infoLog +=
"☑File doesn't contain audio tracks which are unwanted or that require tagging.\n";
response.infoLog += "☑File doesn't contain audio tracks which are unwanted or that require tagging.\n";
}
return response;
}

@ -1,123 +1,133 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz4CleanSubs",
Stage: "Pre-processing",
Name: "Migz-Clean subtitle streams",
Type: "subtitles",
Operation: "Clean",
Description: `This plugin keeps only specified language subtitle tracks & can tag those that have an unknown language. \n\n`,
Version: "2.2",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js",
Tags: "pre-processing,ffmpeg,subtitle only,configurable",
Inputs: [
{
name: "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
id: 'Tdarr_Plugin_MC93_Migz4CleanSubs',
Stage: 'Pre-processing',
Name: 'Migz-Clean subtitle streams',
Type: 'subtitles',
Operation: 'Clean',
Description: 'This plugin keeps only specified language tracks & can tag tracks with an unknown language. \n\n',
Version: '2.4',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js',
Tags: 'pre-processing,ffmpeg,subtitle only,configurable',
Inputs: [{
name: 'language',
tooltip: `Specify language tag/s here for the subtitle tracks you'd like to keep.
\\nMust follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
eng,jap`,
},
{
name: "commentary",
tooltip: `Specify if subtitle tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
eng,jpn`,
},
{
name: 'commentary',
tooltip: `Specify if subtitle tracks that contain commentary/description should be removed.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "tag_language",
tooltip: `Specify a single language for subtitle tracks with no language or unknown language to be tagged with, leave empty to disable. Must follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nExample:\\n
eng
\\nExample:\\n
false`,
},
{
name: 'tag_language',
tooltip: `Specify a single language for subtitle tracks with no language or unknown language to be tagged with.
\\nMust follow ISO-639-2 3 letter format. https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
\\nLeave empty to disable.
\\nExample:\\n
eng
\\nExample:\\n
por`,
},
\\nExample:\\n
por`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
preset: "",
container: "." + file.container,
preset: '',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
console.log("File is not video");
response.infoLog += "☒File is not video \n";
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
}
// Check if inputs.language has been configured. If it hasn't then exit plugin.
if (inputs.language == "") {
response.infoLog +=
"☒Language/s keep have not been configured within plugin settings, please configure required options. Skipping this plugin. \n";
if (inputs.language === '') {
response.infoLog += '☒Language/s to keep have not been configured, '
+ 'please configure required options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
// Set up required variables.
var language = inputs.language.split(",");
var ffmpegCommandInsert = "";
var subtitleIdx = 0;
var convert = false;
const language = inputs.language.split(',');
let ffmpegCommandInsert = '';
let subtitleIdx = 0;
let convert = false;
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Catch error here incase the language metadata is completely missing.
try {
// Check if stream is subtitle AND checks if the tracks language code does not match any of the languages entered in inputs.language.
// Check if stream is subtitle
// AND checks if the tracks language code does not match any of the languages entered in inputs.language.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle" &&
language.indexOf(
file.ffProbeData.streams[i].tags.language.toLowerCase()
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle'
&& language.indexOf(
file.ffProbeData.streams[i].tags.language.toLowerCase(),
) === -1
) {
ffmpegCommandInsert += `-map -0:s:${subtitleIdx} `;
response.infoLog += `☒Subtitle stream detected as being an unwanted language, removing. Subtitle stream 0:s:${subtitleIdx} - ${file.ffProbeData.streams[
i
].tags.language.toLowerCase()} \n`;
response.infoLog += `☒Subtitle stream detected as being unwanted, removing. Stream 0:s:${subtitleIdx} \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Catch error here incase the title metadata is completely missing.
try {
// Check if inputs.commentary is set to true AND if stream is subtitle AND then checks for stream titles with the following "commentary, description, sdh". Removing any streams that are applicable.
// Check if inputs.commentary is set to true
// AND if stream is subtitle
// AND then checks for stream titles with the following "commentary or description".
// Removing any streams that are applicable.
if (
inputs.commentary.toLowerCase() == "true" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle" &&
(file.ffProbeData.streams[i].tags.title
inputs.commentary.toLowerCase() === 'true'
&& file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle'
&& (file.ffProbeData.streams[i].tags.title
.toLowerCase()
.includes("commentary") ||
file.ffProbeData.streams[i].tags.title
.includes('commentary')
|| file.ffProbeData.streams[i].tags.title
.toLowerCase()
.includes("description") ||
file.ffProbeData.streams[i].tags.title.toLowerCase().includes("sdh"))
.includes('description'))
) {
ffmpegCommandInsert += `-map -0:s:${subtitleIdx} `;
response.infoLog += `☒Subtitle stream detected as being Commentary or Description, removing. Subtitle stream 0:s:${subtitleIdx} - ${file.ffProbeData.streams[i].tags.title}. \n`;
response.infoLog += `☒Subtitle stream detected as being descriptive, removing. Stream 0:s:${subtitleIdx} \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Check if inputs.tag_language has something entered (Entered means user actually wants something to happen, empty would disable this) AND checks that stream is subtitle.
// Check if inputs.tag_language has something entered.
// (Entered means user actually wants something to happen, empty would disable this)
// AND checks that stream is subtitle.
if (
inputs.tag_language != "" &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle"
inputs.tag_language !== ''
&& file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle'
) {
// Catch error here incase the metadata is completely missing.
try {
@ -125,46 +135,48 @@ function plugin(file, librarySettings, inputs) {
if (
file.ffProbeData.streams[i].tags.language
.toLowerCase()
.includes("und")
.includes('und')
) {
ffmpegCommandInsert += `-metadata:s:s:${subtitleIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Subtitle stream detected as having unknown language tagged, tagging as ${inputs.tag_language}. \n`;
response.infoLog += `☒Subtitle stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
} catch (err) {}
} catch (err) {
// Error
}
// Checks if the tags metadata is completely missing, if so this would cause playback to show language as "undefined". No catch error here otherwise it would never detect the metadata as missing.
if (typeof file.ffProbeData.streams[i].tags == "undefined") {
// Checks if the tags metadata is completely missing.
// If so this would cause playback to show language as "undefined".
// No catch error here otherwise it would never detect the metadata as missing.
if (typeof file.ffProbeData.streams[i].tags === 'undefined') {
ffmpegCommandInsert += `-metadata:s:s:${subtitleIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Subtitle stream detected as having no language tagged, tagging as ${inputs.tag_language}. \n`;
response.infoLog += `☒Subtitle stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
} else if (typeof file.ffProbeData.streams[i].tags.language === 'undefined') {
// Checks if the tags.language metadata is completely missing.
// If so this would cause playback to show language as "undefined".
// No catch error here otherwise it would never detect the metadata as missing
ffmpegCommandInsert += `-metadata:s:s:${subtitleIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Subtitle stream detected as having no language, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
// Checks if the tags.language metadata is completely missing, if so this would cause playback to show language as "undefined". No catch error here otherwise it would never detect the metadata as missing.
else {
if (typeof file.ffProbeData.streams[i].tags.language == "undefined") {
ffmpegCommandInsert += `-metadata:s:s:${subtitleIdx} language=${inputs.tag_language} `;
response.infoLog += `☒Subtitle stream detected as having no language tagged, tagging as ${inputs.tag_language}. \n`;
convert = true;
}
}
}
// Check if stream type is subtitle and increment subtitleIdx if true.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") {
subtitleIdx++;
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') {
subtitleIdx += 1;
}
}
// Convert file if convert variable is set to true.
if (convert === true) {
response.processFile = true;
response.preset = `, -map 0 ${ffmpegCommandInsert} -c copy -max_muxing_queue_size 4096`;
response.container = "." + file.container;
response.preset = `, -map 0 ${ffmpegCommandInsert} -c copy -max_muxing_queue_size 9999`;
response.container = `.${file.container}`;
response.reQueueAfter = true;
} else {
response.processFile = false;
response.infoLog +=
"☑File doesn't contain subtitle tracks which are unwanted or that require tagging.\n";
response.infoLog += "☑File doesn't contain subtitle tracks which are unwanted or that require tagging.\n";
}
return response;
}

@ -1,148 +1,155 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz5ConvertAudio",
Stage: "Pre-processing",
Name: "Migz-Convert audio streams",
Type: "Audio",
Operation: "Transcode",
Description: `This plugin can convert any 2.0 audio track/s to AAC and can create downmixed audio tracks. \n\n`,
Version: "2.1",
Link: "",
Tags: "pre-processing,ffmpeg,audio only,configurable",
Inputs: [
{
name: "aac_stereo",
tooltip: `Specify if any 2.0 audio tracks should be converted to aac for maximum compatability with devices. Optional.
\\nExample:\\n
true
id: 'Tdarr_Plugin_MC93_Migz5ConvertAudio',
Stage: 'Pre-processing',
Name: 'Migz-Convert audio streams',
Type: 'Audio',
Operation: 'Transcode',
Description: 'This plugin can convert any 2.0 audio track/s to AAC and can create downmixed audio tracks. \n\n',
Version: '2.3',
Link: '',
Tags: 'pre-processing,ffmpeg,audio only,configurable',
Inputs: [{
name: 'aac_stereo',
tooltip: `Specify if any 2.0 audio tracks should be converted to aac for maximum compatability with devices.
\\nOptional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: "downmix",
tooltip: `Specify if downmixing should be used to create extra audio tracks. I.e if you have an 8ch but no 2ch or 6ch, create the missing audio tracks from the 8 ch. Likewise if you only have 6ch, create the missing 2ch from it. Optional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
{
name: 'downmix',
tooltip: `Specify if downmixing should be used to create extra audio tracks.
\\nI.e if you have an 8ch but no 2ch or 6ch, create the missing audio tracks from the 8 ch.
\\nLikewise if you only have 6ch, create the missing 2ch from it. Optional.
\\nExample:\\n
true
\\nExample:\\n
false`,
},
\\nExample:\\n
false`,
},
],
};
}
function plugin(file, librarySettings, inputs) {
var response = {
const response = {
processFile: false,
container: "." + file.container,
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "",
infoLog: '',
};
// Check if both inputs.aac_stereo AND inputs.downmix have been left empty. If they have then exit plugin.
if (inputs && inputs.aac_stereo == "" && inputs.downmix == "") {
response.infoLog +=
"☒Neither aac_stereo or downmix options have been configured within plugin settings, please configure required options. Skipping this plugin. \n";
if (inputs && inputs.aac_stereo === '' && inputs.downmix === '') {
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") {
console.log("File is not video");
response.infoLog += "☒File is not video. \n";
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += '☒File is not video. \n';
response.processFile = false;
return response;
}
// Set up required variables.
var ffmpegCommandInsert = "";
var audioIdx = 0;
var has2Channel = false;
var has6Channel = false;
var has8Channel = false;
var convert = false;
let ffmpegCommandInsert = '';
let audioIdx = 0;
let has2Channel = false;
let has6Channel = false;
let has8Channel = false;
let convert = false;
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
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") {
if (file.ffProbeData.streams[i].channels == "2") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
if (file.ffProbeData.streams[i].channels === 2) {
has2Channel = true;
}
if (file.ffProbeData.streams[i].channels == "6") {
if (file.ffProbeData.streams[i].channels === 6) {
has6Channel = true;
}
if (file.ffProbeData.streams[i].channels == "8") {
if (file.ffProbeData.streams[i].channels === 8) {
has8Channel = true;
}
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Check if stream is audio.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
// Catch error here incase user left inputs.downmix empty.
try {
// Check if inputs.downmix is set to true.
if (inputs.downmix.toLowerCase() == "true") {
if (inputs.downmix.toLowerCase() === 'true') {
// Check if file has 8 channel audio but no 6 channel, if so then create extra downmix from the 8 channel.
if (
has8Channel == true &&
has6Channel == false &&
file.ffProbeData.streams[i].channels == "8"
has8Channel === true
&& has6Channel === false
&& file.ffProbeData.streams[i].channels === 8
) {
ffmpegCommandInsert += `-map 0:${i} -c:a:${audioIdx} ac3 -ac 6 -metadata:s:a:${audioIdx} title="5.1 " `;
response.infoLog +=
"☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n";
response.infoLog += '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n';
convert = true;
}
// Check if file has 6 channel audio but no 2 channel, if so then create extra downmix from the 6 channel.
if (
has6Channel == true &&
has2Channel == false &&
file.ffProbeData.streams[i].channels == "6"
has6Channel === true
&& has2Channel === false
&& file.ffProbeData.streams[i].channels === 6
) {
ffmpegCommandInsert += `-map 0:${i} -c:a:${audioIdx} aac -ac 2 -metadata:s:a:${audioIdx} title="2.0 " `;
response.infoLog +=
"☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n";
response.infoLog += '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n';
convert = true;
}
}
} catch (err) {}
} catch (err) {
// Error
}
// Catch error here incase user left inputs.downmix empty.
try {
// Check if inputs.aac_stereo is set to true.
if (inputs.aac_stereo.toLowerCase() == "true") {
if (inputs.aac_stereo.toLowerCase() === 'true') {
// Check if codec_name for stream is NOT aac AND check if channel ammount is 2.
if (
file.ffProbeData.streams[i].codec_name != "aac" &&
file.ffProbeData.streams[i].channels == "2"
file.ffProbeData.streams[i].codec_name !== 'aac'
&& file.ffProbeData.streams[i].channels === 2
) {
ffmpegCommandInsert += `-c:a:${audioIdx} aac `;
response.infoLog +=
"☒Audio track is 2 channel but is not AAC. Converting. \n";
response.infoLog += '☒Audio track is 2 channel but is not AAC. Converting. \n';
convert = true;
}
}
} catch (err) {}
audioIdx++;
} catch (err) {
// Error
}
audioIdx += 1;
}
}
// Convert file if convert variable is set to true.
if (convert == true) {
if (convert === true) {
response.processFile = true;
response.preset = `, -map 0 -c:v copy -c:a copy ${ffmpegCommandInsert} -strict -2 -c:s copy -max_muxing_queue_size 4096 `;
response.preset = `, -map 0 -c:v copy -c:a copy ${ffmpegCommandInsert} `
+ '-strict -2 -c:s copy -max_muxing_queue_size 9999 ';
} else {
response.infoLog += "☑File contains all required audio formats. \n";
response.infoLog += '☑File contains all required audio formats. \n';
response.processFile = false;
}
return response;

@ -1,184 +1,201 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_Migz6OrderStreams",
Stage: "Pre-processing",
Name: "Migz-Order Streams",
Type: "Streams",
Operation: "Order",
Description: `Orders streams into Video first, then Audio (2ch, 6ch, 8ch) and finally Subtitles. \n\n`,
Version: "1.1",
id: 'Tdarr_Plugin_MC93_Migz6OrderStreams',
Stage: 'Pre-processing',
Name: 'Migz-Order Streams',
Type: 'Streams',
Operation: 'Order',
Description: 'Orders streams into Video first, then Audio (2ch, 6ch, 8ch) and finally Subtitles. \n\n',
Version: '1.3',
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js",
Tags: "pre-processing,ffmpeg,",
'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js',
Tags: 'pre-processing,ffmpeg,',
};
}
function plugin(file) {
var response = {
const response = {
processFile: false,
preset: "",
container: "." + file.container,
preset: '',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
infoLog: "",
infoLog: '',
};
// Set up required variables.
var ffmpegCommandInsert = "";
var videoIdx = 0;
var audioIdx = 0;
var audio2Idx = 0;
var audio6Idx = 0;
var audio8Idx = 0;
var subtitleIdx = 0;
var convert = false;
let ffmpegCommandInsert = '';
let audioIdx = 0;
let audio6Idx = 0;
let audio8Idx = 0;
let subtitleIdx = 0;
let convert = false;
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "video") {
// Check if audioIdx or subtitleIdx do NOT equal 0, if they do then it means a audio or subtitle track has already appeared before the video track so file needs to be organized.
if (audioIdx != "0" || subtitleIdx != "0") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
// Check if audioIdx or subtitleIdx do NOT equal 0
// If so then it means a audio or subtitle track has already appeared before the video track
// So file needs to be organized.
if (audioIdx !== 0 || subtitleIdx !== 0) {
convert = true;
response.infoLog += "☒ Video not first. \n";
response.infoLog += '☒ Video not first. \n';
}
// Increment videoIdx.
videoIdx++;
}
// Check if stream is audio.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") {
// Check if subtitleIdx does NOT equal 0, if it does then it means a subtitle track has already appeared before an audio track so file needs to be organized.
if (subtitleIdx != "0") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') {
// Check if subtitleIdx does NOT equal 0.
// If so then it means a subtitle track has already appeared before an audio track
// So file needs to be organized.
if (subtitleIdx !== 0) {
convert = true;
response.infoLog += "☒ Audio not second. \n";
response.infoLog += '☒ Audio not second. \n';
}
// Increment audioIdx.
audioIdx++;
audioIdx += 1;
// Check if audio track is 2 channel.
if (file.ffProbeData.streams[i].channels == "2") {
// Check if audio6Idx or audio8Idx do NOT equal 0, if they do then it means a 6 channel or 8 channel audio track has already appeared before the 2 channel audio track so file needs to be organized.
if (audio6Idx != "0" || audio8Idx != "0") {
if (file.ffProbeData.streams[i].channels === '2') {
// Check if audio6Idx or audio8Idx do NOT equal 0.
// If so then it means a 6 or 8 channel audio track has already appeared before the 2 channel audio track
// So file needs to be organized.
if (audio6Idx !== 0 || audio8Idx !== 0) {
convert = true;
response.infoLog += "☒ Audio 2ch not first. \n";
response.infoLog += '☒ Audio 2ch not first. \n';
}
// Increment audio2Idx.
audio2Idx++;
}
// Check if audio track is 6 channel.
if (file.ffProbeData.streams[i].channels == "6") {
// Check if audio8Idx does NOT equal 0, if it does then it means a 8 channel audio track has already appeared before the 6 channel audio track so file needs to be organized.
if (audio8Idx != "0") {
if (file.ffProbeData.streams[i].channels === 6) {
// Check if audio8Idx does NOT equal 0.
// If so then it means a 8 channel audio track has already appeared before the 6 channel audio track
// So file needs to be organized.
if (audio8Idx !== 0) {
convert = true;
response.infoLog += "☒ Audio 6ch not second. \n";
response.infoLog += '☒ Audio 6ch not second. \n';
}
// Increment audio6Idx.
audio6Idx++;
audio6Idx += 1;
}
// Check if audio track is 8 channel.
if (file.ffProbeData.streams[i].channels == "8") {
if (file.ffProbeData.streams[i].channels === 8) {
// Increment audio8Idx.
audio8Idx++;
audio8Idx += 1;
}
}
// Check if stream is subtitle.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') {
// Increment subtitleIdx
subtitleIdx++;
subtitleIdx += 1;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is video AND is not a mjpeg.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "video" &&
file.ffProbeData.streams[i].codec_name.toLowerCase() != "mjpeg"
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video'
&& file.ffProbeData.streams[i].codec_name.toLowerCase() !== 'mjpeg'
) {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is audio AND 2 channel.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
file.ffProbeData.streams[i].channels == "2"
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& file.ffProbeData.streams[i].channels === 2
) {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is audio AND 6 channel.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
file.ffProbeData.streams[i].channels == "6"
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& file.ffProbeData.streams[i].channels === 6
) {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is audio AND 8 channel.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
file.ffProbeData.streams[i].channels == "8"
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& file.ffProbeData.streams[i].channels === 8
) {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is audio AND not 2, 6 or 8 channel.
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio" &&
file.ffProbeData.streams[i].channels != "2" &&
file.ffProbeData.streams[i].channels != "6" &&
file.ffProbeData.streams[i].channels != "8"
file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio'
&& file.ffProbeData.streams[i].channels !== 2
&& file.ffProbeData.streams[i].channels !== 6
&& file.ffProbeData.streams[i].channels !== 8
) {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
try {
// Check if stream is subtitle.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') {
ffmpegCommandInsert += `-map 0:${i} `;
}
} catch (err) {}
} catch (err) {
// Error
}
}
// Convert file if convert variable is set to true.
if (convert == true) {
if (convert === true) {
response.processFile = true;
response.preset = `,${ffmpegCommandInsert} -c copy -max_muxing_queue_size 4096`;
response.preset = `,${ffmpegCommandInsert} -c copy -max_muxing_queue_size 9999`;
response.reQueueAfter = true;
response.infoLog +=
"☒ Streams are out of order, reorganizing streams. Video, Audio, Subtitles. \n";
response.infoLog
+= '☒ Streams are out of order, reorganizing streams. Video, Audio, Subtitles. \n';
} else {
response.infoLog += "☑ Streams are in expected order. \n ";
response.infoLog += '☑ Streams are in expected order. \n ';
response.processFile = false;
}
return response;

@ -1,67 +1,68 @@
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
function details() {
return {
id: "Tdarr_Plugin_MC93_MigzImageRemoval",
Stage: "Pre-processing",
Name: "Migz-Remove image formats from file",
Type: "Video",
Operation: "Clean",
Description: `Identify any unwanted image formats in the file and remove those streams. MJPEG & PNG \n\n`,
Version: "1.1",
id: 'Tdarr_Plugin_MC93_MigzImageRemoval',
Stage: 'Pre-processing',
Name: 'Migz-Remove image formats from file',
Type: 'Video',
Operation: 'Clean',
Description: 'Identify any unwanted image formats in the file and remove those streams. MJPEG & PNG \n\n',
Version: '1.3',
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js",
Tags: "pre-processing,ffmpeg,video only",
'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js',
Tags: 'pre-processing,ffmpeg,video only',
};
}
function plugin(file, librarySettings, inputs) {
var response = {
function plugin(file) {
const response = {
processFile: false,
preset: "",
preset: '',
handBrakeMode: false,
container: "." + file.container,
container: `.${file.container}`,
FFmpegMode: true,
reQueueAfter: true,
infoLog: "",
infoLog: '',
};
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== "video") {
if (file.fileMedium !== 'video') {
response.processFile = false;
response.infoLog += "☒File is not a video. \n";
response.infoLog += '☒File is not a video. \n';
return response;
}
// Set up required variables.
var videoIdx = 0;
var extraArguments = "";
var convert = false;
let videoIdx = 0;
let extraArguments = '';
let convert = false;
// Go through each stream in the file.
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
for (let i = 0; i < file.ffProbeData.streams.length; i++) {
// Check if stream is video.
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "video") {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') {
// Check if stream codec is mjpeg or png. Remove if so.
if (
file.ffProbeData.streams[i].codec_name == "mjpeg" ||
file.ffProbeData.streams[i].codec_name == "png"
file.ffProbeData.streams[i].codec_name === 'mjpeg'
|| file.ffProbeData.streams[i].codec_name === 'png'
) {
convert = true;
extraArguments += `-map -v:${videoIdx} `;
}
// Increment videoIdx.
videoIdx++;
videoIdx += 1;
}
}
// Convert file if convert variable is set to true.
if (convert === true) {
response.preset += `,-map 0 -c copy -max_muxing_queue_size 4096 ${extraArguments}`;
response.infoLog += `☒File has image format stream, removing. \n`;
response.preset += `,-map 0 -c copy -max_muxing_queue_size 9999 ${extraArguments}`;
response.infoLog += '☒File has image format stream, removing. \n';
response.processFile = true;
} else {
response.processFile = false;
response.infoLog +=
"☑File doesn't contain any unwanted image format streams.\n";
response.infoLog
+= "☑File doesn't contain any unwanted image format streams.\n";
}
return response;
}

@ -1,94 +1,102 @@
module.exports.dependencies = [
'request',
];
/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_MC93_MigzPlex_Autoscan",
Stage: "Post-processing",
Name: "Send request for file to be scanned by plex_autoscan.",
Type: "Video",
Operation: "",
Description: `Send request for file to be scanned by plex_autoscan. https://github.com/l3uddz/plex_autoscan \n\n`,
Version: "1.1",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js",
Tags: "3rd party,post-processing,configurable",
id: 'Tdarr_Plugin_MC93_MigzPlex_Autoscan',
Stage: 'Post-processing',
Name: 'Send request for file to be scanned by plex_autoscan.',
Type: 'Video',
Operation: '',
Description: 'Send request for file to be scanned by plex_autoscan. https://github.com/l3uddz/plex_autoscan \n\n',
Version: '1.2',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js',
Tags: '3rd party,post-processing,configurable',
Inputs: [{
name: 'autoscan_address',
tooltip: `
Enter the IP address/URL for autoscan. Must include http(s)://
\\nExample:\\n
http://192.168.0.10
\\nExample:\\n
https://subdomain.domain.tld`,
},
{
name: 'autoscan_port',
tooltip: `
Enter the port Autoscan is using, default is 3468
\\nExample:\\n
3468`,
},
{
name: 'autoscan_passkey',
tooltip: `
Inputs: [
{
name: "autoscan_address",
tooltip: `
Enter the IP address/URL for autoscan. Must include http(s)://
\\nExample:\\n
http://192.168.0.10
\\nExample:\\n
https://subdomain.domain.tld`,
},
{
name: "autoscan_port",
tooltip: `
Enter the port Autoscan is using, default is 3468
Enter the autoscan passkey.
\\nExample:\\n
3468`,
},
{
name: "autoscan_passkey",
tooltip: `
Enter the autoscan passkey.
\\nExample:\\n
9c4b81fe234e4d6eb9011cefe514d915`,
},
\\nExample:\\n
9c4b81fe234e4d6eb9011cefe514d915`,
},
],
};
};
module.exports.plugin = function plugin(file, librarySettings, inputs) {
// eslint-disable-next-line global-require,import/no-unresolved
const request = require('request');
// Set up required variables.
const ADDRESS = inputs.autoscan_address;
const PORT = inputs.autoscan_port;
const PASSKEY = inputs.autoscan_passkey;
let filepath = '';
const response = {};
filepath = `${file.file}`;
// Check if all inputs have been configured. If they haven't then exit plugin.
if (
inputs &&
inputs.autoscan_address == "" &&
inputs.autoscan_port == "" &&
inputs.autoscan_passkey == ""
inputs
&& inputs.autoscan_address === ''
&& inputs.autoscan_port === ''
&& inputs.autoscan_passkey === ''
) {
response.infoLog +=
"☒Autoscan options have not been configured, please configure all options. Skipping this plugin. \n";
response.infoLog += '☒Plugin options have not been configured, please configure options. Skipping this plugin. \n';
response.processFile = false;
return response;
}
// Take variable inputs and turn them into read only variable
const request = require("request");
const ADDRESS = inputs.autoscan_address;
const PORT = inputs.autoscan_port;
const PASSKEY = inputs.autoscan_passkey;
// Set up required variables.
var response = "";
filepath = `${file.file}`;
// Set content of request/post.
request.post(
{
headers: {
"content-type": "application/json",
},
url: `${ADDRESS}:${PORT}/${PASSKEY}`,
form: {
eventType: "Manual",
filepath: `${filepath}`,
},
request.post({
headers: {
'content-type': 'application/json',
},
url: `${ADDRESS}:${PORT}/${PASSKEY}`,
form: {
eventType: 'Manual',
filepath: `${filepath}`,
},
(error, res, body) => {
if (error) {
console.error(error);
}
console.log(`statusCode: ${res.statusCode}`);
console.log(body);
},
(error, res, body) => {
if (error) {
// eslint-disable-next-line no-console
console.error(error);
}
);
// eslint-disable-next-line no-console
console.log(`statusCode: ${res.statusCode}`);
// eslint-disable-next-line no-console
console.log(body);
});
console.log("request next");
// eslint-disable-next-line no-console
console.log('request next');
// eslint-disable-next-line no-console
console.log(request.post);
return undefined;
};

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs",

@ -1,3 +1,4 @@
/* eslint-disable */
const vaapiPrefix = ` -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi `;
module.exports.details = function details() {

@ -0,0 +1,213 @@
/* eslint-disable */
//PLugin runs multipass loudnorm filter
//first run gets the required details and stores for the next pass
//second pass applies the values
//stages
// Determined Loudnorm Values
// Applying Normalisation
// Normalisation Complete
//setup global vars
var secondPass = false;
var logOutFile = '';
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 = ''
}
const importFresh = require(rootModules + 'import-fresh');
const library = importFresh('../methods/library.js')
const ffprobePath = require(rootModules + 'ffprobe-static').path;
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation",
Name: "2 Pass Loudnorm Volume Normalisation",
Type: "Video",
Operation: "Transcode",
Description: "PLEASE READ FULL DESCRIPTION BEFORE USE \n Uses multiple passes to normalise audio streams of videos using loudnorm.\n\n The first pass will create an log file in the same directory as the video.\nSecond pass will apply the values determined in the first pass to the file.\nOutput will be MKV to allow metadata to be added for tracking normalisation stage.",
Version: "0.1",
Link: "",
Tags: "pre-processing,ffmpeg,configurable",
Inputs: [
//(Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
{
name: "i",
tooltip: `\"I\" value used in loudnorm pass \n
defaults to -23.0`, //Each line following `Example:` will be clearly formatted. \\n used for line breaks
},
{
name: "lra",
tooltip: `Desired lra value. \n Defaults to 7.0
`,
},
{
name: "tp",
tooltip: `Desired \"tp\" value. \n Defaults to -2.0
`,
},
{
name: "offset",
tooltip: `Desired "offset" value. \n Defaults to 0.0
`,
},
],
}
}
module.exports.plugin = function plugin(file, librarySettings, inputs) {
//Must return this object at some point
var response = {
processFile: false,
preset: '',
container: '.mkv',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
}
response.infoLog += ""
//grab the current file being processed and make an out file for the ffmpeg log
let currentfilename = file._id;
logOutFile = currentfilename.substr(0, currentfilename.lastIndexOf(".")) + ".out"
console.log("Log out file: " + logOutFile)
//get an updated version of the file for checking metadata
var probeData = JSON.parse(require("child_process").execSync(`ffprobe -v quiet -print_format json -show_format -show_streams "${currentfilename}"`).toString())
//setup required varibles
var loudNorm_i = -23.0
var lra = 7.0
var tp = -2.0
var offset = 0.0
//create local varibles for inputs
if (inputs !== undefined) {
if (inputs.i !== undefined) loudNorm_i = inputs.i
if (inputs.lra !== undefined) lra = inputs.lra
if (inputs.tp !== undefined) tp = inputs.tp
if (inputs.offset !== undefined) offset = inputs.offset
}
//check for previous pass tags
if (typeof probeData.format === "undefined" || typeof probeData.format.tags.NORMALISATIONSTAGE === "undefined" || probeData.format.tags.NORMALISATIONSTAGE === "" || file.forceProcessing === true) {
//no metadata found first pass is required
console.log("Searching for audio normailisation values")
response.infoLog += "Searching for required normalisation values. \n"
var loudNormInfo = "";
//Do the first pass, output the log to the out file and use a secondary output for an unchanged file to allow Tdarr to track, Set metadata stage
response.preset = `<io>-af loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json -f null NUL -map 0 -c copy -metadata NORMALISATIONSTAGE="FirstPassComplete" 2>"${logOutFile}"`
response.container = '.mkv'
response.handBrakeMode = false
response.FFmpegMode = true
response.reQueueAfter = true;
response.processFile = true
response.infoLog += "Normalisation first pass processing \n"
return response
}
if (probeData.format.tags.NORMALISATIONSTAGE === "FirstPassComplete") {
//ensure previous out file exists
if (fs.existsSync(logOutFile)) {
secondPass = true;
loudNormInfo = fs.readFileSync(logOutFile).toString();
//grab the json from the out file
var startIndex = loudNormInfo.lastIndexOf("{");
var endIndex = loudNormInfo.lastIndexOf("}");
var outValues = loudNormInfo.toString().substr(startIndex, endIndex)
response.infoLog += "Loudnorm first pass values returned: \n" + outValues
//parse the JSON
var loudNormValues = JSON.parse(outValues)
//use parsed values in second pass
response.preset = `-y<io>-af loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:measured_i=${loudNormValues.input_i}:measured_lra=${loudNormValues.input_lra}:measured_tp=${loudNormValues.input_tp}:measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} -c:a aac -b:a 192k -c:s copy -c:v copy -metadata NORMALISATIONSTAGE="Complete"`
response.container = '.mkv'
response.handBrakeMode = false
response.FFmpegMode = true
response.reQueueAfter = true;
response.processFile = true
response.infoLog += "Normalisation pass processing \n"
return response
} else {
response.infoLog += "Previous log output file is missing. Please rerun with force processing to regenerate."
response.processFile = false;
return response
}
}
if(probeData.format.tags.NORMALISATIONSTAGE === "Complete"){
response.processFile = false;
response.infoLog += "File is already marked as normalised \n"
return response
} else {
//what is this tag?
response.processFile = false;
response.infoLog += "Unknown normalisation stage tag: \n" + probeData.format.tags.NORMALISATIONSTAGE
return response
}
}
module.exports.onTranscodeSuccess = function onTranscodeSuccess(
file,
librarySettings,
inputs
) {
var response = {
file,
removeFromDB: false,
updateDB: true,
};
if (secondPass) {
response.infoLog += "Audio normalisation complete. \n"
//remove old out file
if (fs.existsSync(logOutFile)) {
fs.unlinkSync(logOutFile);
}
return response;
}
else {
response.infoLog += "Audio normalisation first pass complete. \n"
return response;
}
};
module.exports.onTranscodeError = function onTranscodeError(
file,
librarySettings,
inputs
) {
console.log("Failed to normalise audio");
//Optional response if you need to modify database
var response = {
file,
removeFromDB: false,
updateDB: false,
};
return response;
};

@ -0,0 +1,54 @@
/* eslint-disable */
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 = ''
}
const importFresh = require(rootModules + 'import-fresh');
const library = importFresh('../methods/library.js')
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_O8O0dCTlb_Set_File_Permissions_For_UnRaid",
Name: "Set file permissions for UnRaid",
Type: "Video",
Operation: "Transcode",
Description: "Sets file permissions using chown nobody:users to prevent lock from root. Use at end of stack. ",
Version: "",
Link: "",
Tags: "post-processing"
}
}
module.exports.plugin = function plugin(file) {
//Must return this object at some point
var response = {
processFile: false,
preset: '',
container: '.mkv',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: true,
infoLog: '',
}
response.infoLog += ""
if ((true) || file.forceProcessing === true) {
require("child_process").execSync(`chown nobody:users "${file._id}"`)
response.preset = ''
response.container = '.mkv'
response.handBrakeMode = false
response.FFmpegMode = true
response.reQueueAfter = true;
response.processFile = false
response.infoLog += "File permissions set \n"
return response
}
}

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30",

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs",
@ -5,7 +6,7 @@ module.exports.details = function details() {
Name: "Transcode Specific Audio Stream Codecs",
Type: "",
Operation: "Transcode",
Description: `[TESTING][Contains built-in filter] Transcode audio streams with specific codecs into another codec. \n\n`,
Description: `[Contains built-in filter] Transcode audio streams with specific codecs into another codec. \n\n`,
Version: "1.00",
Link: "",
Tags: "pre-processing,audio only,ffmpeg,configurable",
@ -34,6 +35,16 @@ module.exports.details = function details() {
\\nExample:\\n
eac3
`,
},
{
name: "bitrate",
tooltip: `Specify the transcoded audio bitrate (optional):
\\n 384k
\\n 640k
\\nExample:\\n
640k
`,
},
],
@ -53,7 +64,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
infoLog: "",
};
if (inputs.codecs_to_transcode === undefined || inputs.codec === undefined) {
if (inputs.codecs_to_transcode === undefined || inputs.codec === undefined ) {
response.processFile = false;
response.infoLog += "☒ Inputs not entered! \n";
return response;
@ -81,6 +92,9 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
)
) {
ffmpegCommand += ` -map 0:${i} -c:${i} ${encoder} `;
if (inputs.bitrate != undefined) {
ffmpegCommand += `-b:a ${inputs.bitrate} `;
}
hasStreamsToTranscode = true;
} else if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") {
ffmpegCommand += ` -map 0:${i}`;
@ -104,4 +118,4 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
response.infoLog += `☒ File has streams which aren't in desired codec! \n`;
return response;
}
};
};

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3",

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_c0r1_SetDefaultAudioStream",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_drdd_standardise_all_in_one",
@ -230,8 +231,8 @@ function buildAudioConfiguration(_inputs, file, logger) {
function buildSubtitleConfiguration(inputs, file, logger) {
var configuration = new Configurator(["-c:s copy"]);
if (!inputs.wanted_subtitle_languages) return configuration;
var languages = inputs.wanted_subtitle_languages.split(",");
if (languages.length === 0) return configuration;
loopOverStreamsOfType(file, "subtitle", function (stream, id) {
if (stream.codec_name === "eia_608") {
@ -346,6 +347,8 @@ function buildVideoConfiguration(inputs, file, logger) {
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 == "mpeg4") {
configuration.AddInputSetting("-c:v mpeg4_cuvid");
} else if (file.video_codec_name == "vc1") {
configuration.AddInputSetting("-c:v vc1_cuvid");
} else if (file.video_codec_name == "vp8") {
@ -360,7 +363,7 @@ function buildVideoConfiguration(inputs, file, logger) {
if (
(inputs.qsv !== "true" && inputs.nvenc !== "true") ||
stream.codec_name === "mpeg4"
(inputs.qsv === "true" && stream.codec_name === "mpeg4")
) {
configuration.RemoveOutputSetting("-c:v copy");
configuration.AddOutputSetting(`-c:v libx265 ${bitrateSettings}`);

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta",

@ -1,3 +1,4 @@
/* eslint-disable */
const fs = require("fs");
const execSync = require("child_process").execSync;

@ -1,3 +1,4 @@
/* eslint-disable */
const exec = require("child_process").exec;
const fs = require("fs");

@ -1,3 +1,4 @@
/* eslint-disable */
const fs = require("fs");
const execSync = require("child_process").execSync;

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs",

@ -0,0 +1,118 @@
function details() {
return {
id: 'Tdarr_Plugin_henk_Add_Specific_Audio_Codec',
Stage: 'Pre-processing',
Name: '[MKV ONLY] Transcode given codec to other given codec and keep original',
Type: 'Audio',
Operation: 'Transcode',
Description: 'Re-encodes all audio tracks in a given codec to another given codec and keeps original.',
Version: '1.01',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js',
Tags: 'post-processing,configurable',
Inputs: [{
name: 'input_codecs',
tooltip: 'Comma separated list of input codecs to be processed. Defaults to dts.'
+ '\\nExample:\\n'
+ 'dts,aac,ac3',
}, {
name: 'output_codec',
tooltip: 'FFMPEG encoder used for the output of the new tracks. Defaults to ac3.',
}, {
name: 'bitrate',
tooltip: 'Specifies the (stereo) bitrate for the new audio codec. Defaults to 128k. Only numbers.',
}, {
name: 'auto_adjust',
tooltip: '[true/false] Multi-channel audio requires a higher bitrate for the same quality, '
+ 'do you want the plugin to calculate this? (bitrate * (channels / 2))',
}, {
name: 'custom_bitrate_input',
tooltip: 'DIRECT ACCESS TO FFMPEG, USE WITH CAUTION. If filled, can be used for custom bitrate arguments.',
}],
};
}
function plugin(file, librarySettings, inputs) {
const response = {
processFile: false,
preset: ', -c copy -map 0:v ',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: '',
};
// Check if file is a video. If it isn't then exit plugin.
if (file.fileMedium !== 'video') {
response.infoLog += '☒File is not video \n';
return response;
}
// Check if file is mkv. If it isn't then exit plugin.
if (file.container !== 'mkv') {
response.infoLog += '☒File is not mkv \n';
return response;
}
let convertCount = 0;
let streamCount = 0;
let indexCount = 0;
let killPlugin = false;
const inputCodecs = inputs.input_codecs ? inputs.input_codecs.split(',') : ['dts'];
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
const currStream = file.ffProbeData.streams[i];
if (currStream.tags.COPYRIGHT) {
if (currStream.tags.COPYRIGHT === 'henk_asac') {
killPlugin = true;
}
}
}
if (killPlugin) {
response.infoLog
+= '☑File has already been processed by this plugin.\n';
return response;
}
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
const currStream = file.ffProbeData.streams[i];
if (currStream.codec_type.toLowerCase() === 'audio') {
response.preset += ` -map 0:a:${indexCount}? -c:a:${streamCount} copy `;
streamCount += 1;
if (inputCodecs.includes(currStream.codec_name.toLowerCase())) {
convertCount += 1;
let bitrate = `-b:a:${streamCount} `;
if (inputs.custom_bitrate_input) {
bitrate += inputs.custom_bitrate_input;
} else if (inputs.bitrate) {
bitrate += `${inputs.auto_adjust ? (inputs.bitrate * (currStream.channels / 2)) : inputs.bitrate}k`;
} else {
bitrate = '128k';
}
response.preset += ` -map 0:a:${indexCount}? -c:a:${streamCount} ${inputs.output_codec || 'ac3'} ${bitrate} `
+ `-metadata:s:a:${streamCount} title="" -metadata:s:a:${streamCount} copyright="henk_asac" `
+ `-disposition:a:${streamCount} 0`;
streamCount += 1;
}
indexCount += 1;
}
}
if (convertCount > 0) {
response.processFile = true;
response.container = `.${file.container}`;
response.reQueueAfter = true;
response.preset += ' -map 0:s? ';
} else {
response.infoLog
+= '☑File doesn\'t contain audio tracks with the specified codec.\n';
}
return response;
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_lmg1_Reorder_Streams",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV",

@ -0,0 +1,124 @@
const fs = require('fs');
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_rr01_drpeppershaker_extract_subs_to_SRT',
Stage: 'Pre-processing',
Name: 'drpeppershaker Extract embedded subtitles and optionally remove them',
Type: 'Video',
Operation: 'Transcode',
Description: 'This plugin extracts embedded subs in one pass inside Tdarr and will optionally remove them. \n\n '
+ 'All processes happen within Tdarr without the use of any exec() functions, which lets the progress bar '
+ 'report the status correctly. AND all subtitles are extracted in one pass, which is much faster than '
+ 'other options.',
// Created by drpeppershaker with help from reddit user /u/jakejones48, lots of
// improvements made after looking at "Tdarr_Plugin_078d" by HaveAGitGat.
Version: '1.04',
Link: '',
Tags: 'pre-processing,subtitle only,ffmpeg,configurable',
Inputs: [
{
name: 'remove_subs',
tooltip: `Do you want to remove subtitles after they are extracted?
\\nExample:\\n
yes
\\nExample:\\n
no
`,
},
],
};
};
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: true,
preset: '',
container: `.${file.container}`,
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: '',
};
if (inputs.remove_subs === undefined) {
response.processFile = false;
response.infoLog += '☒ Inputs not entered! \n';
return response;
}
const subsArr = file.ffProbeData.streams.filter((row) => row.codec_name === 'subrip');
if (subsArr.length === 0) {
response.infoLog += 'No subs in file to extract!\n';
response.processFile = false;
return response;
}
response.infoLog += 'Found subs to extract!\n';
let command = '-y,';
for (let i = 0; i < subsArr.length; i += 1) {
const subStream = subsArr[i];
let lang = '';
let title = 'none';
if (subStream.tags) {
lang = subStream.tags.language;
}
if (subStream.tags.title) {
title = subStream.tags.title;
}
const { originalLibraryFile } = otherArguments;
let subsFile = '';
// for Tdarr V2 (2.00.05+)
if (originalLibraryFile && originalLibraryFile.file) {
subsFile = originalLibraryFile.file;
} else {
// for Tdarr V1
subsFile = file.file;
}
subsFile = subsFile.split('.');
subsFile[subsFile.length - 2] += `.${lang}`;
subsFile[subsFile.length - 1] = 'srt';
subsFile = subsFile.join('.');
const { index } = subStream;
if (fs.existsSync(`${subsFile}`)) {
response.infoLog += `${lang}.srt already exists. Skipping!\n`;
} else if (title.toLowerCase().includes('commentary') || title.toLowerCase().includes('description')) {
response.infoLog += `Stream ${i} ${lang}.srt is a ${title} track. Skipping!\n`;
} else {
response.infoLog += `Extracting ${lang}.srt\n`;
command += ` -map 0:${index} "${subsFile}"`;
}
}
if (command === '-y,') {
response.infoLog += 'All subs already extracted!\n';
if (inputs.remove_subs === 'no') {
response.processFile = false;
return response;
}
}
response.preset = command;
if (inputs.remove_subs === 'yes') {
response.preset += ' -map 0 -map -0:s -c copy';
}
if (inputs.remove_subs === 'no') {
response.preset += ' -map 0 -c copy';
}
return response;
};

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_s710_nick_h265_nvenc_4K",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_s7x8_winsome_h265",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_s7x9_winsome_h265_10bit",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_s7x9_winsome_h265_nvenc",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_sdd3_Remove_Commentary_Tracks",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio",

@ -0,0 +1,68 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_vdka_Remove_DataStreams",
Stage: "Pre-processing",
Name: "Remove Data Streams ",
Type: "Video",
Description: `[Contains built-in filter] This plugin removes data streams if detected. The output container is the same as the original. Helps with issues like bin_data making files impossible to process. \n\n`,
Version: "1.00",
Link:
"https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js",
Tags: "pre-processing,ffmpeg",
};
}
function plugin(file) {
//Must return this object
var response = {
processFile: false,
preset: "",
container: ".mp4",
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: false,
infoLog: "",
};
if (file.fileMedium !== "video") {
console.log("File is not video");
response.infoLog += "☒File is not video \n";
response.processFile = false;
return response;
} else {
response.FFmpegMode = true;
response.container = ".mkv";
var hasData = false;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (
file.ffProbeData.streams[i].codec_type.toLowerCase() == "data"
) {
hasData = true;
}
} catch (err) {}
}
if (hasData) {
response.infoLog += "☒File has data streams \n";
response.preset = ",-map 0 -c copy -dn -map_chapters -1";
response.reQueueAfter = true;
response.processFile = true;
return response;
} else {
response.infoLog += "☑File has no data streams! \n";
}
response.infoLog += "☑File meets conditions! \n";
return response;
}
}
module.exports.details = details;
module.exports.plugin = plugin;

@ -0,0 +1,227 @@
/* eslint-disable */
function details () {
return {
id: 'Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE',
Stage: 'Pre-processing',
Name: 'Tiered FFMPEG+NVENC CQ:V BASED CONFIGURABLE',
Type: 'Video',
Operation: 'Transcode',
Description: `[Contains built-in filter] This plugin uses different CQ:V values (similar to crf but for nvenc) depending on resolution,
the CQ:V value is configurable per resolution.
FFmpeg Preset can be configured, uses slow by default.
ALL OPTIONS MUST BE CONFIGURED UNLESS MARKED OPTIONAL!
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_NVENC_CQV_BASED_CONFIGURABLE.js',
Tags: 'pre-processing,ffmpeg,video only,nvenc h265,configurable',
Inputs: [
{
name: 'sdCQV',
tooltip: `Enter the CQ:V value you want for 480p and 576p content.
\\nExample:\\n
21`
},
{
name: 'hdCQV',
tooltip: `Enter the CQ:V value you want for 720p content.
\\nExample:\\n
23`
},
{
name: 'fullhdCQV',
tooltip: `Enter the CQ:V value you want for 1080p content.
\\nExample:\\n
25`
},
{
name: 'uhdCQV',
tooltip: `Enter the CQ:V value you want for 4K/UHD/2160p content.
\\nExample:\\n
28`
},
{
name: 'bframe',
tooltip: `Specify amount of b-frames to use, 0-5. Use 0 to disable. (GPU must support this, turing and newer supports this, except for the 1650)
\\nExample:\\n
3`
},
{
name: 'ffmpeg_preset',
tooltip: `OPTIONAL, DEFAULTS TO SLOW IF NOT SET
\\n Enter the ffmpeg preset you want, leave blank for default (slow)
\\n This only applies if video is transcoded, video already in h264 will not be transcoded with this setting
\\nExample:\\n
slow
\\nExample:\\n
medium
\\nExample:\\n
fast
\\nExample:\\n
veryfast`
}
]
}
}
module.exports.plugin = function plugin (file, librarySettings, inputs) {
var transcode = 0 //if this var changes to 1 the file will be transcoded
var subcli = `-c:s copy`
var maxmux = ''
var map = '-map 0'
var cqvinuse = ''
//default values that will be returned
var response = {
processFile: false,
preset: '',
container: '.mkv',
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: true,
infoLog: '',
maxmux: false
}
//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
} else {
// bitrateprobe = file.ffProbeData.streams[0].bit_rate;
response.infoLog += '☑File is a video! \n'
}
//check if the file is already hevc, it will not be transcoded if true and the function will be stopped immediately
if (file.ffProbeData.streams[0].codec_name == 'hevc') {
response.processFile = false
response.infoLog += '☑File is already in hevc! \n'
return response
}
// Check if preset is configured, default to slow if not
var ffmpeg_preset
if (inputs.ffmpeg_preset === undefined) {
ffmpeg_preset = `slow`
response.infoLog += '☑Preset not set, defaulting to slow\n'
} else {
ffmpeg_preset = `${inputs.ffmpeg_preset}`
response.infoLog += `☑Preset set as ${inputs.ffmpeg_preset}\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 == 'h264') {
if (file.ffProbeData.streams[0].profile != 'High 10') {
//Remove HW Decoding for High 10 Profile
response.preset = `-c:v h264_cuvid`
}
} 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`
}
// skipping this one because it's empty
// else if (file.video_codec_name == 'mpeg4') {
// response.preset = ``
// }
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`
} else if (file.video_codec_name == 'vp9') {
response.preset = `-c:v vp9_cuvid`
}
//Set Subtitle Var before adding encode cli
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
if (
file.ffProbeData.streams[i].codec_name.toLowerCase() == 'mov_text' &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == 'subtitle'
) {
subcli = `-c:s srt`
}
} catch (err) {}
//mitigate TrueHD audio causing Too many packets error
try {
if (
file.ffProbeData.streams[i].codec_name.toLowerCase() == 'truehd' ||
(file.ffProbeData.streams[i].codec_name.toLowerCase() == 'dts' &&
file.ffProbeData.streams[i].profile.toLowerCase() == 'dts-hd ma') ||
(file.ffProbeData.streams[i].codec_name.toLowerCase() == 'aac' &&
file.ffProbeData.streams[i].sample_rate.toLowerCase() == '44100' &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == 'audio')
) {
maxmux = ` -max_muxing_queue_size 9999`
}
} catch (err) {}
//mitigate errors due to embeded pictures
try {
if (
(file.ffProbeData.streams[i].codec_name.toLowerCase() == 'png' ||
file.ffProbeData.streams[i].codec_name.toLowerCase() == 'bmp' ||
file.ffProbeData.streams[i].codec_name.toLowerCase() == 'mjpeg') &&
file.ffProbeData.streams[i].codec_type.toLowerCase() == 'video'
) {
map = `-map 0:v:0 -map 0:a -map 0:s?`
}
} catch (err) {}
}
//file will be encoded if the resolution is 480p or 576p
//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}`
transcode = 1
}
//file will be encoded if the resolution is 720p
//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}`
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}`
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}`
transcode = 1
}
//check if the file is eligible for transcoding
//if true the neccessary response values will be changed
if (transcode == 1) {
response.processFile = true
response.FFmpegMode = true
response.reQueueAfter = true
response.infoLog += `☑File is ${file.video_resolution}, using CQ:V value of ${cqvinuse}!\n`
response.infoLog += `☒File is not hevc!\n`
response.infoLog += `File is being transcoded!\n`
}
return response
}
module.exports.details = details

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_x7ab_Remove_Subs",

@ -1,3 +1,4 @@
/* eslint-disable */
function details() {
return {
id: "Tdarr_Plugin_x7ac_Remove_Closed_Captions",

@ -1,142 +1,137 @@
function details() {
return {
id: "Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium",
Stage: "Pre-processing",
Name: "TheRealShadoh FFmpeg Subs Medium, video MP4, audio AAC, keep subs. ",
Type: "Video",
Description: `[Contains built-in filter] This plugin transcodes into H264 using FFmpeg's 'Medium' preset if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
id: 'Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium',
Stage: 'Pre-processing',
Name: 'TheRealShadoh FFmpeg Subs Medium, video MP4, audio AAC, keep subs. ',
Type: 'Video',
Description: '[Contains built-in filter] This plugin transcodes into H264 using FFmpeg\'s '
+ '\'Medium\' preset if the file is not in H264 already. It maintains all subtitles. It removes metadata'
+ ` (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
`,
Version: "1.00",
Version: '1.00',
Link:
"https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js",
Tags: "pre-processing,ffmpeg,h264",
'https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js',
Tags: 'pre-processing,ffmpeg,h264',
};
}
function plugin(file) {
//Must return this object
// Must return this object
var response = {
const response = {
processFile: false,
preset: "",
container: ".mp4",
preset: '',
container: '.mp4',
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
if (file.fileMedium !== "video") {
console.log("File is not video");
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += "☒File is not video \n";
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
} else {
var jsonString = JSON.stringify(file);
var hasSubs = false;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
let streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() == "subtitle" &&
streamData.codec_name != "mov_text"
) {
hasSubs = true;
}
} catch (err) {}
}
if (file.ffProbeData.streams[0].codec_name != "h264") {
response.infoLog += "☒File is not in h264! \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset medium -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File is already in h264! \n";
}
///
if (
file.meta.Title != undefined &&
!jsonString.includes("aac") &&
hasSubs
) {
response.infoLog += "☒File has title metadata and no aac and subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac") && hasSubs) {
response.infoLog += "☒File has no aac track and has subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
const jsonString = JSON.stringify(file);
let hasSubs = false;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
try {
const streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() === 'subtitle'
&& streamData.codec_name !== 'mov_text'
) {
hasSubs = true;
}
} catch (err) {
// err
}
}
if (file.meta.Title != undefined && hasSubs) {
response.infoLog += "☒File has title and has subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (file.ffProbeData.streams[0].codec_name !== 'h264') {
response.infoLog += '☒File is not in h264! \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a '
+ '-c:v libx264 -preset medium -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File is already in h264! \n';
///
if (
file.meta.Title !== undefined
&& !jsonString.includes('aac')
&& hasSubs
) {
response.infoLog += '☒File has title metadata and no aac and subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
///
if (file.meta.Title != undefined) {
response.infoLog += "☒File has title metadata \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no title metadata \n";
}
if (!jsonString.includes('aac') && hasSubs) {
response.infoLog += '☒File has no aac track and has subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac")) {
response.infoLog += "☒File has no aac track \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has aac track \n";
}
if (file.meta.Title !== undefined && hasSubs) {
response.infoLog += '☒File has title and has subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (hasSubs) {
response.infoLog += "☒File has incompatible subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no/compatible subs \n";
}
///
if (file.meta.Title !== undefined) {
response.infoLog += '☒File has title metadata \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no title metadata \n';
if (!jsonString.includes('aac')) {
response.infoLog += '☒File has no aac track \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has aac track \n';
response.infoLog += "☑File meets conditions! \n";
if (hasSubs) {
response.infoLog += '☒File has incompatible subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no/compatible subs \n';
response.infoLog += '☑File meets conditions! \n';
return response;
}
module.exports.details = details;

@ -1,3 +1,9 @@
/* eslint-disable */
module.exports.dependencies = [
'fs-extra',
];
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_z18s_rename_files_based_on_codec",
@ -5,7 +11,7 @@ module.exports.details = function details() {
Name: "Rename based on codec",
Type: "Video",
Operation: "",
Description: `[TESTING][Contains built-in filter]This plugin renames 264 files to 265 or vice versa depending on codec. \n\n`,
Description: `[Contains built-in filter]This plugin renames 264 files to 265 or vice versa depending on codec. \n\n`,
Version: "1.00",
Link: "",
Tags: "post-processing",

@ -1,33 +1,39 @@
module.exports.dependencies = [
'fs-extra',
];
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution",
Stage: "Post-processing",
Name: "Rename based on codec and resolution",
Type: "Video",
Operation: "",
Description: `[TESTING][Contains built-in filter]This plugin renames files depending on codec and resolution\n\n`,
Version: "1.00",
Link: "",
Tags: "post-processing",
id: 'Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution',
Stage: 'Post-processing',
Name: 'Rename based on codec and resolution',
Type: 'Video',
Operation: '',
Description: '[Contains built-in filter]This plugin renames files depending on codec and resolution\n\n',
Version: '1.00',
Link: '',
Tags: 'post-processing',
};
};
module.exports.plugin = function plugin(file, librarySettings, inputs) {
module.exports.plugin = function plugin(file) {
try {
const fs = require("fs");
const path = require("path");
let rootModules
if (fs.existsSync(path.join(process.cwd(), "/npm"))) {
rootModules = path.join(process.cwd(), "/npm/node_modules/");
// 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 = "";
rootModules = '';
}
const fsextra = require(rootModules + "fs-extra");
let fileNameOld = file._id;
// eslint-disable-next-line global-require,import/no-dynamic-require
const fsextra = require(`${rootModules}fs-extra`);
const fileNameOld = file._id;
let resolutions = {
const resolutions = {
_480p: '480p',
_576p: '576p',
_720p: '720p',
@ -35,30 +41,29 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
_4KUHD: '4k',
_DCI4K: '4k',
_8KUHD: '8k',
_Other: 'Other'
}
_Other: 'Other',
};
//only process if properties available
// only process if properties available
if (file.ffProbeData.streams[0].codec_name && file.video_resolution) {
const resolution = `_${file.video_resolution}`;
const resShouldBe = resolutions[resolution];
const codecShouldBe = file.ffProbeData.streams[0].codec_name;
let resolution = '_' + file.video_resolution
let resShouldBe = resolutions[resolution]
let codecShouldBe = file.ffProbeData.streams[0].codec_name
//Remove container from processing
// Remove container from processing
let fileName = file._id;
let parts = fileName.split('/')
fileName = parts[parts.length - 1]
parts.splice(parts.length - 1, 1)
parts = parts.join('/')
fileName = fileName.split('.')
let container = fileName[fileName.length - 1]
fileName.splice(fileName.length - 1, 1)
fileName = fileName.join('.')
//put term substrings below strings (i.e. '480' below '480p')
let terms = [
let parts = fileName.split('/');
fileName = parts[parts.length - 1];
parts.splice(parts.length - 1, 1);
parts = parts.join('/');
fileName = fileName.split('.');
const container = fileName[fileName.length - 1];
fileName.splice(fileName.length - 1, 1);
fileName = fileName.join('.');
// put term substrings below strings (i.e. '480' below '480p')
const terms = [
'480p',
'480',
'576p',
@ -76,60 +81,63 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
'264',
'h265',
'265',
'hevc'
]
'hevc',
];
//clean up res and codec terms from name
for (let i = 0; i < terms.length; i++) {
// clean up res and codec terms from name
for (let i = 0; i < terms.length; i += 1) {
// eslint-disable-next-line no-constant-condition
while (true) {
let idx = fileName.indexOf(terms[i]);
const idx = fileName.indexOf(terms[i]);
if (idx === -1) {
break
break;
} else {
let length = terms[i].length
fileName = fileName.split('')
const { length } = terms[i];
fileName = fileName.split('');
fileName.splice(idx, length);
fileName = fileName.join('')
fileName = fileName.join('');
}
}
}
if (resShouldBe === 'Other') {
fileName = parts + '/' + fileName + '_' + codecShouldBe + '.' + container
fileName = `${parts}/${fileName}_${codecShouldBe}.${container}`;
} else {
fileName = parts + '/' + fileName + '_' + resShouldBe + '_' + codecShouldBe + '.' + container
fileName = `${parts}/${fileName}_${resShouldBe}_${codecShouldBe}.${container}`;
}
//clean up word breakers
let breakers = [
// clean up word breakers
const breakers = [
'.',
'_',
'-',
]
];
fileName = fileName.split('')
for (let i = 0; i < fileName.length; i++) {
for (let j = 0; j < breakers.length; j++) {
for (let k = 0; k < breakers.length; k++) {
fileName = fileName.split('');
for (let i = 0; i < fileName.length; i += 1) {
for (let j = 0; j < breakers.length; j += 1) {
for (let k = 0; k < breakers.length; k += 1) {
if (fileName[i] === breakers[j] && fileName[i + 1] === breakers[k]) {
fileName.splice(i, 1);
i--
i -= 1;
}
}
}
}
fileName = fileName.join('')
fileName = fileName.join('');
file._id = fileName
file.file = fileName
// eslint-disable-next-line no-param-reassign
file._id = fileName;
// eslint-disable-next-line no-param-reassign
file.file = fileName;
if (fileNameOld != file._id) {
if (fileNameOld !== file._id) {
fsextra.moveSync(fileNameOld, file._id, {
overwrite: true,
});
let response = {
const response = {
file,
removeFromDB: false,
updateDB: true,
@ -139,6 +147,9 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.log(err);
}
return undefined;
};

@ -1,143 +1,137 @@
function details() {
return {
id: "Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast",
Stage: "Pre-processing",
Name: "TheRealShadoh FFmpeg Subs Fast, video MP4, audio AAC, keep subs. ",
Type: "Video",
Description: `[Contains built-in filter] This plugin transcodes into H264 using FFmpeg's 'Fast' preset if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
id: 'Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast',
Stage: 'Pre-processing',
Name: 'TheRealShadoh FFmpeg Subs Fast, video MP4, audio AAC, keep subs. ',
Type: 'Video',
Description: '[Contains built-in filter] This plugin transcodes into H264 using '
+ 'FFmpeg\'s \'Fast\' preset if the file is not in H264 already. It maintains all subtitles. '
+ `It removes metadata (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
`,
Version: "1.00",
Version: '1.00',
Link:
"https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js",
Tags: "pre-processing,ffmpeg,h264",
'https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js',
Tags: 'pre-processing,ffmpeg,h264',
};
}
function plugin(file) {
//Must return this object
// Must return this object
var response = {
const response = {
processFile: false,
preset: "",
container: ".mp4",
preset: '',
container: '.mp4',
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
if (file.fileMedium !== "video") {
console.log("File is not video");
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += "☒File is not video \n";
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
} else {
var jsonString = JSON.stringify(file);
var hasSubs = false;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
let streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() == "subtitle" &&
streamData.codec_name != "mov_text"
) {
hasSubs = true;
}
} catch (err) {}
}
if (file.ffProbeData.streams[0].codec_name != "h264") {
response.infoLog += "☒File is not in h264! \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset fast -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File is already in h264! \n";
}
///
if (
file.meta.Title != undefined &&
!jsonString.includes("aac") &&
hasSubs
) {
response.infoLog += "☒File has title metadata and no aac and subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac") && hasSubs) {
response.infoLog += "☒File has no aac track and has subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (file.meta.Title != undefined && hasSubs) {
response.infoLog += "☒File has title and has subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
const jsonString = JSON.stringify(file);
let hasSubs = false;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
try {
const streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() === 'subtitle'
&& streamData.codec_name !== 'mov_text'
) {
hasSubs = true;
}
} catch (err) {
// err
}
}
///
if (file.meta.Title != undefined) {
response.infoLog += "☒File has title metadata \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no title metadata \n";
}
if (file.ffProbeData.streams[0].codec_name !== 'h264') {
response.infoLog += '☒File is not in h264! \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset fast -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File is already in h264! \n';
///
if (
file.meta.Title !== undefined
&& !jsonString.includes('aac')
&& hasSubs
) {
response.infoLog += '☒File has title metadata and no aac and subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac")) {
response.infoLog += "☒File has no aac track \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has aac track \n";
}
if (!jsonString.includes('aac') && hasSubs) {
response.infoLog += '☒File has no aac track and has subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (hasSubs) {
response.infoLog += "☒File has incompatible subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no/compatible subs \n";
}
if (file.meta.Title !== undefined && hasSubs) {
response.infoLog += '☒File has title and has subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += "☑File meets conditions! \n";
///
if (file.meta.Title !== undefined) {
response.infoLog += '☒File has title metadata \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no title metadata \n';
if (!jsonString.includes('aac')) {
response.infoLog += '☒File has no aac track \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has aac track \n';
if (hasSubs) {
response.infoLog += '☒File has incompatible subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no/compatible subs \n';
response.infoLog += '☑File meets conditions! \n';
return response;
}
module.exports.details = details;

@ -1,143 +1,137 @@
function details() {
return {
id: "Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow",
Stage: "Pre-processing",
Name: "TheRealShadoh FFmpeg Subs Slow, video MP4, audio AAC, keep subs. ",
Type: "Video",
Description: `[Contains built-in filter] This plugin transcodes into H264 using FFmpeg's 'Slow' preset if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
id: 'Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow',
Stage: 'Pre-processing',
Name: 'TheRealShadoh FFmpeg Subs Slow, video MP4, audio AAC, keep subs. ',
Type: 'Video',
Description: '[Contains built-in filter] This plugin transcodes into H264 using FFmpeg\'s \'Slow\' preset'
+ ' if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), '
+ `and maintains all audio tracks. The output container is MP4. \n\n
`,
Version: "1.00",
Version: '1.00',
Link:
"https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js",
Tags: "pre-processing,ffmpeg,h264",
'https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js',
Tags: 'pre-processing,ffmpeg,h264',
};
}
function plugin(file) {
//Must return this object
// Must return this object
var response = {
const response = {
processFile: false,
preset: "",
container: ".mp4",
preset: '',
container: '.mp4',
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
if (file.fileMedium !== "video") {
console.log("File is not video");
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += "☒File is not video \n";
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
} else {
var jsonString = JSON.stringify(file);
var hasSubs = false;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
let streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() == "subtitle" &&
streamData.codec_name != "mov_text"
) {
hasSubs = true;
}
} catch (err) {}
}
if (file.ffProbeData.streams[0].codec_name != "h264") {
response.infoLog += "☒File is not in h264! \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset slow -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File is already in h264! \n";
}
///
if (
file.meta.Title != undefined &&
!jsonString.includes("aac") &&
hasSubs
) {
response.infoLog += "☒File has title metadata and no aac and subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac") && hasSubs) {
response.infoLog += "☒File has no aac track and has subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (file.meta.Title != undefined && hasSubs) {
response.infoLog += "☒File has title and has subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
const jsonString = JSON.stringify(file);
let hasSubs = false;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
try {
const streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() === 'subtitle'
&& streamData.codec_name !== 'mov_text'
) {
hasSubs = true;
}
} catch (err) {
// err
}
}
///
if (file.meta.Title != undefined) {
response.infoLog += "☒File has title metadata \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no title metadata \n";
}
if (file.ffProbeData.streams[0].codec_name !== 'h264') {
response.infoLog += '☒File is not in h264! \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset slow -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File is already in h264! \n';
///
if (
file.meta.Title !== undefined
&& !jsonString.includes('aac')
&& hasSubs
) {
response.infoLog += '☒File has title metadata and no aac and subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac")) {
response.infoLog += "☒File has no aac track \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has aac track \n";
}
if (!jsonString.includes('aac') && hasSubs) {
response.infoLog += '☒File has no aac track and has subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (hasSubs) {
response.infoLog += "☒File has incompatible subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no/compatible subs \n";
}
if (file.meta.Title !== undefined && hasSubs) {
response.infoLog += '☒File has title and has subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += "☑File meets conditions! \n";
///
if (file.meta.Title !== undefined) {
response.infoLog += '☒File has title metadata \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no title metadata \n';
if (!jsonString.includes('aac')) {
response.infoLog += '☒File has no aac track \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has aac track \n';
if (hasSubs) {
response.infoLog += '☒File has incompatible subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no/compatible subs \n';
response.infoLog += '☑File meets conditions! \n';
return response;
}
module.exports.details = details;

@ -1,144 +1,139 @@
function details() {
return {
id: "Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast",
Stage: "Pre-processing",
id: 'Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast',
Stage: 'Pre-processing',
Name:
"TheRealShadoh FFmpeg Subs VeryFast, video MP4, audio AAC, keep subs. ",
Type: "Video",
Description: `[Contains built-in filter] This plugin transcodes into H264 using FFmpeg's 'VeryFast' preset if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), and maintains all audio tracks. The output container is MP4. \n\n
'TheRealShadoh FFmpeg Subs VeryFast, video MP4, audio AAC, keep subs. ',
Type: 'Video',
Description: '[Contains built-in filter] This plugin transcodes into H264 using FFmpeg\'s \'VeryFast\' preset '
+ 'if the file is not in H264 already. It maintains all subtitles. It removes metadata (if a title exists), '
+ `and maintains all audio tracks. The output container is MP4. \n\n
`,
Version: "1.00",
Version: '1.00',
Link:
"https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_Veryfast.js",
Tags: "pre-processing,ffmpeg,h264",
'https://github.com/TheRealShadoh/Tdarr_Plugins/blob/master/Community/'
+ 'Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_Veryfast.js',
Tags: 'pre-processing,ffmpeg,h264',
};
}
function plugin(file) {
//Must return this object
// Must return this object
var response = {
const response = {
processFile: false,
preset: "",
container: ".mp4",
preset: '',
container: '.mp4',
handBrakeMode: false,
FFmpegMode: false,
reQueueAfter: false,
infoLog: "",
infoLog: '',
};
if (file.fileMedium !== "video") {
console.log("File is not video");
if (file.fileMedium !== 'video') {
// eslint-disable-next-line no-console
console.log('File is not video');
response.infoLog += "☒File is not video \n";
response.infoLog += '☒File is not video \n';
response.processFile = false;
return response;
} else {
var jsonString = JSON.stringify(file);
var hasSubs = false;
for (var i = 0; i < file.ffProbeData.streams.length; i++) {
try {
let streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() == "subtitle" &&
streamData.codec_name != "mov_text"
) {
hasSubs = true;
}
} catch (err) {}
}
if (file.ffProbeData.streams[0].codec_name != "h264") {
response.infoLog += "☒File is not in h264! \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v libx264 -preset veryfast -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File is already in h264! \n";
}
///
if (
file.meta.Title != undefined &&
!jsonString.includes("aac") &&
hasSubs
) {
response.infoLog += "☒File has title metadata and no aac and subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac") && hasSubs) {
response.infoLog += "☒File has no aac track and has subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (file.meta.Title != undefined && hasSubs) {
response.infoLog += "☒File has title and has subs \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
const jsonString = JSON.stringify(file);
let hasSubs = false;
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
try {
const streamData = file.ffProbeData.streams[i];
if (
streamData.codec_type.toLowerCase() === 'subtitle'
&& streamData.codec_name !== 'mov_text'
) {
hasSubs = true;
}
} catch (err) {
// err
}
}
///
if (file.meta.Title != undefined) {
response.infoLog += "☒File has title metadata \n";
response.preset =
", -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no title metadata \n";
}
if (file.ffProbeData.streams[0].codec_name !== 'h264') {
response.infoLog += '☒File is not in h264! \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? '
+ '-map 0:a -c:v libx264 -preset veryfast -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File is already in h264! \n';
///
if (
file.meta.Title !== undefined
&& !jsonString.includes('aac')
&& hasSubs
) {
response.infoLog += '☒File has title metadata and no aac and subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (!jsonString.includes("aac")) {
response.infoLog += "☒File has no aac track \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has aac track \n";
}
if (!jsonString.includes('aac') && hasSubs) {
response.infoLog += '☒File has no aac track and has subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
if (hasSubs) {
response.infoLog += "☒File has incompatible subs \n";
response.preset =
", -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text";
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
} else {
response.infoLog += "☑File has no/compatible subs \n";
}
if (file.meta.Title !== undefined && hasSubs) {
response.infoLog += '☒File has title and has subs \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += "☑File meets conditions! \n";
///
if (file.meta.Title !== undefined) {
response.infoLog += '☒File has title metadata \n';
response.preset = ', -map_metadata -1 -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no title metadata \n';
if (!jsonString.includes('aac')) {
response.infoLog += '☒File has no aac track \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a aac -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has aac track \n';
if (hasSubs) {
response.infoLog += '☒File has incompatible subs \n';
response.preset = ', -map 0:v -map 0:s? -map 0:a -c:v copy -c:a copy -c:s mov_text';
response.reQueueAfter = true;
response.processFile = true;
response.FFmpegMode = true;
return response;
}
response.infoLog += '☑File has no/compatible subs \n';
response.infoLog += '☑File meets conditions! \n';
return response;
}
module.exports.details = details;

@ -89,243 +89,411 @@ Note, to access FFprobe inside a plugin, use this:
Example file object:
let file = {
_id: 'C:/Users/H/Desktop/Test Input1/Sample.mp4',
DB: 'ZRPDmnmpyuAEQi7nG',
HealthCheck: 'Not attempted',
TranscodeDecisionMaker: 'Not attempted',
bit_rate: 1690430.4,
container: 'mp4',
createdAt: 2019-09-26T06:46:31.929Z,
ffProbeData:
{ streams:
[ { index: 0,
codec_name: 'h264',
codec_long_name: 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10',
profile: 'Main',
codec_type: 'video',
codec_time_base: '1/50',
codec_tag_string: 'avc1',
codec_tag: '0x31637661',
width: 1280,
height: 720,
coded_width: 1280,
coded_height: 720,
has_b_frames: 0,
sample_aspect_ratio: '1:1',
display_aspect_ratio: '16:9',
pix_fmt: 'yuv420p',
level: 31,
chroma_location: 'left',
refs: 1,
is_avc: 'true',
nal_length_size: '4',
r_frame_rate: '25/1',
avg_frame_rate: '25/1',
time_base: '1/12800',
start_pts: 0,
start_time: '0.000000',
duration_ts: 67584,
duration: '5.280000',
bit_rate: '1205959',
bits_per_raw_sample: '8',
nb_frames: '132',
disposition:
{ default: 1,
dub: 0,
original: 0,
comment: 0,
lyrics: 0,
karaoke: 0,
forced: 0,
hearing_impaired: 0,
visual_impaired: 0,
clean_effects: 0,
attached_pic: 0,
timed_thumbnails: 0 },
tags:
{ creation_time: '1970-01-01T00:00:00.000000Z',
language: 'und',
handler_name: 'VideoHandler' } },
{ index: 1,
codec_name: 'aac',
codec_long_name: 'AAC (Advanced Audio Coding)',
profile: 'LC',
codec_type: 'audio',
codec_time_base: '1/48000',
codec_tag_string: 'mp4a',
codec_tag: '0x6134706d',
sample_fmt: 'fltp',
sample_rate: '48000',
channels: 6,
channel_layout: '5.1',
bits_per_sample: 0,
r_frame_rate: '0/0',
avg_frame_rate: '0/0',
time_base: '1/48000',
start_pts: 0,
start_time: '0.000000',
duration_ts: 254976,
duration: '5.312000',
bit_rate: '384828',
max_bit_rate: '400392',
nb_frames: '249',
disposition:
{ default: 1,
dub: 0,
original: 0,
comment: 0,
lyrics: 0,
karaoke: 0,
forced: 0,
hearing_impaired: 0,
visual_impaired: 0,
clean_effects: 0,
attached_pic: 0,
timed_thumbnails: 0 },
tags:
{ creation_time: '1970-01-01T00:00:00.000000Z',
language: 'und',
handler_name: 'SoundHandler' } } ] },
ffProbeRead: 'success',
file: 'C:/Users/H/Desktop/Test Input1/Sample.mp4',
fileMedium: 'video',
file_size: 1.056519,
meta:
{ SourceFile: 'C:/Users/H/Desktop/Test Input1/Sample.mp4',
errors: [],
Duration: 5.312,
PreviewDuration: 0,
SelectionDuration: 0,
TrackDuration: 5.28,
MediaDuration: 5.312,
ExifToolVersion: 11.65,
FileName: 'Sample.mp4',
Directory: 'C:/Users/H/Desktop/Test Input1',
FileSize: '1032 kB',
FileModifyDate:
{ year: 2019,
month: 9,
day: 24,
hour: 7,
minute: 24,
second: 22,
millisecond: 0,
tzoffsetMinutes: 60,
rawValue: '2019:09:24 07:24:22+01:00' },
FileAccessDate:
{ year: 2019,
month: 9,
day: 26,
hour: 7,
minute: 44,
second: 30,
millisecond: 0,
tzoffsetMinutes: 60,
rawValue: '2019:09:26 07:44:30+01:00' },
FileCreateDate:
{ year: 2019,
month: 9,
day: 26,
hour: 7,
minute: 44,
second: 30,
millisecond: 0,
tzoffsetMinutes: 60,
rawValue: '2019:09:26 07:44:30+01:00' },
FilePermissions: 'rw-rw-rw-',
FileType: 'MP4',
FileTypeExtension: 'mp4',
MIMEType: 'video/mp4',
MajorBrand: 'MP4 Base Media v1 [IS0 14496-12:2003]',
MinorVersion: '0.2.0',
CompatibleBrands: [ 'isom', 'iso2', 'avc1', 'mp41' ],
MovieDataSize: 0,
MovieDataOffset: 1051515,
MovieHeaderVersion: 0,
CreateDate:
{ year: 1970,
month: 1,
day: 8,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
rawValue: '1970:01:08 00:00:00' },
ModifyDate:
{ year: 2014,
month: 7,
day: 19,
hour: 17,
minute: 15,
second: 29,
millisecond: 0,
rawValue: '2014:07:19 17:15:29' },
TimeScale: 1000,
PreferredRate: 1,
PreferredVolume: '100.00%',
PreviewTime: '0 s',
PosterTime: '0 s',
SelectionTime: '0 s',
CurrentTime: '0 s',
NextTrackID: 3,
TrackHeaderVersion: 0,
TrackCreateDate: '0000:00:00 00:00:00',
TrackModifyDate: '0000:00:00 00:00:00',
TrackID: 1,
TrackLayer: 0,
TrackVolume: '0.00%',
ImageWidth: 1280,
ImageHeight: 720,
GraphicsMode: 'srcCopy',
OpColor: '0 0 0',
CompressorID: 'avc1',
SourceImageWidth: 1280,
SourceImageHeight: 720,
XResolution: 72,
YResolution: 72,
BitDepth: 24,
VideoFrameRate: 25,
MatrixStructure: '1 0 0 0 1 0 0 0 1',
MediaHeaderVersion: 0,
MediaCreateDate: '0000:00:00 00:00:00',
MediaModifyDate: '0000:00:00 00:00:00',
MediaTimeScale: 48000,
MediaLanguageCode: 'und',
HandlerDescription: 'SoundHandler',
Balance: 0,
AudioFormat: 'mp4a',
AudioChannels: 2,
AudioBitsPerSample: 16,
AudioSampleRate: 48000,
HandlerType: 'Metadata',
HandlerVendorID: 'Apple',
Encoder: 'Lavf53.24.2',
Title: 'Sample title test',
Composer: 'th',
BeatsPerMinute: '',
ContentCreateDate: 2018,
Genre: 'this',
Artist: 'hhj',
Comment: 'hhk',
Subtitle: 'jj',
Mood: 'lik',
ContentDistributor: 'cont',
Conductor: 'jo',
Writer: 'writ',
InitialKey: 'ho',
Producer: 'prod',
ParentalRating: 'par',
Director: 'dir',
Period: 'pol',
Publisher: 'pub',
PromotionURL: 'prom',
AuthorURL: 'auth',
EncodedBy: 'enc',
Category: 'h',
ImageSize: '1280x720',
Megapixels: 0.922,
AvgBitrate: '1.58 Mbps',
Rotation: 0 },
processingStatus: false,
video_codec_name: 'h264',
video_resolution: '720p' }
"meta": {
"SourceFile": "C:/Users/H/Desktop/Transcode/Source/SampleVideo_1280x720_30mb - Copy (5).mp4",
"errors": [],
"Duration": 170.902,
"PreviewDuration": 0,
"SelectionDuration": 0,
"TrackDuration": 170.861,
"MediaDuration": 170.901333333333,
"ExifToolVersion": 12.1,
"FileName": "SampleVideo_1280x720_30mb - Copy (5).mp4",
"Directory": "C:/Users/H/Desktop/Transcode/Source",
"FileSize": "16 MB",
"FileModifyDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 12,
"minute": 29,
"second": 11,
"millisecond": 0,
"tzoffsetMinutes": 60,
"rawValue": "2020:12:26 12:29:11+01:00"
},
"FileAccessDate": {
"year": 2020,
"month": 12,
"day": 27,
"hour": 11,
"minute": 42,
"second": 53,
"millisecond": 0,
"tzoffsetMinutes": 60,
"rawValue": "2020:12:27 11:42:53+01:00"
},
"FileCreateDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 12,
"minute": 29,
"second": 22,
"millisecond": 0,
"tzoffsetMinutes": 60,
"rawValue": "2020:12:26 12:29:22+01:00"
},
"FilePermissions": "rw-rw-rw-",
"FileType": "MP4",
"FileTypeExtension": "mp4",
"MIMEType": "video/mp4",
"MajorBrand": "MP4 v2 [ISO 14496-14]",
"MinorVersion": "0.2.0",
"CompatibleBrands": [
"isom",
"iso2",
"avc1",
"mp41"
],
"MediaDataSize": 16831682,
"MediaDataOffset": 48,
"MovieHeaderVersion": 0,
"CreateDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"ModifyDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"TimeScale": 1000,
"PreferredRate": 1,
"PreferredVolume": "100.00%",
"PreviewTime": "0 s",
"PosterTime": "0 s",
"SelectionTime": "0 s",
"CurrentTime": "0 s",
"NextTrackID": 3,
"TrackHeaderVersion": 0,
"TrackCreateDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"TrackModifyDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"TrackID": 1,
"TrackLayer": 0,
"TrackVolume": "0.00%",
"ImageWidth": 1280,
"ImageHeight": 720,
"GraphicsMode": "srcCopy",
"OpColor": "0 0 0",
"CompressorID": "avc1",
"SourceImageWidth": 1280,
"SourceImageHeight": 720,
"XResolution": 72,
"YResolution": 72,
"BitDepth": 24,
"PixelAspectRatio": "1:1",
"VideoFrameRate": 24.997,
"MatrixStructure": "1 0 0 0 1 0 0 0 1",
"MediaHeaderVersion": 0,
"MediaCreateDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"MediaModifyDate": {
"year": 2020,
"month": 12,
"day": 26,
"hour": 11,
"minute": 28,
"second": 53,
"millisecond": 0,
"rawValue": "2020:12:26 11:28:53"
},
"MediaTimeScale": 48000,
"MediaLanguageCode": "und",
"HandlerDescription": "Stereo",
"Balance": 0,
"AudioFormat": "mp4a",
"AudioChannels": 2,
"AudioBitsPerSample": 16,
"AudioSampleRate": 48000,
"Track2Name": "Stereo",
"Track2Title": "Stereo",
"HandlerType": "Metadata",
"HandlerVendorID": "Apple",
"Encoder": "HandBrake 1.3.3 2020061300",
"ImageSize": "1280x720",
"Megapixels": 0.922,
"AvgBitrate": "788 kbps",
"Rotation": 0
},
"mediaInfo": {
"@ref": "",
"track": [
{
"@type": "General",
"VideoCount": "1",
"AudioCount": "1",
"Format": "MPEG-4",
"Format_Profile": "Base Media",
"CodecID": "mp42",
"CodecID_Compatible": "isom/iso2/avc1/mp41",
"FileSize": "16965336",
"Duration": "170.902",
"OverallBitRate": "794155",
"FrameRate": "25.000",
"FrameCount": "4271",
"StreamSize": "133654",
"HeaderSize": "40",
"DataSize": "16831690",
"FooterSize": "133606",
"IsStreamable": "No",
"Encoded_Date": "UTC 2020-12-26 11:28:53",
"Tagged_Date": "UTC 2020-12-26 11:28:53",
"Encoded_Application": "HandBrake 1.3.3 2020061300"
},
{
"@type": "Video",
"StreamOrder": "0",
"ID": "1",
"Format": "AVC",
"Format_Profile": "Main",
"Format_Level": "4",
"Format_Settings_CABAC": "Yes",
"Format_Settings_RefFrames": "4",
"CodecID": "avc1",
"Duration": "170.861",
"BitRate": "627225",
"Width": "1280",
"Height": "720",
"Sampled_Width": "1280",
"Sampled_Height": "720",
"PixelAspectRatio": "1.000",
"DisplayAspectRatio": "1.778",
"Rotation": "0.000",
"FrameRate_Mode": "VFR",
"FrameRate": "25.000",
"FrameRate_Minimum": "16.393",
"FrameRate_Maximum": "25.000",
"FrameCount": "4271",
"ColorSpace": "YUV",
"ChromaSubsampling": "4:2:0",
"BitDepth": "8",
"ScanType": "Progressive",
"StreamSize": "13394380",
"Encoded_Library": "x264 - core 157 r2935 545de2f",
"Encoded_Library_Name": "x264",
"Encoded_Library_Version": "core 157 r2935 545de2f",
"Encoded_Library_Settings": "cabac=1 / ref=1 / deblock=1:0:0 / analyse=0x1:0x111 / me=hex / subme=2 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=0 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=0 / threads=22 / lookahead_threads=5 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=1 / keyint=250 / keyint_min=25 / scenecut=40 / intra_refresh=0 / rc_lookahead=10 / rc=crf / mbtree=1 / crf=24.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / vbv_maxrate=20000 / vbv_bufsize=25000 / crf_max=0.0 / nal_hrd=none / filler=0 / ip_ratio=1.40 / aq=1:1.00",
"Encoded_Date": "UTC 2020-12-26 11:28:53",
"Tagged_Date": "UTC 2020-12-26 11:28:53",
"colour_description_present": "Yes",
"colour_description_present_Source": "Stream",
"colour_range": "Limited",
"colour_range_Source": "Stream",
"colour_primaries": "BT.709",
"colour_primaries_Source": "Stream",
"transfer_characteristics": "BT.709",
"transfer_characteristics_Source": "Stream",
"matrix_coefficients": "BT.709",
"matrix_coefficients_Source": "Stream",
"extra": {
"CodecConfigurationBox": "avcC"
}
},
{
"@type": "Audio",
"StreamOrder": "1",
"ID": "2",
"Format": "AAC",
"Format_Settings_SBR": "No (Explicit)",
"Format_AdditionalFeatures": "LC",
"CodecID": "mp4a-40-2",
"Duration": "170.902",
"BitRate_Mode": "CBR",
"BitRate": "160902",
"Channels": "2",
"ChannelPositions": "Front: L R",
"ChannelLayout": "L R",
"SamplesPerFrame": "1024",
"SamplingRate": "48000",
"SamplingCount": "8203296",
"FrameRate": "46.875",
"FrameCount": "8011",
"Compression_Mode": "Lossy",
"StreamSize": "3437302",
"StreamSize_Proportion": "0.20261",
"Title": "Stereo",
"Default": "Yes",
"AlternateGroup": "1",
"Encoded_Date": "UTC 2020-12-26 11:28:53",
"Tagged_Date": "UTC 2020-12-26 11:28:53"
}
]
},
"hasClosedCaptions": false,
"container": "mp4",
"ffProbeRead": "success",
"ffProbeData": {
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
"profile": "Main",
"codec_type": "video",
"codec_time_base": "170861/8542000",
"codec_tag_string": "avc1",
"codec_tag": "0x31637661",
"width": 1280,
"height": 720,
"coded_width": 1280,
"coded_height": 720,
"has_b_frames": 2,
"sample_aspect_ratio": "1:1",
"display_aspect_ratio": "16:9",
"pix_fmt": "yuv420p",
"level": 40,
"color_range": "tv",
"color_space": "bt709",
"color_transfer": "bt709",
"color_primaries": "bt709",
"chroma_location": "left",
"refs": 1,
"is_avc": "true",
"nal_length_size": "4",
"r_frame_rate": "50/1",
"avg_frame_rate": "4271000/170861",
"time_base": "1/90000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 15377490,
"duration": "170.861000",
"bit_rate": "627147",
"bits_per_raw_sample": "8",
"nb_frames": "4271",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2020-12-26T11:28:53.000000Z",
"language": "und",
"handler_name": "VideoHandler"
}
},
{
"index": 1,
"codec_name": "aac",
"codec_long_name": "AAC (Advanced Audio Coding)",
"profile": "LC",
"codec_type": "audio",
"codec_time_base": "1/48000",
"codec_tag_string": "mp4a",
"codec_tag": "0x6134706d",
"sample_fmt": "fltp",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/48000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 8202240,
"duration": "170.880000",
"bit_rate": "160902",
"max_bit_rate": "160902",
"nb_frames": "8011",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0
},
"tags": {
"creation_time": "2020-12-26T11:28:53.000000Z",
"language": "und",
"handler_name": "Stereo"
}
}
]
},
"file_size": 16.179405212402344,
"bit_rate": 794155.0596248143,
"video_resolution": "720p",
"fileMedium": "video",
"video_codec_name": "h264",
"_id": "C:/Users/H/Desktop/Transcode/Source/SampleVideo_1280x720_30mb - Copy (5).mp4",
"file": "C:/Users/H/Desktop/Transcode/Source/SampleVideo_1280x720_30mb - Copy (5).mp4",
"DB": "WratRWZpe",
"lastPluginDetails": "none",
"processingStatus": false,
"createdAt": "2020-12-27T10:42:55.642Z",
"statSync": {
"dev": 3832468976,
"mode": 33206,
"nlink": 1,
"uid": 0,
"gid": 0,
"rdev": 0,
"blksize": 4096,
"ino": 5066549580826442,
"size": 16965336,
"blocks": 33136,
"atimeMs": 1609065774191.6953,
"mtimeMs": 1608982151506.065,
"ctimeMs": 1608982164201.0798,
"birthtimeMs": 1608982162081.075,
"atime": "2020-12-27T10:42:54.192Z",
"mtime": "2020-12-26T11:29:11.506Z",
"ctime": "2020-12-26T11:29:24.201Z",
"birthtime": "2020-12-26T11:29:22.081Z"
},
"history": ""
}

@ -1,19 +1,24 @@
// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs:
module.exports.dependencies = [
'import-fresh',
];
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.
Name: "No title meta data ",
Type: "Video",
Operation: "Transcode",
Description: `This plugin removes metadata (if a title exists). The output container is the same as the original. \n\n`,
Version: "1.00",
Link: "https://github.com/HaveAGitGat/Tdarr_Plugin_aaaa_Pre_Proc_Example",
Tags: "ffmpeg,h265", //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
id: 'Tdarr_Plugin_aaaa_Pre_Proc_Example',
Stage: 'Pre-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed.
Name: 'No title meta data ',
Type: 'Video',
Operation: 'Transcode',
Description: 'This plugin removes metadata (if a title exists). The output container is the same as the original. \n\n',
Version: '1.00',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugin_aaaa_Pre_Proc_Example',
Tags: 'ffmpeg,h265', // 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: [
//(Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
// (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
{
name: "language",
name: 'language',
tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep.
\\nExample:\\n
@ -25,10 +30,10 @@ module.exports.details = function details() {
\\nExample:\\n
de`, //Each line following `Example:` will be clearly formatted. \\n used for line breaks
de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks
},
{
name: "channels",
name: 'channels',
tooltip: `Desired audio channel number.
\\nExample:\\n
@ -39,67 +44,69 @@ module.exports.details = function details() {
};
module.exports.plugin = function plugin(file, librarySettings, inputs) {
//Must return this object at some point in the function else plugin will fail.
// Only 'require' dependencies within this function or other functions. Do not require in the top scope.
const importFresh = require('import-fresh');
// Must return this object at some point in the function else plugin will fail.
var response = {
processFile: false, //If set to false, the file will be skipped. Set to true to have the file transcoded.
preset: "", //HandBrake/FFmpeg CLI arguments you'd like to use.
//For FFmpeg, the input arguments come first followed by <io>, followed by the output argument.
const response = {
processFile: false, // If set to false, the file will be skipped. Set to true to have the file transcoded.
preset: '', // HandBrake/FFmpeg CLI arguments you'd like to use.
// For FFmpeg, the input arguments come first followed by <io>, followed by the output argument.
// Examples
//HandBrake
// HandBrake
// '-Z "Very Fast 1080p30"'
//FFmpeg
// FFmpeg
// '-sn <io> -map_metadata -1 -c:v copy -c:a copy'
container: ".mp4", // The container of the transcoded output file.
handBrakeMode: false, //Set whether to use HandBrake or FFmpeg for transcoding
container: '.mp4', // The container of the transcoded output file.
handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding
FFmpegMode: false,
reQueueAfter: true, //Leave as true. File will be re-qeued afterwards and pass through the plugin filter again to make sure it meets conditions.
infoLog: "", //This will be shown when the user clicks the 'i' (info) button on a file in the output queue if
//it has been skipped.
reQueueAfter: true, // Leave as true. File will be re-qeued afterwards and pass through the plugin filter again to make sure it meets conditions.
infoLog: '', // This will be shown when the user clicks the 'i' (info) button on a file in the output queue if
// it has been skipped.
// Give reasons why it has been skipped ('File has no title metadata, File meets conditions!')
//Optional (include together)
// Optional (include together)
file,
removeFromDB: false, //Tell Tdarr to remove file from database if true
updateDB: false, //Change file object above and update database if true
removeFromDB: false, // Tell Tdarr to remove file from database if true
updateDB: false, // Change file object above and update database if true
};
console.log(inputs.language); //eng if user entered 'eng' in input box in Tdarr plugin UI
console.log(inputs.channels); //2 if user entered '2' in input box in Tdarr plugin UI
console.log(inputs.language); // eng if user entered 'eng' in input box in Tdarr plugin UI
console.log(inputs.channels); // 2 if user entered '2' in input box in Tdarr plugin UI
//Here we specify that we want the output file container to be the same as the current container.
response.container = "." + file.container;
// Here we specify that we want the output file container to be the same as the current container.
response.container = `.${file.container}`;
//We will use FFmpeg for this procedure.
// We will use FFmpeg for this procedure.
response.FFmpegMode = true;
//Check if file has title metadata
// Check if file has title metadata
if (file.meta.Title != undefined) {
//if so, remove it
// if so, remove it
response.infoLog += " File has title metadata";
response.preset = ",-map_metadata -1 -c:v copy -c:a copy";
response.infoLog += ' File has title metadata';
response.preset = ',-map_metadata -1 -c:v copy -c:a copy';
response.processFile = true;
return response;
} else {
response.infoLog += " File has no title metadata";
}
response.infoLog += ' File has no title metadata';
response.infoLog += " File meets conditions!";
response.infoLog += ' File meets conditions!';
return response;
};
module.exports.onTranscodeSuccess = function onTranscodeSuccess(
file,
librarySettings,
inputs
inputs,
) {
console.log(
"Transcode success! Now do some stuff with the newly scanned file."
'Transcode success! Now do some stuff with the newly scanned file.',
);
//Optional response if you need to modify database
var response = {
// Optional response if you need to modify database
const response = {
file,
removeFromDB: false,
updateDB: false,
@ -111,12 +118,12 @@ module.exports.onTranscodeSuccess = function onTranscodeSuccess(
module.exports.onTranscodeError = function onTranscodeError(
file,
librarySettings,
inputs
inputs,
) {
console.log("Transcode fail! Now do some stuff with the original file.");
console.log('Transcode fail! Now do some stuff with the original file.');
//Optional response if you need to modify database
var response = {
// Optional response if you need to modify database
const response = {
file,
removeFromDB: false,
updateDB: false,
@ -125,7 +132,7 @@ module.exports.onTranscodeError = function onTranscodeError(
return response;
};
//Example file object:
// Example file object:
// {
// _id: 'C:/Users/H/Desktop/Test Input1/Sample.mp4',
// DB: 'ZRPDmnmpyuAEQi7nG',

@ -0,0 +1,36 @@
module.exports.details = function details() {
return {
id: 'Tdarr_Plugin_bbbb_Filter_Example',
Stage: 'Pre-processing',
Name: 'Filter keywords ',
Type: 'Video',
Operation: 'Filter',
Description: 'This plugin prevents processing files which contain keywords \n\n',
Version: '1.00',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugin_bbbb_Filter_Example.js',
Tags: '',
};
};
module.exports.plugin = function plugin(file) {
// Must return this object at some point in the function else plugin will fail.
const response = {
processFile: true,
infoLog: '',
};
const keywords = [
'Low quality',
];
for (let i = 0; i < keywords.length; i += 1) {
if (file.file.includes(keywords[i])) {
response.processFile = false;
response.infoLog += `Filter preventing processing. File title contains keyword ${keywords[i]}`;
break;
}
}
return response;
};

@ -1,19 +1,24 @@
// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs:
module.exports.dependencies = [
'import-fresh',
];
module.exports.details = function details() {
return {
id: "Tdarr_Plugin_zzzz_Post_Proc_Example",
Stage: "Post-processing", //Preprocessing or Post-processing. Determines when the plugin will be executed. This plugin does some stuff after all plugins have been executed
Name: "Post proc ",
Type: "Video",
Operation: "",
Description: `This plugin does some stuff after all plugins have been executed. \n\n`,
Version: "1.00",
Link: "https://github.com/HaveAGitGat/Tdarr_Plugin_aaaa_Post_Proc_Example",
Tags: "ffmpeg,h265", //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
id: 'Tdarr_Plugin_zzzz_Post_Proc_Example',
Stage: 'Post-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed. This plugin does some stuff after all plugins have been executed
Name: 'Post proc ',
Type: 'Video',
Operation: '',
Description: 'This plugin does some stuff after all plugins have been executed. \n\n',
Version: '1.00',
Link: 'https://github.com/HaveAGitGat/Tdarr_Plugin_aaaa_Post_Proc_Example',
Tags: 'ffmpeg,h265', // 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: [
//(Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
// (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
{
name: "language",
name: 'language',
tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep.
\\nExample:\\n
@ -23,10 +28,10 @@ module.exports.details = function details() {
fr
\\nExample:\\n
de`, //Each line following `Example:` will be clearly formatted. \\n used for line breaks
de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks
},
{
name: "channels",
name: 'channels',
tooltip: `Desired audio channel number.
\\nExample:\\n
@ -37,12 +42,15 @@ module.exports.details = function details() {
};
module.exports.plugin = function plugin(file, librarySettings, inputs) {
// Only 'require' dependencies within this function or other functions. Do not require in the top scope.
const importFresh = require('import-fresh');
console.log(
"Transcode success! Now do some stuff with the newly scanned file."
'Transcode success! Now do some stuff with the newly scanned file.',
);
//Optional response if you need to modify database
var response = {
// Optional response if you need to modify database
const response = {
file,
removeFromDB: false,
updateDB: false,
@ -51,7 +59,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) {
return response;
};
//Example file object:
// Example file object:
// {
// _id: 'C:/Users/H/Desktop/Test Input1/Sample.mp4',
// DB: 'ZRPDmnmpyuAEQi7nG',

@ -1,21 +1,25 @@
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/");
const fs = require('fs');
const path = require('path');
let rootModules;
if (fs.existsSync(path.join(process.cwd(), '/npm'))) {
rootModules = path.join(process.cwd(), '/npm/node_modules/');
} else {
var rootModules = "";
rootModules = '';
}
const importFresh = require(rootModules + "import-fresh");
// eslint-disable-next-line import/no-dynamic-require
const importFresh = require(`${rootModules}import-fresh`);
module.exports.remuxContainer = importFresh(
"./library/actions/remuxContainer.js"
'./library/actions/remuxContainer.js',
);
module.exports.transcodeStandardiseAudioCodecs = importFresh(
"./library/actions/transcodeStandardiseAudioCodecs.js"
'./library/actions/transcodeStandardiseAudioCodecs.js',
);
module.exports.transcodeAddAudioStream = importFresh(
"./library/actions/transcodeAddAudioStream.js"
'./library/actions/transcodeAddAudioStream.js',
);
module.exports.transcodeKeepOneAudioStream = importFresh(
"./library/actions/transcodeKeepOneAudioStream.js"
'./library/actions/transcodeKeepOneAudioStream.js',
);

@ -1,3 +1,4 @@
/* eslint-disable */
var fs = require("fs");
var path = require("path");
if (fs.existsSync(path.join(process.cwd(), "/npm"))) {

@ -1,3 +1,4 @@
/* eslint-disable */
var fs = require("fs");
var path = require("path");
if (fs.existsSync(path.join(process.cwd(), "/npm"))) {

@ -1,3 +1,4 @@
/* eslint-disable */
function remuxContainer(file, container) {
try {
if (file.container != container) {

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports = function transcodeAddAudioStream(
file,
audioEncoder,

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports = function transcodeKeepOneAudioStream(
file,
audioEncoder,

@ -1,3 +1,4 @@
/* eslint-disable */
module.exports = function transcodeStandardiseAudioCodecs(file, audioEncoder) {
//Function required responses
// preset

@ -1,24 +1,24 @@
function filterByAge(file, ageCutOff_Seconds) {
function filterByAge(file, ageCutOff_Seconds, type) {
try {
var timeNow = new Date();
var dateCreated = new Date(file.statSync.birthtime);
var fileAge = Math.round((timeNow - dateCreated) / 1000);
const timeNow = new Date();
const dateCreated = new Date(file.statSync.birthtime);
const fileAge = Math.round((timeNow - dateCreated) / 1000);
if (fileAge > ageCutOff_Seconds) {
var response = {
if ((type === 'exclude' && fileAge > ageCutOff_Seconds) || (type === 'include' && fileAge < ageCutOff_Seconds)) {
const response = {
outcome: false,
note: `☒File creation date is older than specified requirement. \n`,
};
return response;
} else {
var response = {
outcome: true,
note: `☑File creation date is within specified requirement. \n`,
note: 'File creation date is not within specified requirement. Wont process. \n',
};
return response;
}
const response = {
outcome: true,
note: 'File creation date is within specified requirement. Will process. \n',
};
return response;
} catch (err) {
var response = {
const response = {
outcome: false,
note: `library.filters.filterByAge error: ${err} \n`,
};

@ -1,3 +1,4 @@
/* eslint-disable */
function filterByCodec(file, mode, codecs) {
try {
// console.log(file,mode,codecs)

@ -1,3 +1,4 @@
/* eslint-disable */
function filterByMedium(file, medium) {
try {
if (file.fileMedium !== medium) {

@ -1,3 +1,4 @@
/* eslint-disable */
function filterByResolution(file, mode, resolution) {
try {
if (mode === "exclude") {

@ -1,3 +1,4 @@
/* eslint-disable */
function filterBySize(file, lowerBound, upperBound) {
try {
if (

15
node_modules/.bin/prettier generated vendored

@ -1,15 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
"$basedir/node" "$basedir/../prettier/bin-prettier.js" "$@"
ret=$?
else
node "$basedir/../prettier/bin-prettier.js" "$@"
ret=$?
fi
exit $ret

17
node_modules/.bin/prettier.cmd generated vendored

@ -1,17 +0,0 @@
@ECHO off
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
"%_prog%" "%dp0%\..\prettier\bin-prettier.js" %*
ENDLOCAL
EXIT /b %errorlevel%
:find_dp0
SET dp0=%~dp0
EXIT /b

18
node_modules/.bin/prettier.ps1 generated vendored

@ -1,18 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
& "$basedir/node$exe" "$basedir/../prettier/bin-prettier.js" $args
$ret=$LASTEXITCODE
} else {
& "node$exe" "$basedir/../prettier/bin-prettier.js" $args
$ret=$LASTEXITCODE
}
exit $ret

96
node_modules/callsites/index.d.ts generated vendored

@ -0,0 +1,96 @@
declare namespace callsites {
interface CallSite {
/**
Returns the value of `this`.
*/
getThis(): unknown | undefined;
/**
Returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
*/
getTypeName(): string | null;
/**
Returns the current function.
*/
getFunction(): Function | undefined;
/**
Returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
*/
getFunctionName(): string | null;
/**
Returns the name of the property of `this` or one of its prototypes that holds the current function.
*/
getMethodName(): string | undefined;
/**
Returns the name of the script if this function was defined in a script.
*/
getFileName(): string | null;
/**
Returns the current line number if this function was defined in a script.
*/
getLineNumber(): number | null;
/**
Returns the current column number if this function was defined in a script.
*/
getColumnNumber(): number | null;
/**
Returns a string representing the location where `eval` was called if this function was created using a call to `eval`.
*/
getEvalOrigin(): string | undefined;
/**
Returns `true` if this is a top-level invocation, that is, if it's a global object.
*/
isToplevel(): boolean;
/**
Returns `true` if this call takes place in code defined by a call to `eval`.
*/
isEval(): boolean;
/**
Returns `true` if this call is in native V8 code.
*/
isNative(): boolean;
/**
Returns `true` if this is a constructor call.
*/
isConstructor(): boolean;
}
}
declare const callsites: {
/**
Get callsites from the V8 stack trace API.
@returns An array of `CallSite` objects.
@example
```
import callsites = require('callsites');
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
*/
(): callsites.CallSite[];
// TODO: Remove this for the next major release, refactor the whole definition to:
// declare function callsites(): callsites.CallSite[];
// export = callsites;
default: typeof callsites;
};
export = callsites;

13
node_modules/callsites/index.js generated vendored

@ -0,0 +1,13 @@
'use strict';
const callsites = () => {
const _prepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const stack = new Error().stack.slice(1);
Error.prepareStackTrace = _prepareStackTrace;
return stack;
};
module.exports = callsites;
// TODO: Remove this for the next major release
module.exports.default = callsites;

@ -1,4 +1,6 @@
Copyright © James Long and contributors
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

@ -0,0 +1,71 @@
{
"_from": "callsites@^3.0.0",
"_id": "callsites@3.1.0",
"_inBundle": false,
"_integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"_location": "/callsites",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "callsites@^3.0.0",
"name": "callsites",
"escapedName": "callsites",
"rawSpec": "^3.0.0",
"saveSpec": null,
"fetchSpec": "^3.0.0"
},
"_requiredBy": [
"/parent-module"
],
"_resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"_shasum": "b3630abd8943432f54b3f0519238e33cd7df2f73",
"_spec": "callsites@^3.0.0",
"_where": "C:\\Users\\H\\Documents\\GitHub\\tdarr_express_node\\assets\\app\\plugins\\node_modules\\parent-module",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"bugs": {
"url": "https://github.com/sindresorhus/callsites/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Get callsites from the V8 stack trace API",
"devDependencies": {
"ava": "^1.4.1",
"tsd": "^0.7.2",
"xo": "^0.24.0"
},
"engines": {
"node": ">=6"
},
"files": [
"index.js",
"index.d.ts"
],
"homepage": "https://github.com/sindresorhus/callsites#readme",
"keywords": [
"stacktrace",
"v8",
"callsite",
"callsites",
"stack",
"trace",
"function",
"file",
"line",
"debug"
],
"license": "MIT",
"name": "callsites",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/callsites.git"
},
"scripts": {
"test": "xo && ava && tsd"
},
"version": "3.1.0"
}

48
node_modules/callsites/readme.md generated vendored

@ -0,0 +1,48 @@
# callsites [![Build Status](https://travis-ci.org/sindresorhus/callsites.svg?branch=master)](https://travis-ci.org/sindresorhus/callsites)
> Get callsites from the [V8 stack trace API](https://v8.dev/docs/stack-trace-api)
## Install
```
$ npm install callsites
```
## Usage
```js
const callsites = require('callsites');
function unicorn() {
console.log(callsites()[0].getFileName());
//=> '/Users/sindresorhus/dev/callsites/test.js'
}
unicorn();
```
## API
Returns an array of callsite objects with the following methods:
- `getThis`: returns the value of `this`.
- `getTypeName`: returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property.
- `getFunction`: returns the current function.
- `getFunctionName`: returns the name of the current function, typically its `name` property. If a name property is not available an attempt will be made to try to infer a name from the function's context.
- `getMethodName`: returns the name of the property of `this` or one of its prototypes that holds the current function.
- `getFileName`: if this function was defined in a script returns the name of the script.
- `getLineNumber`: if this function was defined in a script returns the current line number.
- `getColumnNumber`: if this function was defined in a script returns the current column number
- `getEvalOrigin`: if this function was created using a call to `eval` returns a string representing the location where `eval` was called.
- `isToplevel`: is this a top-level invocation, that is, is this the global object?
- `isEval`: does this call take place in code defined by a call to `eval`?
- `isNative`: is this call in native V8 code?
- `isConstructor`: is this a constructor call?
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)

@ -0,0 +1,30 @@
/**
Import a module while bypassing the cache.
@example
```
// foo.js
let i = 0;
module.exports = () => ++i;
// index.js
import importFresh = require('import-fresh');
require('./foo')();
//=> 1
require('./foo')();
//=> 2
importFresh('./foo')();
//=> 1
importFresh('./foo')();
//=> 1
const foo = importFresh<typeof import('./foo')>('./foo');
```
*/
declare function importFresh<T>(moduleId: string): T;
export = importFresh;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save