Compare commits

..

8 Commits

2 changed files with 650 additions and 97 deletions

View File

@ -1,13 +1,62 @@
const details = () => ({ const details = () => ({
id: 'Tdarr_Plugin_MM1_Full_Stack', id: 'Tdarr_Plugin_MM1_MrMeeb_Full_Stack',
Stage: 'Pre-processing', Stage: 'Pre-processing',
Name: 'MrMeeb Full Stack Processing', Name: 'MrMeeb Full Stack Processing',
Type: 'Video', Type: 'Video',
Operation: 'Transcode', Operation: 'Transcode',
Description: 'Automatically removes TrueHD and replaces with a suitable AC3 5.1 track, either pre-existing or created.\n Removes non-English and non-German subtitles.\n Transcodes x264 content to HEVC using NVENC, with a focus on quality', Description: 'Automatically removes TrueHD and replaces with a suitable AC3 5.1 track, either pre-existing or created.\n Removes non-English and non-German subtitles.\n Transcodes x264 content to HEVC using NVENC, with a focus on quality',
Version: '0.1', Version: '0.1',
Tags: 'pre-processing', Tags: 'pre-processing',
Inputs: [], Inputs: [
{
name: 'process_video',
type: 'boolean',
defaultValue: true,
inputUI: {
type: 'dropdown',
options: [
'true',
'false',
],
},
tooltip: `Select whether to process video, or just copy over`,
},
{
name: 'process_audio',
type: 'boolean',
defaultValue: true,
inputUI: {
type: 'dropdown',
options: [
'true',
'false',
],
},
tooltip: `Select whether to process audio, or just copy over`,
},
{
name: 'process_subs',
type: 'boolean',
defaultValue: true,
inputUI: {
type: 'dropdown',
options: [
'true',
'false',
],
},
tooltip: `Select whether to process subs, or just copy over`,
},
{
name: 'crf',
type: 'string', // set the data type of the input ('string', 'number', 'boolean')
defaultValue: '19', // set the default value of the input incase the user enters no input
inputUI: {
type: 'text', // specify how the input UI will appear to the user ('text' or 'dropdown')
},
tooltip: `Enter CRF value used for video transcoding.`,
},
],
}); });
@ -16,53 +65,65 @@ const details = () => ({
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const plugin = (file, librarySettings, inputs, otherArguments) => { const plugin = (file, librarySettings, inputs, otherArguments) => {
const lib = require('../methods/lib')(); const lib = require('../methods/lib')();
// load default plugin inputs // load default plugin inputs
inputs = lib.loadDefaultValues(inputs, details); inputs = lib.loadDefaultValues(inputs, details);
// Load response object // Load response object
const response = { const response = {
processFile: false, // If set to false, the file will be skipped. Set to true to have the file transcoded. 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. 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. // For FFmpeg, the input arguments come first followed by <io>, followed by the output argument.
// Examples // Examples
// HandBrake // HandBrake
// '-Z "Very Fast 1080p30"' // '-Z "Very Fast 1080p30"'
// FFmpeg // FFmpeg
// '-sn <io> -map_metadata -1 -c:v copy -c:a copy' // '-sn <io> -map_metadata -1 -c:v copy -c:a copy'
container: '.mp4', // The container of the transcoded output file. container: '.mp4', // The container of the transcoded output file.
handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding
FFmpegMode: false, FFmpegMode: false,
infoLog: '', // This will be shown when the user clicks the 'i' (info) button on a file in the output queue if 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. // it has been skipped.
// Give reasons why it has been skipped ('File has no title metadata, File meets conditions!') // Give reasons why it has been skipped ('File has no title metadata, File meets conditions!')
// Optional (include together) // Optional (include together)
//file, //file,
//removeFromDB: false, // Tell Tdarr to remove file from 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 //updateDB: false, // Change file object above and update database if true
}; };
// Required global variables for functions // Required global variables for functions
let ffmpegAudioFirstTrack = ''; let ffmpegAudioFirstTrack = '';
let ffmpegAudioOtherTracks = ''; let ffmpegAudioOtherTracks = '';
let ffmpegSubs = ''; let ffmpegSubs = '';
let ffmpegVideo = ''; let ffmpegVideo = '';
// Defining functions // Defining functions
const processAudio = (file, librarySettings, inputs, otherArguments) => { const processAudio = (file, librarySettings, inputs, otherArguments) => {
if (inputs.process_audio == false) {
response.infoLog += `Processing audio set to skip. Copying all present tracks over.\n`
}
else {
// Function variables // Function variables
let audioIdx = -1; let audioIdx = -1;
let audioIdxTruehd = -1; let audioIdxTruehd = -1;
let trueHD = -1; let trueHD = -1;
let trueHDi = -1; let trueHDi = -1;
let audioIdxDtshdma = -1;
let dtshdma = -1;
let dtshdmai = -1;
let ac3Count = 0; let ac3Count = 0;
let dtsHandled = 0;
console.log(` === Running processAudio function === `) console.log(` === Running processAudio function === `)
// Go through all streams, hunting for TrueHD // Go through all streams, hunting for TrueHD/DTS-HD MA
for (let i = 0; i < file.ffProbeData.streams.length; i ++) { for (let i = 0; i < file.ffProbeData.streams.length; i ++) {
// Work with audio streams only // Work with audio streams only
@ -134,7 +195,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if (trueHDReplacementCount <= 1) { if (trueHDReplacementCount <= 1) {
ffmpegAudioFirstTrack = ` -map 0:a:${audioIdxTruehd} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${audioIdxTruehd} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default`
} }
else { else {
@ -186,14 +247,14 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
response.infoLog += 'TrueHD track exists, but no AC3 compatibility track is present. Creating one, and downmixing to 6 channels.\n' response.infoLog += 'TrueHD track exists, but no AC3 compatibility track is present. Creating one, and downmixing to 6 channels.\n'
ffmpegAudioFirstTrack = `-map 0:a:${trueHD} -c:a:0 ac3 -b:a:0 640k -ac:a:0 6 -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${trueHD} -c:a:0 ac3 -b:a:0 640k -ac:a:0 6 -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default`
} }
else { else {
response.infoLog += 'TrueHD track exists, but no AC3 compatibility track is present. Creating one.\n' response.infoLog += 'TrueHD track exists, but no AC3 compatibility track is present. Creating one.\n'
ffmpegAudioFirstTrack = `-map 0:a:${trueHD} -c:a:0 ac3 -b:a:0 640k -ac:a:0 ${file.ffProbeData.streams[trueHDi].channels} -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${trueHD} -c:a:0 ac3 -b:a:0 640k -ac:a:0 ${file.ffProbeData.streams[trueHDi].channels} -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default`
} }
@ -202,6 +263,145 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
} }
// Identify if stream is DTS-HD MA
if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'dts') {
// Check if DTS-HD MA has already been handled by a previous run
// Assume that it will be the second stream (first audio stream) - it should be, if already handled
MMChannelLayout = file.ffProbeData.streams[i].channel_layout.replace('(side)', ''); // Have to declare this here to make the if statement work. Leaving it in place further down as well
if (file.ffProbeData.streams[i].tags.title === `DTS-HD MA - ${MMChannelLayout} - MM`) {
console.log(`Stream ${i} (audio stream ${audioIdx}) is DTS-HD MA and has already been handled. Safe to assume this file has already been treated.`);
response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is DTS-HD MA and has already been handled. Safe to assume this file has already been treated.\n`
// Mark DTS-HD MA handling process as complete causing everything else audio-related to be skipped
dtsHandled = 1
} else {
console.log(`Stream ${i} (audio stream ${audioIdx}) is DTS-HD MA and has not been handled. Commencing DTS-HD MA handling process`);
response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is DTS-HD MA. Commencing DTS-HD MA handling process.\n`
};
dtshdma = audioIdx; // Index of DTS-HD MA track relative to other audio tracks
dtshdmai = i; // Index of DTS-HD MA track within all tracks (audio, video, subs, etc)
let dtshdmaLang = file.ffProbeData.streams[i].tags.language // Language of DTS-HD MA track
let dtshdmaReplacementCount = 0 // Number of DTS-HD MA replacement candidates
// Look if there is a suitable AC3 5.1 track to use alongside it
for (let i = 0; i < file.ffProbeData.streams.length; i ++) { // Iterate through all streams
if (dtsHandled == 0 && file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { // If the stream is an audio stream
audioIdxDtshdma++; // Index of replacement track
MMCodec = file.ffProbeData.streams[i].codec_name.toUpperCase();
MMChannelLayout = file.ffProbeData.streams[i].channel_layout.replace('(side)', '');
try {
// Identify if stream is commentary track
if (
file.ffProbeData.streams[i].tags.title != undefined &&
file.ffProbeData.streams[i].tags.title.toLowerCase().includes("commentary") ||
file.ffProbeData.streams[i].disposition.comment == 1
) {
console.log(`Stream ${i} (audio stream ${audioIdxDtshdma}) is probably a commentary track. Removing.`)
response.infoLog += `Stream ${i} (audio stream ${audioIdxDtshdma}) is probably a commentary track. Removing.\n`
}
// Identify if stream is AC3 5.1
else if (
file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3' && // is codec AC3
file.ffProbeData.streams[i].channels === 6 && // does it have 6 channels (5.1)
file.ffProbeData.streams[i].tags.language === dtshdmaLang // does it have the same language as the DTS-HD MA track
) {
console.log(`Stream ${i} (audio stream ${audioIdxDtshdma}) is AC3 5.1 and in matching language. Keeping alongside the DTS-HD MA track.`);
console.log(`${MMCodec} <- Should be AC3`)
response.infoLog += `Stream ${i} (audio stream ${audioIdxDtshdma}) is AC3 5.1 and in matching language. Keeping alongside the DTS-HD MA track.\n`
response.infoLog +=`${MMCodec} <- Should be AC3\n`
dtshdmaReplacementCount++ // Add to the DTS-HD MA replacement count
if (dtshdmaReplacementCount <= 1) { // If we found a single DTS-HD MA replacement...
ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${dtshdma} -metadata:s:a:0 "title=DTS-HD MA - ${MMChannelLayout} - MM" -disposition:a:0 default -map 0:a:${audioIdxDtshdma} -metadata:s:a:1 "title=${MMCodec} - ${MMChannelLayout} - MM"` // Keep both the DTS-HD MA and AC3 tracks, label them
}
else {
console.log('More than 1 suitable AC3 5.1 track has been detected - something has probably gone wrong in detection of commentary tracks. The first found track will be kept')
response.infoLog += 'More than 1 suitable AC3 5.1 track has been detected - something has probably gone wrong in detection of commentary tracks. The first found track will be kept\n'
}
}
else if (
file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3' && // is codec AC3
file.ffProbeData.streams[i].channels === 6 && // does it have 6 channels (5.1)
file.ffProbeData.streams[i].tags.language !== dtshdmaLang && // does it NOT match the language of the DTS-HD MA track
file.ffProbeData.streams[i].tags.language.toLowerCase() == 'eng' // is the AC3 track in English
) {
console.log(`Stream ${i} (audio stream ${audioIdxDtshdma}) is AC3 5.1. It does not match the native language of the movie, but is in English. Probably want to keep.`);
response.infoLog += `Stream ${i} (audio stream ${audioIdxDtshdma}) is AC3 5.1. It does not match the native language of the movie, but is in English. Probably want to keep.\n`
ffmpegAudioOtherTracks += ` -map 0:a:${audioIdxDtshdma}` // Add the track to the list of additional tracks
}
else if (file.ffProbeData.streams[i].codec_name.toLowerCase() !== 'dts') { // Remove any track that isn't AC3 since we already have DTS-HD MA to work with
console.log(`Stream ${i} (audio stream ${audioIdxDtshdma}) isn't AC3 5.1, and DTS-HD MA is already available to create an AC3 5.1 track if not present. Removing.`)
response.infoLog += `Stream ${i} (audio stream ${audioIdxDtshdma}) isn't AC3 5.1, and DTS-HD MA is already available to create an AC3 5.1 track if not present. Removing.\n`
};
} catch (error) {
}
};
};
if (dtsHandled == 0 && dtshdmaReplacementCount == 0) { // If we didn't find a replacement candidate
console.log('A suitable DTS-HD MA replacement track is not available. Creating my own.')
//console.log(`${MMCodec} <- Should be AC3`)
if (file.ffProbeData.streams[dtshdmai].channels > 6) { // If the DTS-HD MA track has more than 6 channels, downmix to 6 channels
response.infoLog += 'DTS-HD MA track exists, but no AC3 compatibility track is present. Creating one, and downmixing to 6 channels.\n'
ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${dtshdma} -metadata:s:a:0 "title=DTS-HD MA - ${MMChannelLayout} - MM" -disposition:a:0 default -map 0:a:${dtshdma} -c:a:1 ac3 -b:a:1 640k -ac:a:1 6 -metadata:s:a:1 "title=AC3 - 5.1 - MM"`
}
else { // Otherwise, create an AC3 track with the same channel layout
response.infoLog += 'DTS-HD MA track exists, but no AC3 compatibility track is present. Creating one.\n'
ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${dtshdma} -metadata:s:a:0 "title=DTS-HD MA - ${MMChannelLayout} - MM" -disposition:a:0 default -map 0:a:${dtshdma} -c:a:1 ac3 -b:a:1 640k -ac:a:1 ${file.ffProbeData.streams[dtshdmai].channels} -metadata:s:a:1 "title=AC3 - 5.1 - MM"`
}
}
// Mark DTS-HD MA handling process as complete, skipping the logic for no THD/DTS-HD MA. Was already set if the run had completed before.
dtsHandled = 1
}
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@ -209,9 +409,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
} }
}; };
// Logic for if no TrueHD track present // Logic for if no TrueHD/DTS-HD MA track present
if (trueHD < 0) { if (trueHD < 0 && dtsHandled == 0) {
console.log('No TrueHD track is present in the file.') console.log('No TrueHD track is present in the file.')
response.infoLog += `No TrueHD track is present.\n` response.infoLog += `No TrueHD track is present.\n`
@ -272,8 +472,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
//LOGIC NEEDS ADDING FOR IF AN AC3 TRACK HAS BEEN CREATED WITH FEWER THAN 6 CHANNELS //LOGIC NEEDS ADDING FOR IF AN AC3 TRACK HAS BEEN CREATED WITH FEWER THAN 6 CHANNELS
console.log(`Neither a TrueHD track or a desireable AC3 5.1 track exist. Selecting the first audio track and converting it to AC3.`) console.log(`Neither a TrueHD track nor a desireable AC3 5.1 track exist. Selecting the first audio track and converting it to AC3.`)
response.infoLog += 'Neither a TrueHD track or a desireable AC3 5.1 track exist. Selecting the first audio track and converting it to AC3.\n' response.infoLog += 'Neither a TrueHD track nor a desireable AC3 5.1 track exist. Selecting the first audio track and converting it to AC3.\n'
if (file.ffProbeData.streams[1].codec_type.toLowerCase() === 'audio') { if (file.ffProbeData.streams[1].codec_type.toLowerCase() === 'audio') {
@ -286,12 +486,12 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if ( file.ffProbeData.streams[1].channels > 6 ) { if ( file.ffProbeData.streams[1].channels > 6 ) {
ffmpegAudioFirstTrack = ` -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:0 6 -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:0 6 -metadata:s:a:0 "title=AC3 - 5.1 - MM" -disposition:a:0 default`
} }
else { else {
ffmpegAudioFirstTrack = ` -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:0 ${file.ffProbeData.streams[1].channels} -metadata:s:a:0 "title=AC3 - ${MMChannelLayout} - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:0 -c:a:0 ac3 -b:a:0 640k -ac:a:0 ${file.ffProbeData.streams[1].channels} -metadata:s:a:0 "title=AC3 - ${MMChannelLayout} - MM" -disposition:a:0 default`
} }
@ -355,7 +555,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
console.log(`Stream ${i} (audio stream ${audioIdx}) is AC3 5.1, but not labelled correctly. Correcting.`) console.log(`Stream ${i} (audio stream ${audioIdx}) is AC3 5.1, but not labelled correctly. Correcting.`)
response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is AC3 5.1, but not labelled correctly. Correcting.\n` response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is AC3 5.1, but not labelled correctly. Correcting.\n`
ffmpegAudioFirstTrack = ` -map 0:a:${audioIdx} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:${audioIdx} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default`
} }
@ -394,7 +594,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
let MMCodec = file.ffProbeData.streams[1].codec_name.toUpperCase(); let MMCodec = file.ffProbeData.streams[1].codec_name.toUpperCase();
let MMChannelLayout = file.ffProbeData.streams[1].channel_layout.replace('(side)', ''); let MMChannelLayout = file.ffProbeData.streams[1].channel_layout.replace('(side)', '');
ffmpegAudioFirstTrack = ` -map 0:a:0 -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default` ffmpegAudioFirstTrack = ` -map -0:a -map 0:a:0 -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default`
console.log(`Copying over other AC3 5.1 tracks`) console.log(`Copying over other AC3 5.1 tracks`)
@ -429,7 +629,17 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
}; };
const processSubs = (file, librarySettings, inputs, otherArguments) => { };
const processSubs = (file, librarySettings, inputs, otherArguments) => {
if (inputs.process_subs == false) {
response.infoLog += `Processing subs set to skip. Copying all present tracks over.\n`
}
else {
// Function variables // Function variables
let subIdx = -1; let subIdx = -1;
@ -439,11 +649,11 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// Go through all subtitles, removing non-English/German subs and commentary subs // Go through all subtitles, removing non-English/German subs and commentary subs
for (let i = 0; i < file.ffProbeData.streams.length; i ++) { for (let i = 0; i < file.ffProbeData.streams.length; i ++) {
console.log(file.ffProbeData.streams[i].codec_type)
// Work with subtitle streams only // Work with subtitle streams only
if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') { if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') {
//console.log(`Stream ${i} is a subtitle stream`);
// Keep track of subtitle stream relative numbers // Keep track of subtitle stream relative numbers
try { try {
if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") { if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") {
@ -451,10 +661,18 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
} }
} catch (err) {} } catch (err) {}
//console.log(`language = ${file.ffProbeData.streams[i].tags.language.toLowerCase()}`) // Remove any subtitles with no language set
if (file.ffProbeData.streams[i].tags.language == undefined) {
console.log(`Stream ${i} (subtitle stream ${subIdx}) has no language. Removing.`)
response.infoLog += `Stream ${i} (subtitle stream ${subIdx}) has no language. Removing. \n`
ffmpegSubs += ` -map -0:s:${subIdx}`
}
// Remove any subtitles not in English or German // Remove any subtitles not in English or German
if ( else if (
file.ffProbeData.streams[i].tags.language.toLowerCase() !== "eng" && file.ffProbeData.streams[i].tags.language.toLowerCase() !== "eng" &&
file.ffProbeData.streams[i].tags.language.toLowerCase() !== "ger" file.ffProbeData.streams[i].tags.language.toLowerCase() !== "ger"
) { ) {
@ -494,7 +712,17 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
}; };
const processVideo = (file, librarySettings, inputs, otherArguments) => { };
const processVideo = (file, librarySettings, inputs, otherArguments) => {
if (inputs.process_video == false) {
response.infoLog += `Processing video set to skip. Copying all present tracks over.\n`
}
else {
console.log(` === Running processVideo function === `) console.log(` === Running processVideo function === `)
@ -511,49 +739,51 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
console.log('File needs transcoding to HEVC. Converting now.') console.log('File needs transcoding to HEVC. Converting now.')
response.infoLog += `☒ File needs transcoding to HEVC.\n` response.infoLog += `☒ File needs transcoding to HEVC.\n`
ffmpegVideo = ` -c:v hevc_nvenc -b:v 0 -preset p7 -cq 1 -rc-lookahead 32 -bf 0` ffmpegVideo = ` -c:v libx265 -pix_fmt yuv420p10le -preset slow -x265-params crf=${inputs.crf}:bframes=8:rc-lookahead=32:b-intra=1:aq-mode=3`
} }
}; };
// Start running functions
processAudio(file, librarySettings, inputs, otherArguments);
processSubs(file, librarySettings, inputs, otherArguments);
processVideo(file, librarySettings, inputs, otherArguments);
if (ffmpegAudioFirstTrack.length > 0 || ffmpegAudioOtherTracks.length > 0 || ffmpegSubs.length > 0 || ffmpegVideo.length > 0) {
response.infoLog += '☒ Changes are required! \n';
response.FFmpegMode = true;
response.container = `.${file.container}`;
response.preset = `,-c copy -map 0:v?${ffmpegVideo}${ffmpegAudioFirstTrack}${ffmpegAudioOtherTracks} -map 0:s?${ffmpegSubs} -map 0:d? -max_muxing_queue_size 9999`;
console.log(response.preset)
response.processFile = true;
return response;
}
if (ffmpegAudioFirstTrack.length == 0 && ffmpegAudioOtherTracks.length == 0 && ffmpegSubs.length == 0 && ffmpegVideo.length == 0) {
response.infoLog += '☑ No changes are required \n';
response.processFile = false;
return response;
}
}; };
// Start running functions
processAudio(file, librarySettings, inputs, otherArguments);
processSubs(file, librarySettings, inputs, otherArguments);
processVideo(file, librarySettings, inputs, otherArguments);
if (ffmpegAudioFirstTrack.length > 0 || ffmpegAudioOtherTracks.length > 0 || ffmpegSubs.length > 0 || ffmpegVideo.length > 0) {
response.infoLog += '☒ Changes are required! \n';
response.FFmpegMode = true;
response.container = `.${file.container}`;
response.preset = `,-c copy -map 0:v?${ffmpegVideo} -map 0:a?${ffmpegAudioFirstTrack}${ffmpegAudioOtherTracks} -map 0:s?${ffmpegSubs} -map 0:d? -max_muxing_queue_size 9999`;
console.log(response.preset)
response.processFile = true;
return response;
}
if (ffmpegAudioFirstTrack.length == 0 && ffmpegAudioOtherTracks.length == 0 && ffmpegSubs.length == 0 && ffmpegVideo.length == 0) {
response.infoLog += '☑ No changes are required \n';
response.processFile = false;
return response;
}
};
module.exports.details = details; module.exports.details = details;
module.exports.plugin = plugin; module.exports.plugin = plugin;

View File

@ -0,0 +1,323 @@
/* eslint-disable no-await-in-loop */
module.exports.dependencies = ['axios@0.27.2', '@cospired/i18n-iso-languages'];
// tdarrSkipTest
const details = () => ({
id: 'Tdarr_Plugin_MM_Keep_Native_Lang_Plus_Eng',
Stage: 'Pre-processing',
Name: 'Remove all langs except native and English - MM',
Type: 'Audio',
Operation: 'Transcode',
Description: `(Updated version of Henk's plugin, provided by them in Discord' This plugin will remove all language audio tracks except the 'native'
(requires TMDB api key) and English.
'Native' languages are the ones that are listed on imdb. It does an API call to
Radarr, Sonarr to check if the movie/series exists and grabs the IMDB id. As a last resort it
falls back to the IMDB id in the filename.`,
Version: '1.2',
Tags: 'pre-processing,configurable',
Inputs: [
{
name: 'user_langs',
type: 'string',
defaultValue: '',
inputUI: {
type: 'text',
},
tooltip:
'Input a comma separated list of ISO-639-2 languages. It will still keep English and undefined tracks.' +
'(https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 639-2 column)' +
'\\nExample:\\n' +
'nld,nor',
},
{
name: 'priority',
type: 'string',
defaultValue: 'Radarr',
inputUI: {
type: 'text',
},
tooltip:
'Priority for either Radarr or Sonarr. Leaving it empty defaults to Radarr first.' +
'\\nExample:\\n' +
'sonarr',
},
{
name: 'api_key',
type: 'string',
defaultValue: '',
inputUI: {
type: 'text',
},
tooltip:
'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)',
},
{
name: 'radarr_api_key',
type: 'string',
defaultValue: '',
inputUI: {
type: 'text',
},
tooltip: 'Input your Radarr api key here.',
},
{
name: 'radarr_url',
type: 'string',
defaultValue: '192.168.1.2:7878',
inputUI: {
type: 'text',
},
tooltip:
'Input your Radarr url here. (Without http://). Do include the port.' +
'\\nExample:\\n' +
'192.168.1.2:7878',
},
{
name: 'sonarr_api_key',
type: 'string',
defaultValue: '',
inputUI: {
type: 'text',
},
tooltip: 'Input your Sonarr api key here.',
},
{
name: 'sonarr_url',
type: 'string',
defaultValue: '192.168.1.2:8989',
inputUI: {
type: 'text',
},
tooltip:
'Input your Sonarr url here. (Without http://). Do include the port.' +
'\\nExample:\\n' +
'192.168.1.2:8989',
},
],
});
const response = {
processFile: false,
preset: ', -map 0 ',
container: '.',
handBrakeMode: false,
FFmpegMode: true,
reQueueAfter: false,
infoLog: '',
};
const processStreams = (result, file, user_langs) => {
// eslint-disable-next-line import/no-unresolved
const languages = require('@cospired/i18n-iso-languages');
const tracks = {
keep: [],
remove: [],
remLangs: '',
};
let streamIndex = 0;
// If the original language is pulled as Chinese 'cn' is used. iso-language expects 'zh' for Chinese.
const langsTemp =
result.original_language === 'cn' ? 'zh' : result.original_language;
let langs = [];
langs.push(languages.alpha2ToAlpha3B(langsTemp));
// Some console reporting for clarification of what the plugin is using and reporting.
response.infoLog += `Original language: ${langsTemp}, Using code: ${languages.alpha2ToAlpha3B(
langsTemp,
)}\n`;
if (user_langs) {
langs = langs.concat(user_langs);
}
if (!langs.includes('eng')) langs.push('eng');
if (!langs.includes('und')) langs.push('und');
response.infoLog += 'Keeping languages: ';
// Print languages to UI
langs.forEach((l) => {
response.infoLog += `${languages.getName(l, 'en')}, `;
});
response.infoLog = `${response.infoLog.slice(0, -2)}\n`;
for (const stream of file.ffProbeData.streams) {
if (stream.codec_type === 'audio') {
if (!stream.tags) {
response.infoLog += `☒No tags found on audio track ${streamIndex}. Keeping it. \n`;
tracks.keep.push(streamIndex);
streamIndex += 1;
// eslint-disable-next-line no-continue
continue;
}
if (stream.tags.language) {
if (langs.includes(stream.tags.language)) {
tracks.keep.push(streamIndex);
} else {
tracks.remove.push(streamIndex);
response.preset += `-map -0:a:${streamIndex} `;
tracks.remLangs += `${languages.getName(
stream.tags.language,
'en',
)}, `;
}
streamIndex += 1;
} else {
response.infoLog += `☒No language tag found on audio track ${streamIndex}. Keeping it. \n`;
}
}
}
response.preset += ' -c copy -max_muxing_queue_size 9999';
return tracks;
};
const tmdbApi = async (filename, api_key, axios) => {
let fileName;
// If filename begins with tt, it's already an imdb id
if (filename) {
if (filename.substring(0, 2) === 'tt') {
fileName = filename;
} else {
const idRegex = /(tt\d{7,8})/;
const fileMatch = filename.match(idRegex);
// eslint-disable-next-line prefer-destructuring
if (fileMatch) fileName = fileMatch[1];
}
}
if (fileName) {
const result = await axios
.get(
`https://api.themoviedb.org/3/find/${fileName}?api_key=` +
`${api_key}&language=en-US&external_source=imdb_id`,
)
.then((resp) =>
resp.data.movie_results.length > 0
? resp.data.movie_results[0]
: resp.data.tv_results[0],
);
if (!result) {
response.infoLog += '☒No IMDB result was found. \n';
}
return result;
}
return null;
};
// eslint-disable-next-line consistent-return
const parseArrResponse = async (body, filePath, arr) => {
// eslint-disable-next-line default-case
switch (arr) {
case 'radarr':
return body.movie;
case 'sonarr':
return body.series;
}
};
// eslint-disable-next-line no-unused-vars
const plugin = async (file, librarySettings, inputs, otherArguments) => {
const lib = require('../methods/lib')();
// eslint-disable-next-line no-unused-vars,no-param-reassign
inputs = lib.loadDefaultValues(inputs, details);
// eslint-disable-next-line import/no-unresolved
const axios = require('axios').default;
response.container = `.${file.container}`;
let prio = ['radarr', 'sonarr'];
let radarrResult = null;
let sonarrResult = null;
let tmdbResult = null;
if (inputs.priority) {
if (inputs.priority === 'sonarr') {
prio = ['sonarr', 'radarr'];
}
}
const fileNameEncoded = encodeURIComponent(file.meta.FileName);
for (const arr of prio) {
let imdbId;
// eslint-disable-next-line default-case
switch (arr) {
case 'radarr':
if (tmdbResult) break;
if (inputs.radarr_api_key) {
radarrResult = await parseArrResponse(
await axios
.get(
`${inputs.radarr_url}/api/v3/parse?apikey=${inputs.radarr_api_key}&title=${fileNameEncoded}`,
)
.then((resp) => resp.data),
fileNameEncoded,
'radarr',
);
if (radarrResult) {
imdbId = radarrResult.imdbId;
response.infoLog += `Grabbed ID (${imdbId}) from Radarr \n`;
const languages = require('@cospired/i18n-iso-languages');
tmdbResult = {original_language: languages.getAlpha2Code(radarrResult.originalLanguage.name, 'en')};
} else {
response.infoLog += "Couldn't grab ID from Radarr \n";
imdbId = fileNameEncoded;
}
}
break;
case 'sonarr':
if (tmdbResult) break;
if (inputs.sonarr_api_key) {
sonarrResult = await parseArrResponse(
await axios.get(
`${inputs.sonarr_url}/api/v3/parse?apikey=${inputs.sonarr_api_key}&title=${fileNameEncoded}`
)
.then((resp) => resp.data),
file.meta.Directory,
'sonarr',
);
if (sonarrResult) {
imdbId = sonarrResult.imdbId;
response.infoLog += `Grabbed ID (${imdbId}) from Sonarr \n`;
} else {
response.infoLog += "Couldn't grab ID from Sonarr \n";
imdbId = fileNameEncoded;
}
tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios);
}
}
}
if (tmdbResult) {
const tracks = processStreams(
tmdbResult,
file,
inputs.user_langs ? inputs.user_langs.split(',') : '',
);
if (tracks.remove.length > 0) {
if (tracks.keep.length > 0) {
response.infoLog += `☑Removing tracks with languages: ${tracks.remLangs.slice(
0,
-2,
)}. \n`;
response.processFile = true;
response.infoLog += '\n';
} else {
response.infoLog +=
'☒Cancelling plugin otherwise all audio tracks would be removed. \n';
}
} else {
response.infoLog += '☒No audio tracks to be removed. \n';
}
} else {
response.infoLog += "☒Couldn't find the IMDB id of this file. Skipping. \n";
}
return response;
};
module.exports.details = details;
module.exports.plugin = plugin;