From 543dd99587594d43b34fb9fa4b79ac6856c14295 Mon Sep 17 00:00:00 2001 From: MrMeeb Date: Sat, 10 Jun 2023 14:24:15 +0000 Subject: [PATCH] Upload files to '' --- Tdarr_Plugin_MM1_MrMeeb_Full_Stack.js | 559 ++++++++++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100644 Tdarr_Plugin_MM1_MrMeeb_Full_Stack.js diff --git a/Tdarr_Plugin_MM1_MrMeeb_Full_Stack.js b/Tdarr_Plugin_MM1_MrMeeb_Full_Stack.js new file mode 100644 index 0000000..62cfc6e --- /dev/null +++ b/Tdarr_Plugin_MM1_MrMeeb_Full_Stack.js @@ -0,0 +1,559 @@ +const details = () => ({ + id: 'Tdarr_Plugin_MM1_Full_Stack', + Stage: 'Pre-processing', + Name: 'MrMeeb Full Stack Processing', + Type: 'Video', + 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', + Version: '0.1', + Tags: 'pre-processing', + Inputs: [], +}); + + + + +// eslint-disable-next-line no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + + const lib = require('../methods/lib')(); + // load default plugin inputs + inputs = lib.loadDefaultValues(inputs, details); + + // Load response object + const response = { + processFile: false, // If set to false, the file will be skipped. Set to true to have the file transcoded. + preset: '', // HandBrake/FFmpeg CLI arguments you'd like to use. + // For FFmpeg, the input arguments come first followed by , followed by the output argument. + // Examples + // HandBrake + // '-Z "Very Fast 1080p30"' + // FFmpeg + // '-sn -map_metadata -1 -c:v copy -c:a copy' + container: '.mp4', // The container of the transcoded output file. + handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding + FFmpegMode: false, + infoLog: '', // This will be shown when the user clicks the 'i' (info) button on a file in the output queue if + // it has been skipped. + // Give reasons why it has been skipped ('File has no title metadata, File meets conditions!') + + // Optional (include together) + //file, + //removeFromDB: false, // Tell Tdarr to remove file from database if true + //updateDB: false, // Change file object above and update database if true + }; + + // Required global variables for functions + let ffmpegAudioFirstTrack = ''; + let ffmpegAudioOtherTracks = ''; + let ffmpegSubs = ''; + let ffmpegVideo = ''; + + // Defining functions + + const processAudio = (file, librarySettings, inputs, otherArguments) => { + + // Function variables + let audioIdx = -1; + let audioIdxTruehd = -1; + let trueHD = -1; + let trueHDi = -1; + let ac3Count = 0; + + console.log(` === Running processAudio function === `) + + // Go through all streams, hunting for TrueHD + for (let i = 0; i < file.ffProbeData.streams.length; i ++) { + + // Work with audio streams only + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + + let MMCodec = file.ffProbeData.streams[i].codec_name.toUpperCase(); + let MMChannelLayout = file.ffProbeData.streams[i].channel_layout.replace('(side)', ''); + + //console.log(`Stream ${i} is an audio stream`); + + // Keep track of audio stream relative numbers + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") { + audioIdx++; + } + } catch (err) {} + + // Identify wanted tracks + try { + + // Identify if stream is TrueHD + if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'truehd') { + + console.log(`Stream ${i} (audio stream ${audioIdx}) is TrueHD. Commencing TrueHD handling process`); + response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is TrueHD. Commencing TrueHD handling process.\n` + + trueHD = audioIdx; + trueHDi = i; + let trueHDLang = file.ffProbeData.streams[i].tags.language + let trueHDReplacementCount = 0 + + // Look for a suitable AC3 5.1 track to replace it + + for (let i = 0; i < file.ffProbeData.streams.length; i ++) { + + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + + audioIdxTruehd++; + MMCodec = file.ffProbeData.streams[i].codec_name.toUpperCase(); + MMChannelLayout = file.ffProbeData.streams[i].channel_layout.replace('(side)', ''); + + try { + + 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 ${audioIdxTruehd}) is probably a commentary track. Removing.`) + response.infoLog += `Stream ${i} (audio stream ${audioIdxTruehd}) is probably a commentary track. Removing.\n` + + } + + // Identify if stream is AC3 5.1 + else if ( + file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3' && + file.ffProbeData.streams[i].channels === 6 && + file.ffProbeData.streams[i].tags.language === trueHDLang + ) { + + console.log(`Stream ${i} (audio stream ${audioIdxTruehd}) is AC3 5.1 and in matching language. Suitable replacement.`); + console.log(`${MMCodec} <- Should be AC3`) + + response.infoLog += `Stream ${i} (audio stream ${audioIdxTruehd}) is AC3 5.1 and in matching language. Suitable replacement.\n` + response.infoLog +=`${MMCodec} <- Should be AC3\n` + + trueHDReplacementCount++ + + if (trueHDReplacementCount <= 1) { + + ffmpegAudioFirstTrack = ` -map 0:a:${audioIdxTruehd} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default` + + } + 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' && + file.ffProbeData.streams[i].channels === 6 && + file.ffProbeData.streams[i].tags.language !== trueHDLang && + file.ffProbeData.streams[i].tags.language.toLowerCase() == 'eng' + ) { + + console.log(`Stream ${i} (audio stream ${audioIdxTruehd}) 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 ${audioIdxTruehd}) 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:${audioIdxTruehd}` + + } + + else if (file.ffProbeData.streams[i].codec_name.toLowerCase() !== 'truehd') { + + console.log(`Stream ${i} (audio stream ${audioIdxTruehd}) isn't AC3 5.1, and TrueHD is already available to create an AC3 5.1 track if not present. Removing.`) + response.infoLog += `Stream ${i} (audio stream ${audioIdxTruehd}) isn't AC3 5.1, and TrueHD is already available to create an AC3 5.1 track if not present. Removing.\n` + + }; + + } catch (error) { + + } + + }; + + }; + + if (trueHDReplacementCount = 0) { + + console.log('A suitable TrueHD replacement track is not available. Creating my own.') + //console.log(`${MMCodec} <- Should be AC3`) + + + if (file.ffProbeData.streams[trueHDi].channels > 6) { + + 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` + + } + else { + + 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` + + } + + + } + + } + + } catch (error) { + console.log(error) + } + + } + + }; + + // Logic for if no TrueHD track present + if (trueHD < 0) { + + console.log('No TrueHD track is present in the file.') + response.infoLog += `No TrueHD track is present.\n` + audioIdx = -1 + + for (let i = 0; i < file.ffProbeData.streams.length; i ++) { + + // Work with audio streams only + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + + //console.log(`Stream ${i} is an audio stream`); + + // Keep track of audio stream relative numbers + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") { + audioIdx++; + } + } catch (err) {} + + try { + // Try to identify commentary tracks + 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 ${audioIdx}) is probably a commentary track. Leaving it out`) + response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is probably a commentary track. Removing.\n` + + } + + else if ( + file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3' && + file.ffProbeData.streams[i].channels == 6 + ) { + + console.log(`Stream ${i} (audio stream ${audioIdx}) is an AC3 5.1 track`) + response.infoLog += `Stream ${i} (audio stream ${audioIdx}) is an AC3 5.1 track.\n` + ac3Count++ + //console.log(`ac3Count = ${ac3Count}`) + + } + + //console.log(`codec_type = ${file.ffProbeData.streams[i].codec_name.toLowerCase()}`) + //console.log(`channels = ${file.ffProbeData.streams[i].channels == 6}`) + //console.log(`ac3Count = ${ac3Count}`) + + } catch (error) { + + } + + } + + } + + if (ac3Count == 0) { + + //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.`) + 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' + + if (file.ffProbeData.streams[1].codec_type.toLowerCase() === 'audio') { + + let MMCodec = file.ffProbeData.streams[1].codec_name.toUpperCase(); + let MMChannelLayout = file.ffProbeData.streams[1].channel_layout.replace('(side)', ''); + console.log(`Assuming stream 2 will be the best audio track available.`) + + console.log(`Stream 2 is ${file.ffProbeData.streams[1].codec_name} with ${file.ffProbeData.streams[1].channels} channels.`) + response.infoLog += `Stream 2 is ${file.ffProbeData.streams[1].codec_name} with ${file.ffProbeData.streams[1].channels} channels.\n` + + 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` + + } + 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` + + } + + } + + else { + + console.log(`Stream 2 isn't an audio track. Taking no action.`) + response.infoLog += `☒ Stream 2 isn't an audio track. Taking no action.\n` + + }; + + } + + if (ac3Count == 1) { + + audioIdx = -1; + + console.log(`Only 1 AC3 5.1 track present, checking to see if it's correctly labelled`) + response.infoLog += `Only 1 AC3 5.1 track present, checking to see if it's correctly labelled.\n` + + for (let i = 0; i < file.ffProbeData.streams.length; i ++) { + + // Work with audio streams only + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + + //console.log(`Stream ${i} is an audio stream`); + let MMCodec = file.ffProbeData.streams[i].codec_name.toUpperCase(); + let MMChannelLayout = file.ffProbeData.streams[i].channel_layout.replace('(side)', ''); + + // Keep track of audio stream relative numbers + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") { + audioIdx++; + //console.log(`audioIdx = ${audioIdx}`) + } + } catch (err) {} + + // Look only for AC3 5.1 streams (should only find 1) + if ( + file.ffProbeData.streams[i].codec_name.toLowerCase() == "ac3" && + file.ffProbeData.streams[i].channels == 6 + ) { + + // Checking if AC3 5.1 already default and labelled - sign the file has probably already been processed + if ( + file.ffProbeData.streams[i].disposition.default = 1 && + file.ffProbeData.streams[i].tags.title === `${MMCodec} - ${MMChannelLayout} - MM` + ) { + + console.log('Track is labelled correctly. No further work required.') + response.infoLog += '☑ Track is labelled correctly. No further work required.\n' + + } + + // Checking if AC3 5.1 exists but not labelled correctly + else if ( + file.ffProbeData.streams[i].disposition.default != 1 || + file.ffProbeData.streams[i].tags.title !== `${MMCodec} - ${MMChannelLayout} - MM` + ) { + + 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` + ffmpegAudioFirstTrack = ` -map 0:a:${audioIdx} -metadata:s:a:0 "title=${MMCodec} - ${MMChannelLayout} - MM" -disposition:a:0 default` + + } + + } + + } + + } + + } + + if (ac3Count > 1) { + + console.log(`There is more than 1 AC3 5.1 track. This is probably due to it being a foreign-language film with an additional English dub.`) + console.log(`Checking for signs that the first audio track has been processed`) + console.log(`Assuming stream 2 is the first audio track`) + response.infoLog += `There is more than 1 AC3 5.1 track. This is probably due to it being a foreign-language film with an additional English dub.\n Checking for signs that the first audio track has been processed.\n Assuming stream 2 is the first audio track.\n` + + if ( + file.ffProbeData.streams[1].codec_type.toLowerCase() === 'audio' && + file.ffProbeData.streams[1].codec_name.toLowerCase() === 'ac3' && + file.ffProbeData.streams[1].tags.title != undefined && + file.ffProbeData.streams[1].tags.title.includes("MM") + ) { + + console.log(`First track is labelled by this plugin, and set as default - safe to assume this file has been processed. Taking no action.`) + response.infoLog += `☑ First track is labelled by this plugin, and set as default - safe to assume this file has been processed. Taking no action.\n` + + } + + else { + + console.log(`First audio stream is AC3 5.1, but not labelled as expected. Resolving.`) + response.infoLog += `First audio stream is AC3 5.1, but not labelled as expected. Resolving.\n` + + let MMCodec = file.ffProbeData.streams[1].codec_name.toUpperCase(); + 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` + + console.log(`Copying over other AC3 5.1 tracks`) + + audioIdx = 0 //Set to 0 since already accounted for track 1 + + for (let i = 2; i < file.ffProbeData.streams.length; i ++) { + + // Work with audio streams only + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + + //console.log(`Stream ${i} is an audio stream`); + + // Keep track of audio stream relative numbers + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "audio") { + audioIdx++; + //console.log(`audioIdx = ${audioIdx}`) + } + } catch (err) {} + + ffmpegAudioOtherTracks += ` -map 0:a:${audioIdxTruehd}` + + }; + + }; + + }; + + }; + + }; + + }; + + const processSubs = (file, librarySettings, inputs, otherArguments) => { + + // Function variables + let subIdx = -1; + + console.log(` === Running processSubs function === `) + + // Go through all subtitles, removing non-English/German subs and commentary subs + for (let i = 0; i < file.ffProbeData.streams.length; i ++) { + + // Work with subtitle streams only + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'subtitle') { + + //console.log(`Stream ${i} is a subtitle stream`); + + // Keep track of subtitle stream relative numbers + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() == "subtitle") { + subIdx++; + } + } catch (err) {} + + //console.log(`language = ${file.ffProbeData.streams[i].tags.language.toLowerCase()}`) + + // Remove any subtitles not in English or German + if ( + file.ffProbeData.streams[i].tags.language.toLowerCase() !== "eng" && + file.ffProbeData.streams[i].tags.language.toLowerCase() !== "ger" + ) { + + console.log(`Stream ${i} (subtitle stream ${subIdx}) is not in a desired language. Removing.`) + response.infoLog += `Stream ${i} (subtitle stream ${subIdx}) is not in a desired language. Removing. \n` + + ffmpegSubs += ` -map -0:s:${subIdx}` + + } + + // Remove any subtitles that are for commentary + else 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} (subtitle stream ${subIdx}) is for a commentary track. Removing.`) + response.infoLog += `Stream ${i} (subtitle stream ${subIdx}) is for a commentary track. Removing. \n` + + ffmpegSubs += ` -map -0:s:${subIdx}` + + } + + else { + + console.log(`Stream ${i} (subtitle stream ${subIdx}) is wanted. Keeping.`) + response.infoLog += `Stream ${i} (subtitle stream ${subIdx}) is wanted. Keeping. \n` + + } + + }; + + }; + + }; + + const processVideo = (file, librarySettings, inputs, otherArguments) => { + + console.log(` === Running processVideo function === `) + + // Check if video track is already HEVC + if (file.ffProbeData.streams[0].codec_name.toLowerCase() === 'hevc') { + + console.log(`File is already HEVC. No action needed.`) + response.infoLog += `☑ File is already HEVC. No action needed.\n` + + } + + else { + + console.log('File needs transcoding to HEVC. Converting now.') + 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` + + } + + }; + + // 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; + + } + + }; + +module.exports.details = details; +module.exports.plugin = plugin; \ No newline at end of file