commit
2fce8abb1a
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
@ -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,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;
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
};
|
||||
@ -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
|
||||
@ -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,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,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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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;
|
||||
@ -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"
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
# callsites [](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…
Reference in new issue