From 114c57471617a09fad0674c8050855ca45320e59 Mon Sep 17 00:00:00 2001 From: Zach Gelnett Date: Wed, 14 Apr 2021 16:55:29 -0400 Subject: [PATCH 01/28] Modified the plugins to use the built in file object rather than creating their own mediainfo and ffprobe objects. --- ...Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js | 1509 +++++++++-------- .../Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js | 127 +- 2 files changed, 846 insertions(+), 790 deletions(-) diff --git a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js index ef4c22e..32092f6 100644 --- a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js +++ b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js @@ -1,738 +1,771 @@ -////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Author: JarBinks, Zachg99, Jeff47 -// Date: 08/19/2020 -// -// This is my attempt to create an all in one routine that will maintain my library in optimal format !!!!FOR MY REQUIREMENTS!!!! -// Chances are very good you will need to make some changes to this routine and it's partner in order to make it work for you -// Chances are also very good you will need to run linux commands and learn about ffmpeg, vaapi, Tdarr and this script -// -// With that out of the way...Tdarr is awesome because it allowed me to create data driven code that makes my library the best it could be. -// Thanks to everyone involved. Especially HaveAGitGat and Migz whos existing code and assistance were invaluable -// -// My belief is that given enough information about the video file an optimal configuration can be determined specific to that file -// This is based on what my goals are and uses external programs to gather as much useful information as possible to make decisions -// There is a lot that goes into the gather and analysis part because: -// It is the basis of the decisions and "garbage in, garbage out" -// The video files are far from perfect when we get them and we need to make sure we learn as much as possible -// -// The script adds metatags to the media files to control processing, or better yet not doing extra processing on the same file -// This is especially useful when resetting Tdarr because each file gets touched again, so this expedites a full library scan -// Tdarr does not seem to handle when a plugin code takes a while to run so all effort has been made minimize time within the plugin code -// This is especially noticeable on a library reset and these scripts because of the extra time spent analyzing the media files -// -// (TODO: If the file name has non standard characters in it some calls to external programs will fail, I have seen it in about 0.2% of files) -// -// Video: (Only one video stream is used!!) -// The script computes a desired bitrate based on the following equation -// (videoheight * videowidth * videoFPS) * targetcodeccompression -// The first 3 give a raw number of bits that the stream requires, however with encoding there is a certain amount of acceptable loss, this is targetcodeccompression -// This number is pretty low for hevc. I have found 0.07 to be about the norm. -// This means that for hevc only 7% of the raw bitrate is necessary to produce some decent results and actually I have used, and seen, as low as 3.5% -// -// If the source video is less than this rate the script will either: -// Copy the existing stream, if the codec is hevc -// Transcode the stream to hevc using 80% of the original streams bitrate -// It could probably be less but if the source is of low bitrate we don’t want to compromise too much on the transcode -// -// If the source media bitrate is close, within 10%, of the target bitrate and the codec is hevc, it will copy instead of transcode to preserve quality -// -// The script will do an on chip transcode, meaning the decode and encode is all done on chip, except for mpeg4 which must be decoded on the CPU -// -// (TODO: Videos with a framerate higher than a threshold, lets say 30, should be changed) -// -// Audio: (Only one audio stream is used!!) -// The script will choose one audio stream in the file that has: -// The desired language -// The highest channel count -// If the language is not set on the audio track it assumes it is in the desired language -// -// The audio bit rate is set to a threshold, currently 64K, * number channels in AAC. This seems to give decent results -// -// If the source audio is less than this rate the script will either: -// Copy the existing stream, if the codec is aac -// Transcode the stream to aac using 100% of the original streams bitrate -// It could probably be less but if the source is of low bitrate but, we don’t want to compromise too much on the transcode -// -// Subtitles: -// All are removed?? (TODO: ensure this is correct and mention the flag to keep them if desired) -// All are copied (They usually take up little space so I keep them) -// Any that are in mov_text will be converted to srt -// -// Chapters: -// If chapters are found the script keeps them unless... -// Any chapter start time is a negative number (Yes I have seen it) -// Any duplicate start times are found -// -// (TODO: incomplete chapter info gets added to or removed...I have seen 2 chapters in the beginning and then nothing) -// -// The second routine will add chapters at set intervals to any file that has no chapters -// -// Metadata: -// Global metadata is cleared, I.E. title -// Stream specific metadata is copied -// -// Some requirements: (You should really really really really read this!!!) -//!!!!! Docker on linux !!!!! -// Intel QSV compatible processor, I run it on an i5-9400 and I know earlier models have no HEVC capability or produce lessor results -// -// First off the Matching pair: -// Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile (JB - QSV(vaapi), H265, AAC, MKV, bitrate optimized) -// Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix (JB - MKV Stats, Chapters, Audio Language) -// -// The order I run them in: -// Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile (JB - H265, AAC, MKV, bitrate optimized) -// Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix (JB - MKV Stats, Chapters, Audio Language) -// -// I am running the docker image provided for Tdarr, however there are some additions that must be added in order for the script to run -// This is to add mediainfo and mkvtoolnix because these are used to get more media info and update the file without running a transcode -// -// Here is my docker config (I am running compose so yours might be a little different) -// Tdarr: -// image: haveagitgat/tdarr_aio:qsv -// container_name: tdarr -// restart: unless-stopped -// network_mode: host -// ports: -// - "8265:8265" -// environment: -// - PUID=${PUID} # default user id, defined in .env -// - PGID=${PGID} # default group id, defined in .env -// - TZ=${TZ} # timezone, defined in .env -// devices: -// - /dev/dri:/dev/dri -// volumes: -// - "${ROOT}/complete:/home/Tdarr/Media:rw" -// - /transtemp:/transtemp -// - "${ROOT}/config/Tdarr:/home/Tdarr/Documents/Tdarr:rw" -// - "/etc/localtime:/etc/localtime:ro" -// -// I then connect to the docker container by using the following command -// sudo docker exec -it tdarr /bin/bash -// -// **THIS IS NOT NEEDED** if mediainfo and mkvtoolnix are already installed in the container. The pro_latest works fine without this. -// -// Here is the script that I run after the docker container is up and running (This requires a couple of (y)es'es to complete) -// -// //It is important to get mediainfo from a custom repository because it is a newer version that includes JSON output -// sudo apt-get install wget -// sudo wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-12_all.deb && sudo dpkg -i repo-mediaarea_1.0-12_all.deb && sudo apt-get update -// sudo apt-get install mediainfo -// -// sudo wget -q -O - https://mkvtoolnix.download/gpg-pub-moritzbunkus.txt | sudo apt-key add - -// sudo sh -c 'echo "deb https://mkvtoolnix.download/ubuntu/ bionic main" >> /etc/apt/sources.list.d/bunkus.org.list' -// sudo sh -c 'echo "deb-src https://mkvtoolnix.download/ubuntu/ bionic main" >> /etc/apt/sources.list.d/bunkus.org.list' -// sudo apt update -// sudo apt install mkvtoolnix -// -////////////////////////////////////////////////////////////////////////////////////////////////////// - -function details() { - return { - id: "Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile", - Stage: "Pre-processing", - Name: "JB - QSV(vaapi), H265, AAC, MKV, bitrate optimized", - Type: "Video", - Operation: "Transcode", - Description: "***You should not use this*** until you read the comments at the top of the code and understand how it works **this does alot** and is 1 of 2 routines you should to run **Part 1** \n", - Version: "1.6", - Link: "https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js", - Tags: "pre-processing,ffmpeg,video,audio,qsv h265,aac" - } -} - -function plugin(file, librarySettings, inputs, otherArguments) { - - var response = { - processFile: false, - preset: "", - container: ".mkv", - handBrakeMode: false, - FFmpegMode: true, - reQueueAfter: true, - infoLog: "" - } - - var currentfilename = file._id; //.replace(/'/g, "'\"'\"'"); - - //Settings - ////////////////////////////////////////////////////////////////////////////////////////////////////// - //Process Handling - var intStatsDays = 21; //If the stats date on the file, usually for mkv only, are older than this it will first update them - - //Video - var targetvideocodec = "hevc"; //This is the basis of the routine, if you want to change it you probably want to use a different script - var boluse10bit = true; //This will encode in 10 bit - var minsizedifffortranscode = 1.2 //If the existing bitrate is this much more than the target bitrate it is ok to transcode, otherwise there might not be enough extra to get decent quality - var targetreductionforcodecswitchonly = 0.8; //When a video codec change happens and the source bitrate is lower than optimal, we still lower the bitrate by this since hevc is ok with a lower rate - - var maxvideoheight = 1080; //Any thing over this size, I.E. 4K, will be reduced to this - var targetcodeccompression = 0.075; //This effects the target bitrate by assuming a compresion ratio - - //Since videos can have many widths and heights we need to convert to pixels (WxH) to understand what we are dealing with and set a minimal optimal bitrate to not go below - var minvideopixels4K = 6500000; - var minvideorate4K = 8500000; - - var minvideopixels2K = 1500000; - var minvideorate2K = 2400000; - - var minvideopixelsHD = 750000; - var minvideorateHD = 1100000; - - var minvideorateSD = 450000; - - //Audio - var targetaudiocodec = "aac"; //Desired Audio Coded, if you change this it will might require code changes - var targetaudiolanguage = "eng"; //Desired Audio Language - var targetaudiobitrateperchannel = 64000; //64K per channel gives you the good lossy quality out of AAC - var targetaudiochannels = 6; //Any thing above this number of channels will be reduced to it, because I cannot listen to it - - //Subtitles - var bolIncludeSubs = false; - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - var proc = require("child_process"); - var bolStatsAreCurrent = false; - - //Run MediaInfo and load the results it into an object - ////////////////////////////////////////////////////////////////////////////////////////////////////// - response.infoLog += "Getting Media Info.\n"; - var objMedInfo = ""; - objMedInfo = JSON.parse(proc.execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - //Run ffprobe with full info and load the results it into an object - ////////////////////////////////////////////////////////////////////////////////////////////////////// - response.infoLog += "Getting FFProbe Info.\n"; - var objFFProbeInfo = ""; - objFFProbeInfo = JSON.parse(proc.execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - // response.processFile = false; - // response.infoLog += objMedInfo + " \n"; - // return response; - - //response.infoLog += "HomePath:" + JSON.stringify(otherArguments, null, 4) + "\n"; - //response.infoLog += "FIID:" + file._id + "\n"; - //response.infoLog += "IPID:" + inputs._id + "\n"; - //response.infoLog += "FIDB:" + JSON.stringify(file, null, 4) + "\n"; - //response.infoLog += "CacheDir:" + librarySettings.cache + "\n"; - //response.infoLog += "filename:" + require("crypto").createHash("md5").update(file._id).digest("hex") + "\n"; - //response.infoLog += "MediaInfo:" + JSON.stringify(objMedInfo, null, 4) + "\n"; - //response.infoLog += "FFProbeInfo:" + JSON.stringify(objFFProbeInfo, null, 4) + "\n"; - //response.infoLog += "file.ffProbeData:" + JSON.stringify(file.ffProbeData, null, 4) + "\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. Exiting \n"; - return response; - } - - //If the file has already been processed we dont need to do more -// if (file.container == "mkv" && (objMedInfo.media.track[0].extra != undefined && objMedInfo.media.track[0].extra.JBDONEVERSION != undefined && objMedInfo.media.track[0].extra.JBDONEVERSION == "1")) { -// response.processFile = false; -// response.infoLog += "File already Processed! \n"; -// return response; -// } - - //If the existing container is mkv there is a possbility the stats were not updated during any previous transcode, lets make sure - if (file.container == "mkv") { - var datStats = Date.parse(new Date(70, 1).toISOString()); - if (objFFProbeInfo.streams[0].tags != undefined && objFFProbeInfo.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] != undefined) { - datStats = Date.parse(objFFProbeInfo.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] + " GMT"); - } - - if (objMedInfo.media.track[0].extra != undefined && objMedInfo.media.track[0].extra.JBDONEDATE != undefined) { - var JBDate = Date.parse(objMedInfo.media.track[0].extra.JBDONEDATE); - - response.infoLog += "JBDate: " + JBDate + ", StatsDate: " + datStats + "\n"; - if (datStats >= JBDate) { - bolStatsAreCurrent = true; - } - } else { - var statsThres = Date.parse(new Date(new Date().setDate(new Date().getDate()- intStatsDays)).toISOString()); - - response.infoLog += "StatsThres: " + statsThres + ", StatsDate: " + datStats + "\n"; - if (datStats >= statsThres) { - bolStatsAreCurrent = true; - } - } - - if (!bolStatsAreCurrent) { - response.infoLog += "Stats need to be updated! \n"; - - try { - output = proc.execSync('mkvpropedit --add-track-statistics-tags "' + currentfilename + '"'); - } catch(err) { - response.infoLog += "Error Updating Status Probably Bad file, A remux will probably fix, will continue\n"; - } - response.infoLog += "Getting Stats Objects, again!\n"; - objMedInfo = JSON.parse(proc.execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); - objFFProbeInfo = JSON.parse(proc.execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); - } - } - - //Logic Controls - var bolscaleVideo = false; - var boltranscodeVideo = false; - var optimalbitrate = 0; - var videonewwidth = 0; - var bolSource10bit = false; - var boltranscodeSoftwareDecode = false; - - var audionewchannels = 0; - var boltranscodeAudio = false; - var boldownmixAudio = false; - - var audioChannels = 0; - var audioBitrate = 0; - var audioIdxChannels = 0; - var audioIdxBitrate = 0; - - var boldosubs = false; - var bolforcenosubs = true; - var boldosubsconvert = false; - - var boldochapters = true; - - // Set up required variables - var videoIdx = -1; - var videoIdxFirst = -1; - var audioIdx = -1; - var audioIdxOther = -1; - - var strstreamType = ""; - - // Go through each stream in the file. - for (var i = 0; i < objFFProbeInfo.streams.length; i++) { - - strstreamType = objFFProbeInfo.streams[i].codec_type.toLowerCase(); - - //Looking For Video - ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (strstreamType == "video") { - //First we need to check if it is included in the MediaInfo struture, it might not be (mjpeg??, others??) - var MILoc = findMediaInfoItem(objMedInfo, i); - if (MILoc > -1) { - var streamheight = objFFProbeInfo.streams[i].height * 1; - var streamwidth = objFFProbeInfo.streams[i].width * 1; - var streamFPS = objMedInfo.media.track[MILoc].FrameRate * 1; - var streamBR = objMedInfo.media.track[MILoc].BitRate * 1; - - response.infoLog += "Video stream " + i + ":" + Math.floor(objFFProbeInfo.format.duration / 60) + ":" + objFFProbeInfo.streams[i].codec_name + ((bolSource10bit) ? "(10)" : ""); - response.infoLog += ":" + streamwidth + "x" + streamheight + "x" + streamFPS + ":" + streamBR + "bps \n"; - - if (videoIdxFirst == -1) { - videoIdxFirst = i; - } - - if (videoIdx == -1) { - videoIdx = i; - } else { - var MILocC = findMediaInfoItem(objMedInfo,videoIdx); - var curstreamheight = objFFProbeInfo.streams[videoIdx].height * 1; - var curstreamwidth = objFFProbeInfo.streams[videoIdx].width * 1; - var curstreamFPS = objMedInfo.media.track[MILocC].FrameRate * 1; - var curstreamBR = objMedInfo.media.track[MILocC].BitRate * 1; - - //Only check here based on bitrate and video width - if (streamBR > curstreamBR && streamwidth >= curstreamwidth) { - videoIdx = i; - } - } - } - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - //Looking For Audio - ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (strstreamType == "audio") { - //response.processFile = false; - //response.infoLog += i + ":" + objFFProbeInfo.streams[i].tags.language + " \n"; - //audioIdxFirst = i; - - //response.infoLog += JSON.stringify(objFFProbeInfo.streams[i]) + " \n"; - - //console.log("value of audio i: " + i + "; findmediainfoitem return value: " + findMediaInfoItem(objMedInfo, i)); - - //console.log("streamorder: " + objMedInfo.media.track[i].StreamOrder); - - audioChannels = objFFProbeInfo.streams[i].channels * 1; - audioBitrate = objMedInfo.media.track[findMediaInfoItem(objMedInfo, i)].BitRate * 1; - - if (objFFProbeInfo.streams[i].tags != undefined && objFFProbeInfo.streams[i].tags.language == targetaudiolanguage) { - response.infoLog += "Audio stream " + i + ":" + targetaudiolanguage + ":" + objFFProbeInfo.streams[i].codec_name + ":" + audioChannels + ":" + audioBitrate + "bps:"; - - if (audioIdx == -1) { - response.infoLog += "First Audio Stream \n"; - audioIdx = i; - } else { - - audioIdxChannels = objFFProbeInfo.streams[audioIdx].channels * 1; - audioIdxBitrate = objMedInfo.media.track[findMediaInfoItem(objMedInfo, audioIdx)].BitRate; - - if (audioChannels > audioIdxChannels) { - response.infoLog += "More Audio Channels \n"; - audioIdx = i; - } else if (audioChannels == audioIdxChannels && audioBitrate > audioIdxBitrate) { - response.infoLog += "Higher Audio Rate \n"; - audioIdx = i; - } - } - } else { - response.infoLog += "Audio stream " + i + ":???:" + objFFProbeInfo.streams[i].codec_name + ":" + audioChannels + ":" + audioBitrate + "bps:"; - - if (audioIdxOther == -1) { - response.infoLog += "First Audio Stream \n"; - audioIdxOther = i; - } else { - audioIdxChannels = objFFProbeInfo.streams[audioIdxOther].channels * 1; - audioIdxBitrate = objMedInfo.media.track[findMediaInfoItem(objMedInfo, audioIdxOther)].BitRate; - - if (audioChannels > audioIdxChannels) { - response.infoLog += "More Audio Channels \n"; - audioIdxOther = i; - } else if (audioChannels == audioIdxChannels && audioBitrate > audioIdxBitrate) { - response.infoLog += "Higher Audio Rate \n"; - audioIdxOther = i; - } - } - } - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - //Looking For Subtitles - ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (!bolforcenosubs && !boldosubs && (strstreamType == "text" || strstreamType == "subtitle")) { - if (objMedInfo.media.track[findMediaInfoItem(objMedInfo, i)].CodecID != "S_TEXT/WEBVTT") { //A sub has an S_TEXT/WEBVTT codec, ffmpeg will fail with it - boldosubs = true; - if (objFFProbeInfo.streams[i].codec_name == "mov_text") { - boldosubsconvert = true; - response.infoLog += "SubTitles Found (mov_text), will convert \n"; - } else { - response.infoLog += "SubTitles Found, will copy \n"; - } - } else { - response.infoLog += "SubTitles Found (S_TEXT/WEBVTT), will not copy \n"; - bolforcenosubs = true; - } - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - } - - //If the file has already been processed we dont need to do more - if (file.container == "mkv" && objFFProbeInfo.streams[videoIdx].codec_name == targetvideocodec && (objMedInfo.media.track[0].extra != undefined && objMedInfo.media.track[0].extra.JBDONEVERSION != undefined && objMedInfo.media.track[0].extra.JBDONEVERSION == "1")) { - - var audioIdxChk = 0; - - if (audioIdx != -1) - audioIdxChk = audioIdx; - else audioIdxChk = audioIdxOther; - - if (objFFProbeInfo.streams[audioIdxChk].codec_name == targetaudiocodec) { - response.processFile = false; - response.infoLog += "File already Processed! \n"; - return response; - } - - } - - //return response; - - // Go through chapters in the file looking for badness - ////////////////////////////////////////////////////////////////////////////////////////////////////// - for (var i = 0; i < objFFProbeInfo.chapters.length; i++) { - - //Bad start times - if (objFFProbeInfo.chapters[i].start_time < 0) { - boldochapters = false; - break; //Dont need to continue because we know they are bad - } - - //Duplicate start times - for (var x = 0; i < objFFProbeInfo.chapters.length; i++) { - if (i != x && objFFProbeInfo.chapters[i].start_time == objFFProbeInfo.chapters[x].start_time) { - boldochapters = false; - break; //Dont need to continue because we know they are bad - } - } - } - - //Video Decision section - ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (videoIdx == -1) { - response.processFile = false; - response.infoLog += "No Video Track !! \n"; - return response; - } - - boltranscodeVideo = true; //We will assume we will be transcoding - var MILoc = findMediaInfoItem(objMedInfo, videoIdx); - - var videoheight = objFFProbeInfo.streams[videoIdx].height * 1; - var videowidth = objFFProbeInfo.streams[videoIdx].width * 1; - var videoFPS = objMedInfo.media.track[MILoc].FrameRate * 1; - var videoBR = objMedInfo.media.track[MILoc].BitRate * 1; - - if (objFFProbeInfo.streams[videoIdx].profile.includes("10")) { - bolSource10bit = true; - } - - //Lets see if we need to scal down the video size - if (videoheight > maxvideoheight) { - bolscaleVideo = true; - videonewwidth = Math.floor((maxvideoheight / videoheight) * videowidth); - response.infoLog += "Video Resolution, " + videowidth + "x" + videoheight + ", need to convert to " + videonewwidth + "x" + maxvideoheight + " \n"; - videoheight = maxvideoheight; - videowidth = videonewwidth; - } - - //Figure out the desired bitrate - optimalvideobitrate = Math.floor((videoheight * videowidth * videoFPS) * targetcodeccompression); - - //We need to check for a minimum bitrate - if ((videoheight * videowidth) > minvideopixels4K && optimalvideobitrate < minvideopixels4K) { - response.infoLog += "Video Bitrate calulcated for 4K, " + optimalvideobitrate + ", is below minimum, " + minvideopixels4K +" \n"; - optimalvideobitrate = minvideorate4K; - } else if ((videoheight * videowidth) > minvideopixels2K && optimalvideobitrate < minvideorate2K) { - response.infoLog += "Video Bitrate calulcated for 2K, " + optimalvideobitrate + ", is below minimum, " + minvideorate2K + " \n"; - optimalvideobitrate = minvideorate2K; - } else if ((videoheight * videowidth) > minvideopixelsHD && optimalvideobitrate < minvideorateHD) { - response.infoLog += "Video Bitrate calulcated for HD, " + optimalvideobitrate + ", is below minimum, " + minvideorateHD + " \n"; - optimalvideobitrate = minvideorateHD; - } else if (optimalvideobitrate < minvideorateSD) { - response.infoLog += "Video Bitrate calulcated for SD, " + optimalvideobitrate + ", is below minimum, " + minvideorateSD +" \n"; - optimalvideobitrate = minvideorateSD; - } - - //Check if it is already hvec, if not then we must transcode - if (objFFProbeInfo.streams[videoIdx].codec_name != targetvideocodec) { - response.infoLog += "Video existing Codex is " + objFFProbeInfo.streams[videoIdx].codec_name + ((bolSource10bit) ? "(10)" : ""); - response.infoLog += ", need to convert to " + targetvideocodec + ((boluse10bit) ? "(10)" : "") + " \n"; - - if (objFFProbeInfo.streams[videoIdx].codec_name == "mpeg4") { - boltranscodeSoftwareDecode = true; - response.infoLog += "Video existing Codex is " + objFFProbeInfo.streams[videoIdx].codec_name + ", need to decode with software codec \n"; - } else if (objFFProbeInfo.streams[videoIdx].codec_name == "h264" && objFFProbeInfo.streams[videoIdx].profile.includes("10")) { - //If the source is 10 bit then we must software decode since qsv will not decode 264 10 bit?? - boltranscodeSoftwareDecode = true; - response.infoLog += "Video existing Codex is " + objFFProbeInfo.streams[videoIdx].codec_name + " 10 bit, need to decode with software codec \n"; - } - } - - if (videoBR < (optimalvideobitrate * minsizedifffortranscode)) { - //We need to be careful here are else we could produce a bad quality - response.infoLog += "Low source bitrate! \n"; - if (objFFProbeInfo.streams[videoIdx].codec_name == targetvideocodec) { - if (bolSource10bit == boluse10bit) { - response.infoLog += "Video existing Bitrate, " + videoBR + ", is close to target Bitrate, " + optimalvideobitrate + ", using existing stream \n"; - boltranscodeVideo = false; - } else { - response.infoLog += "Video existing bit depth is different from target, without a codec change, using using existing bitrate \n"; - optimalvideobitrate = videoBR; - } - } else { - //We have a codec change with not much meat so we need to adjust are target rate - response.infoLog += "Video existing Bitrate, " + videoBR + ", is close to, or lower than, target Bitrate, "; - response.infoLog += optimalvideobitrate + ", with a codec change, using " + Math.floor(targetreductionforcodecswitchonly * 100) + "% of existing \n"; - optimalvideobitrate = Math.floor(videoBR * targetreductionforcodecswitchonly); - boltranscodeVideo = true; - } - } else { - //We already know the existing bitrate has enough meat for a decent transcode - //boltranscodeVideo = true; - response.infoLog += "Video existing Bitrate, " + videoBR + ", is higher than target, " + optimalvideobitrate + ", transcoding \n"; - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - //Audio Decision section - ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (audioIdx == -1) { - if (audioIdxOther != -1) { - response.infoLog += "Using Unknown Audio Track !! \n"; - audioIdx = audioIdxOther; - } else { - response.processFile = false; - response.infoLog += "No Audio Track !! \n"; - return response; - } - } - - var audioBR = objMedInfo.media.track[findMediaInfoItem(objMedInfo, audioIdx)].BitRate * 1; - - if (objFFProbeInfo.streams[audioIdx].channels > targetaudiochannels) { - boldownmixAudio = true; - audionewchannels = targetaudiochannels; - response.infoLog += "Audio existing Channels, " + objFFProbeInfo.streams[audioIdx].channels + ", is higher than target, " + targetaudiochannels + " \n"; - } else { - audionewchannels = objFFProbeInfo.streams[audioIdx].channels; - } - - var optimalaudiobitrate = audionewchannels * targetaudiobitrateperchannel; - - //Now what are we going todo with the audio part - if (audioBR > (optimalaudiobitrate * 1.1)) { - boltranscodeAudio = true; - response.infoLog += "Audio existing Bitrate, " + audioBR + ", is higher than target, " + optimalaudiobitrate + " \n"; - } - - //If the audio codec is not what we want then we should transcode - if (objFFProbeInfo.streams[audioIdx].codec_name != targetaudiocodec) { - boltranscodeAudio = true; - response.infoLog += "Audio Codec, " + objFFProbeInfo.streams[audioIdx].codec_name + ", is different than target, " + targetaudiocodec + ", Changing \n"; - } - - //If the source bitrate is less than out target bitrate we should not ever go up - if (audioBR < optimalaudiobitrate) { - response.infoLog += "Audio existing Bitrate, " + audioBR + ", is lower than target, " + optimalaudiobitrate + ", using existing "; - optimalaudiobitrate = audioBR; - if (objFFProbeInfo.streams[audioIdx].codec_name != targetaudiocodec) { - response.infoLog += "rate"; - }else{ - response.infoLog += "stream"; - } - response.infoLog += " \n"; - } - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - - // lets assemble our ffmpeg command - ////////////////////////////////////////////////////////////////////////////////////////////////////// - var strtrancodebasehw = " -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi "; - var strtrancodebasesw = " -vaapi_device /dev/dri/renderD128 "; - var strtranscodevideomapping = " -max_muxing_queue_size 8000 -map 0:{0} "; - var strtranscodevideocopy = " -c:v:0 copy "; - var strtranscodevideotranscoding = " -c:v:0 hevc_vaapi "; - var strtranscodevideooptions = ' -vf "{0}" '; //Used to make the output 10bit, I think the quotes need to be this way for ffmpeg - var strtranscodevideoscaling = "w=-1:h=1080"; //Used when video is above our target of 1080 - var strtranscodevideoformathw = "scale_vaapi="; //Used to make the output 10bit - var strtranscodevideoformat = "format={0}"; //Used to add filters to the hardware transcode - var strtranscodevideo10bit = "p010"; //Used to make the output 10bit - var strtranscodevideo8bit = "p008"; //Used to make the output 8bit - var strtranscodevideoswdecode = "hwupload"; //Used to make it use software decode if necessary - var strtranscodevideoswdecode10bit = "nv12|vaapi"; //Used to make it sure the software decode is in the proper pixel format - var strtranscodevideobitrate = " -b:v {0} "; //Used when video is above our target of 1080 - var strtranscodeaudiomapping = " -map 0:{0} "; - var strtranscodeaudiocopy = " -c:a:0 copy "; - var strtranscodeaudiotranscoding = " -c:a:0 ${targetaudiocodec} -b:a {0} "; - var strtranscodeaudiodownmixing = " -ac {0} "; - var strtranscodesubs = " -map 0:s -scodec copy "; - var strtranscodesubsconvert = " -map 0:s -c:s srt "; - var strtranscodesubsnone = " -map -0:s "; - var strtranscodemetadata = " -map_metadata:g -1 -metadata JBDONEVERSION=1 -metadata JBDONEDATE={0} "; - var strtranscodechapters = " -map_chapters {0} "; - - var strtranscodefileoptions = " "; - - var strFFcmd = ""; - if (boltranscodeVideo) { - if (boltranscodeSoftwareDecode) { - strFFcmd += strtrancodebasesw; - } else { - strFFcmd += strtrancodebasehw; - } - } - strFFcmd += strtranscodevideomapping.replace("{0}",videoIdx); - if (boltranscodeVideo) { - strFFcmd += strtranscodevideotranscoding; - - if (bolscaleVideo || boluse10bit || boltranscodeSoftwareDecode) { - var stroptions = ""; - var strformat = ""; - if (bolscaleVideo) { - stroptions += strtranscodevideoscaling; - } - if (strformat.length > 0) { - strformat += "="; - } - if (boluse10bit && !bolSource10bit) { - strformat += strtranscodevideo10bit; - } - if (!boluse10bit && bolSource10bit) { - strformat += strtranscodevideo8bit; - } - if (boltranscodeSoftwareDecode) { - if (bolSource10bit) { - if (strformat.length > 0) { - strformat += ","; - } - strformat += strtranscodevideoswdecode10bit; - } - if (strformat.length > 0) { - strformat += ","; - } - strformat += strtranscodevideoswdecode; - } - if (strformat.length > 0) { - if (stroptions.length > 0) { - stroptions += ","; - } - stroptions += strtranscodevideoformat.replace("{0}",strformat); - } - - if (boltranscodeSoftwareDecode) { - strFFcmd += strtranscodevideooptions.replace("{0}",stroptions); - } else { - strFFcmd += strtranscodevideooptions.replace("{0}",strtranscodevideoformathw + stroptions); - } - - } - strFFcmd += strtranscodevideobitrate.replace("{0}",optimalvideobitrate); - - } else { - strFFcmd += strtranscodevideocopy; - } - - strFFcmd += strtranscodeaudiomapping.replace("{0}",audioIdx); - if (boltranscodeAudio) { - strFFcmd += strtranscodeaudiotranscoding.replace("{0}",optimalaudiobitrate).replace("${targetaudiocodec}",targetaudiocodec); - } else { - strFFcmd += strtranscodeaudiocopy; - } - if (boldownmixAudio) { - strFFcmd += strtranscodeaudiodownmixing.replace("{0}",audionewchannels); - } - if (bolforcenosubs) { - strFFcmd += strtranscodesubsnone; - } else if (boldosubs) { - if (boldosubsconvert) { - strFFcmd += strtranscodesubsconvert; - } else { - strFFcmd += strtranscodesubs; - } - } - - strFFcmd += strtranscodemetadata.replace("{0}",new Date().toISOString()); - if (boldochapters) { - strFFcmd += strtranscodechapters.replace("{0}","0"); - } else { - strFFcmd += strtranscodechapters.replace("{0}","-1"); - } - - strFFcmd += strtranscodefileoptions; - ////////////////////////////////////////////////////////////////////////////////////////////////////// - - //response.infoLog += strFFcmd + "\n"; - - response.preset += strFFcmd; - response.processFile = true; - response.infoLog += "File needs work. Transcoding. \n"; - return response; -} - -function findMediaInfoItem(objMedInfo, index) { - for (var i = 0; i < objMedInfo.media.track.length; i++) { - //console.log("streamorder: " + objMedInfo.media.track[i].StreamOrder); - if (objMedInfo.media.track[i].StreamOrder != null && (objMedInfo.media.track[i].StreamOrder == index || objMedInfo.media.track[i].StreamOrder == "0-" + index)) { - return i; - } - } - return -1; -} - -module.exports.details = details; -module.exports.plugin = plugin; +////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Author: JarBinks, Zachg99, Jeff47 +// Date: 04/11/2021 +// +// This is my attempt to create an all in one routine that will maintain my library in optimal format !!!!FOR MY REQUIREMENTS!!!! +// Chances are very good you will need to make some changes to this routine and it's partner in order to make it work for you +// Chances are also very good you will need to run linux commands and learn about ffmpeg, vaapi, Tdarr and this script +// +// With that out of the way...Tdarr is awesome because it allowed me to create data driven code that makes my library the best it could be. +// Thanks to everyone involved. Especially HaveAGitGat and Migz whos existing code and assistance were invaluable +// +// My belief is that given enough information about the video file an optimal configuration can be determined specific to that file +// This is based on what my goals are and uses external programs to gather as much useful information as possible to make decisions +// There is a lot that goes into the gather and analysis part because: +// It is the basis of the decisions and "garbage in, garbage out" +// The video files are far from perfect when we get them and we need to make sure we learn as much as possible +// +// The script adds metatags to the media files to control processing, or better yet not doing extra processing on the same file +// This is especially useful when resetting Tdarr because each file gets touched again, so this expedites a full library scan +// Tdarr does not seem to handle when a plugin code takes a while to run so all effort has been made minimize time within the plugin code +// This is especially noticeable on a library reset and these scripts because of the extra time spent analyzing the media files +// +// (TODO: If the file name has non standard characters in it some calls to external programs will fail, I have seen it in about 0.2% of files) +// +// Video: (Only one video stream is used!!) +// The script computes a desired bitrate based on the following equation +// (videoheight * videowidth * videoFPS) * targetcodeccompression +// The first 3 give a raw number of bits that the stream requires, however with encoding there is a certain amount of acceptable loss, this is targetcodeccompression +// This number is pretty low for hevc. I have found 0.07 to be about the norm. +// This means that for hevc only 7% of the raw bitrate is necessary to produce some decent results and actually I have used, and seen, as low as 3.5% +// +// If the source video is less than this rate the script will either: +// Copy the existing stream, if the codec is hevc +// Transcode the stream to hevc using 80% of the original streams bitrate +// It could probably be less but if the source is of low bitrate we don’t want to compromise too much on the transcode +// +// If the source media bitrate is close, within 10%, of the target bitrate and the codec is hevc, it will copy instead of transcode to preserve quality +// +// The script will do an on chip transcode, meaning the decode and encode is all done on chip, except for mpeg4 which must be decoded on the CPU +// +// (TODO: Videos with a framerate higher than a threshold, lets say 30, should be changed) +// +// Audio: (Only one audio stream is used!!) +// The script will choose one audio stream in the file that has: +// The desired language +// The highest channel count +// If the language is not set on the audio track it assumes it is in the desired language +// +// The audio bit rate is set to a threshold, currently 64K, * number channels in AAC. This seems to give decent results +// +// If the source audio is less than this rate the script will either: +// Copy the existing stream, if the codec is aac +// Transcode the stream to aac using 100% of the original streams bitrate +// It could probably be less but if the source is of low bitrate but, we don’t want to compromise too much on the transcode +// +// Subtitles: +// All are removed?? (TODO: ensure this is correct and mention the flag to keep them if desired) +// All are copied (They usually take up little space so I keep them) +// Any that are in mov_text will be converted to srt +// +// Chapters: +// If chapters are found the script keeps them unless... +// Any chapter start time is a negative number (Yes I have seen it) +// Any duplicate start times are found +// +// (TODO: incomplete chapter info gets added to or removed...I have seen 2 chapters in the beginning and then nothing) +// +// The second routine will add chapters at set intervals to any file that has no chapters +// +// Metadata: +// Global metadata is cleared, I.E. title +// Stream specific metadata is copied +// +// Some requirements: (You should really really really really read this!!!) +//!!!!! Docker on linux !!!!! +// Intel QSV compatible processor, I run it on an i5-9400 and I know earlier models have no HEVC capability or produce lessor results +// +// First off the Matching pair: +// Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile (JB - QSV(vaapi), H265, AAC, MKV, bitrate optimized) +// Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix (JB - MKV Stats, Chapters, Audio Language) +// +// The order I run them in: +// Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile (JB - H265, AAC, MKV, bitrate optimized) +// Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix (JB - MKV Stats, Chapters, Audio Language) +// +// I am running the docker image provided for Tdarr +// +// Here is my docker config (I am running compose so yours might be a little different) +// tdarr_server: +// container_name: tdarr_server +// image: haveagitgat/tdarr:latest +// privileged: true +// restart: unless-stopped +// environment: +// - PUID=${PUID} # default user id, defined in .env +// - PGID=${PGID} # default group id, defined in .env +// - TZ=${TZ} # timezone, defined in .env +// - serverIP=tdarr_server #using internal docker networking. This should at least work when the nodes are on the same docker compose as the server +// - serverPort=8266 +// - webUIPort=8265 +// volumes: +// - ${ROOT}/tdarr/server:/app/server/Tdarr # Tdarr server files +// - ${ROOT}/tdarr/configs:/app/configs # config files - can be same as NODE (unless separate server) +// - ${ROOT}/tdarr/logs:/app/logs # Tdarr log files +// - ${ROOT}/tdarr/cache:/temp # Cache folder, Should be same path mapped on NODE +// - ${ROOT}/tdarr/testmedia:/home/Tdarr/testmedia # Should be same path mapped on NODE if using a test folder +// - ${ROOT}/tdarr/scripts:/home/Tdarr/scripts # my random way of saving script files +// - /volume1/video:/media # video library Should be same path mapped on NODE +// ports: +// - 8265:8265 #Exposed to access webui externally +// - 8266:8266 #Exposed to allow external nodes to reach the server +// logging: +// options: +// max-size: "2m" +// max-file: "3" +// +// tdarr_node: +// container_name: tdarr_node +// image: haveagitgat/tdarr_node:latest +// privileged: true +// restart: unless-stopped +// devices: +// - /dev/dri:/dev/dri +// environment: +// - PUID=${PUID} # default user id, defined in .env +// - PGID=${PGID} # default group id, defined in .env +// - TZ=${TZ} # timezone, defined in .env +// - serverIP=192.168.x.x #container name of the server, should be modified if server is on another machine +// - serverPort=8266 +// - nodeID=TDARRNODE_2 +// - nodeIP=192.168.x.x #container name of the node +// - nodePort=9267 #not exposed via a "ports: " setting as the server/node communication is done on the internal docker network and can communicate on all ports +// volumes: +// - ${ROOT}/tdarr/configs:/app/configs # config files - can be same as server (unless separate server) +// - ${ROOT}/tdarr/logs:/app/logs # config files - can be same as server (unless separate server) +// - ${ROOT}/tdarr/testmedia:/home/Tdarr/testmedia # Should be same path mapped on server if using a test folder +// - ${ROOT}/tdarr/scripts:/home/Tdarr/scripts # my random way of saving script files +// - ${ROOT}/tdarr/cache:/temp # Cache folder, Should be same path mapped on server +// - /mnt/video:/media # video library Should be same path mapped on server +// ports: +// - 9267:9267 +// logging: +// options: +// max-size: "2m" +// max-file: "3" +// +// +////////////////////////////////////////////////////////////////////////////////////////////////////// + +function details() { + return { + id: "Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile", + Stage: "Pre-processing", + Name: "JB - QSV(vaapi), H265, AAC, MKV, bitrate optimized", + Type: "Video", + Operation: "Transcode", + Description: "***You should not use this*** until you read the comments at the top of the code and understand how it works **this does alot** and is 1 of 2 routines you should to run **Part 1** \n", + Version: "2.0", + Link: "https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js", + Tags: "pre-processing,ffmpeg,video,audio,qsv h265,aac" + } +} + +function plugin(file, librarySettings, inputs, otherArguments) { + + var response = { + processFile: false, + preset: "", + container: ".mkv", + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: "" + } + + var currentfilename = file._id; //.replace(/'/g, "'\"'\"'"); + + //Settings + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //Process Handling + var intStatsDays = 21; //If the stats date on the file, usually for mkv only, are older than this it will first update them + + //Video + var targetvideocodec = "hevc"; //This is the basis of the routine, if you want to change it you probably want to use a different script + var boluse10bit = true; //This will encode in 10 bit + var targetframerate = 25; //Any frame rate greater than this will be adjusted + + var minsizedifffortranscode = 1.2 //If the existing bitrate is this much more than the target bitrate it is ok to transcode, otherwise there might not be enough extra to get decent quality + var targetreductionforcodecswitchonly = 0.8; //When a video codec change happens and the source bitrate is lower than optimal, we still lower the bitrate by this since hevc is ok with a lower rate + + var maxvideoheight = 2160; //Any thing over this size, I.E. 4K, will be reduced to this + var targetcodeccompression = 0.08; //This effects the target bitrate by assuming a compression ratio + + //Since videos can have many widths and heights we need to convert to pixels (WxH) to understand what we are dealing with and set a minimal optimal bitrate to not go below + var minvideopixels4K = 6500000; + var minvideorate4K = 8500000; + + var minvideopixels2K = 1500000; + var minvideorate2K = 2400000; + + var minvideopixelsHD = 750000; + var minvideorateHD = 1100000; + + var minvideorateSD = 450000; + + //Audio + var targetaudiocodec = "aac"; //Desired Audio Coded, if you change this it will might require code changes + var targetaudiolanguage = "eng"; //Desired Audio Language + var targetaudiobitrateperchannel = 64000; //64K per channel gives you the good lossy quality out of AAC + var targetaudiochannels = 6; //Any thing above this number of channels will be reduced to it, because I cannot listen to it + + //Subtitles + var bolIncludeSubs = true; + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + var proc = require("child_process"); + var bolStatsAreCurrent = false; + + //Run MediaInfo and load the results it into an object + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //response.infoLog += "Getting Media Info.\n"; + //var objMedInfo = ""; + //objMedInfo = JSON.parse(proc.execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + //Run ffprobe with full info and load the results it into an object + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //response.infoLog += "Getting FFProbe Info.\n"; + //var objFFProbeInfo = ""; + //objFFProbeInfo = JSON.parse(proc.execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + // response.processFile = false; + // response.infoLog += objMedInfo + " \n"; + // return response; + + //response.infoLog += "HomePath:" + JSON.stringify(otherArguments, null, 4) + "\n"; + //response.infoLog += "FIID:" + file._id + "\n"; + //response.infoLog += "IPID:" + inputs._id + "\n"; + //response.infoLog += "FIDB:" + JSON.stringify(file, null, 4) + "\n"; + //response.infoLog += "CacheDir:" + librarySettings.cache + "\n"; + //response.infoLog += "filename:" + require("crypto").createHash("md5").update(file._id).digest("hex") + "\n"; + //response.infoLog += "MediaInfo:" + JSON.stringify(objMedInfo, null, 4) + "\n"; + //response.infoLog += "FFProbeInfo:" + JSON.stringify(objFFProbeInfo, null, 4) + "\n"; + //response.infoLog += "objFFProbeInfo:" + JSON.stringify(objFFProbeInfo, null, 4) + "\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. Exiting \n"; + return response; + } + + //If the file has already been processed we dont need to do more + if (file.container == "mkv" && (file.mediaInfo.track[0].extra != undefined && file.mediaInfo.track[0].extra.JBDONEVERSION != undefined && file.mediaInfo.track[0].extra.JBDONEVERSION == "1")) { + response.processFile = false; + response.infoLog += "File already Processed! \n"; + return response; + } + + //If the existing container is mkv there is a possibility the stats were not updated during any previous transcode, lets make sure + if (file.container == "mkv") { + var datStats = Date.parse(new Date(70, 1).toISOString()); + if (file.ffProbeData.streams[0].tags != undefined && file.ffProbeData.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] != undefined) { + datStats = Date.parse(file.ffProbeData.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] + " GMT"); + } + + if (file.mediaInfo.track[0].extra != undefined && file.mediaInfo.track[0].extra.JBDONEDATE != undefined) { + var JBDate = Date.parse(file.mediaInfo.track[0].extra.JBDONEDATE); + + response.infoLog += "JBDate: " + JBDate + ", StatsDate: " + datStats + "\n"; + if (datStats >= JBDate) { + bolStatsAreCurrent = true; + } + } else { + var statsThres = Date.parse(new Date(new Date().setDate(new Date().getDate()- intStatsDays)).toISOString()); + + response.infoLog += "StatsThres: " + statsThres + ", StatsDate: " + datStats + "\n"; + if (datStats >= statsThres) { + bolStatsAreCurrent = true; + } + } + + if (!bolStatsAreCurrent) { + response.infoLog += "Stats need to be updated! \n"; + + try { + output = proc.execSync('mkvpropedit --add-track-statistics-tags "' + currentfilename + '"'); + } catch(err) { + response.infoLog += "Error Updating Status Probably Bad file, A remux will probably fix, will continue\n"; + } + response.infoLog += "Getting Stats Objects, again!\n"; + //objMedInfo = JSON.parse(proc.execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); + //objFFProbeInfo = JSON.parse(proc.execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); + } + } + + //Logic Controls + var bolscaleVideo = false; + var boltranscodeVideo = false; + var bolchangeframerateVideo = false; + var optimalbitrate = 0; + var videonewwidth = 0; + var bolSource10bit = false; + var boltranscodeSoftwareDecode = false; + + var audionewchannels = 0; + var boltranscodeAudio = false; + var boldownmixAudio = false; + + var audioChannels = 0; + var audioBitrate = 0; + var audioIdxChannels = 0; + var audioIdxBitrate = 0; + + var boldosubs = false; + var bolforcenosubs = false; + var boldosubsconvert = false; + + var boldochapters = true; + + // Set up required variables + var videoIdx = -1; + var videoIdxFirst = -1; + var audioIdx = -1; + var audioIdxOther = -1; + + var strstreamType = ""; + + // Go through each stream in the file. + for (var i = 0; i < file.ffProbeData.streams.length; i++) { + + strstreamType = file.ffProbeData.streams[i].codec_type.toLowerCase(); + + //Looking For Video + ////////////////////////////////////////////////////////////////////////////////////////////////////// + if (strstreamType == "video") { + //First we need to check if it is included in the MediaInfo struture, it might not be (mjpeg??, others??) + var MILoc = findMediaInfoItem(file, i); + if (MILoc > -1) { + var streamheight = file.ffProbeData.streams[i].height * 1; + var streamwidth = file.ffProbeData.streams[i].width * 1; + var streamFPS = file.mediaInfo.track[MILoc].FrameRate * 1; + var streamBR = file.mediaInfo.track[MILoc].BitRate * 1; + + response.infoLog += "Video stream " + i + ":" + Math.floor(file.meta.Duration / 60) + ":" + file.ffProbeData.streams[i].codec_name + ((bolSource10bit) ? "(10)" : ""); + response.infoLog += ":" + streamwidth + "x" + streamheight + "x" + streamFPS + ":" + streamBR + "bps \n"; + + if (videoIdxFirst == -1) { + videoIdxFirst = i; + } + + if (videoIdx == -1) { + videoIdx = i; + } else { + var MILocC = findMediaInfoItem(file,videoIdx); + var curstreamheight = file.ffProbeData.streams[videoIdx].height * 1; + var curstreamwidth = file.ffProbeData.streams[videoIdx].width * 1; + var curstreamFPS = file.mediaInfo.track[MILocC].FrameRate * 1; + var curstreamBR = file.mediaInfo.track[MILocC].BitRate * 1; + + //Only check here based on bitrate and video width + if (streamBR > curstreamBR && streamwidth >= curstreamwidth) { + videoIdx = i; + } + } + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + //Looking For Audio + ////////////////////////////////////////////////////////////////////////////////////////////////////// + if (strstreamType == "audio") { + //response.processFile = false; + //response.infoLog += i + ":" + objFFProbeInfo.streams[i].tags.language + " \n"; + //audioIdxFirst = i; + + //response.infoLog += JSON.stringify(objFFProbeInfo.streams[i]) + " \n"; + + audioChannels = file.ffProbeData.streams[i].channels * 1; + audioBitrate = file.mediaInfo.track[findMediaInfoItem(file, i)].BitRate * 1; + + if (file.ffProbeData.streams[i].tags != undefined && file.ffProbeData.streams[i].tags.language == targetaudiolanguage) { + response.infoLog += "Audio stream " + i + ":" + targetaudiolanguage + ":" + file.ffProbeData.streams[i].codec_name + ":" + audioChannels + ":" + audioBitrate + "bps:"; + + if (audioIdx == -1) { + response.infoLog += "First Audio Stream \n"; + audioIdx = i; + } else { + + audioIdxChannels = file.ffProbeData.streams[audioIdx].channels * 1; + audioIdxBitrate = file.mediaInfo.track[findMediaInfoItem(file, audioIdx)].BitRate; + + if (audioChannels > audioIdxChannels) { + response.infoLog += "More Audio Channels \n"; + audioIdx = i; + } else if (audioChannels == audioIdxChannels && audioBitrate > audioIdxBitrate) { + response.infoLog += "Higher Audio Rate \n"; + audioIdx = i; + } + } + } else { + response.infoLog += "Audio stream " + i + ":???:" + file.ffProbeData.streams[i].codec_name + ":" + audioChannels + ":" + audioBitrate + "bps:"; + + if (audioIdxOther == -1) { + response.infoLog += "First Audio Stream \n"; + audioIdxOther = i; + } else { + audioIdxChannels = file.ffProbeData.streams[audioIdxOther].channels * 1; + audioIdxBitrate = file.mediaInfo.track[findMediaInfoItem(file, audioIdxOther)].BitRate; + + if (audioChannels > audioIdxChannels) { + response.infoLog += "More Audio Channels \n"; + audioIdxOther = i; + } else if (audioChannels == audioIdxChannels && audioBitrate > audioIdxBitrate) { + response.infoLog += "Higher Audio Rate \n"; + audioIdxOther = i; + } + } + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + //Looking For Subtitles + ////////////////////////////////////////////////////////////////////////////////////////////////////// + if (!bolforcenosubs && !boldosubs && (strstreamType == "text" || strstreamType == "subtitle")) { + //if (file.mediaInfo.track[findMediaInfoItem(file, i)].CodecID != "S_TEXT/WEBVTT") { //A sub has an S_TEXT/WEBVTT codec, ffmpeg will fail with it + if (file.mediaInfo.track[findMediaInfoItem(file, i)].CodecID != "S_TEXT/WEBVTT") { //A sub has an S_TEXT/WEBVTT codec, ffmpeg will fail with it + boldosubs = true; + if (file.ffProbeData.streams[i].codec_name == "mov_text") { + boldosubsconvert = true; + response.infoLog += "SubTitles Found (mov_text), will convert \n"; + } else { + response.infoLog += "SubTitles Found, will copy \n"; + } + } else { + response.infoLog += "SubTitles Found (S_TEXT/WEBVTT), will not copy \n"; + bolforcenosubs = true; + } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + //return response; + + // Go through chapters in the file looking for badness + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // Not processing chapters - fileobject doesn't seem to have the chapters section + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //for (var i = 0; i < objFFProbeInfo.chapters.length; i++) { + + //Bad start times + // if (objFFProbeInfo.chapters[i].start_time < 0) { + // boldochapters = false; + // break; //Dont need to continue because we know they are bad + // } + + //Duplicate start times + // for (var x = 0; i < objFFProbeInfo.chapters.length; i++) { + // if (i != x && objFFProbeInfo.chapters[i].start_time == objFFProbeInfo.chapters[x].start_time) { + // boldochapters = false; + // break; //Dont need to continue because we know they are bad + // } + // } + //} + + //Video Decision section + ////////////////////////////////////////////////////////////////////////////////////////////////////// + if (videoIdx == -1) { + response.processFile = false; + response.infoLog += "No Video Track !! \n"; + return response; + } + + boltranscodeVideo = true; //We will assume we will be transcoding + var MILoc = findMediaInfoItem(file, videoIdx); + + var videoheight = file.ffProbeData.streams[videoIdx].height * 1; + var videowidth = file.ffProbeData.streams[videoIdx].width * 1; + var videoFPS = file.mediaInfo.track[MILoc].FrameRate * 1; + var videoBR = file.mediaInfo.track[MILoc].BitRate * 1; + + if (file.ffProbeData.streams[videoIdx].profile != undefined && file.ffProbeData.streams[videoIdx].profile.includes != undefined && file.ffProbeData.streams[videoIdx].profile.includes("10")) { + bolSource10bit = true; + } + + if (file.mediaInfo.track[MILoc].FrameRate_Mode == 'VFR') + videoFPS = 9999 //Source is Variable Frame rate but we will transcode to fixed + + if (videoFPS > targetframerate) { + bolchangeframerateVideo = true; //Need to fix this it does not work :-( + } + + //Lets see if we need to scal down the video size + if (videoheight > maxvideoheight) { + bolscaleVideo = true; + videonewwidth = Math.floor((maxvideoheight / videoheight) * videowidth); + response.infoLog += "Video Resolution, " + videowidth + "x" + videoheight + ", need to convert to " + videonewwidth + "x" + maxvideoheight + " \n"; + videoheight = maxvideoheight; + videowidth = videonewwidth; + } + + //Figure out the desired bitrate + optimalvideobitrate = Math.floor((videoheight * videowidth * targetframerate) * targetcodeccompression); + response.infoLog += "Pre Video Calc: " + videoheight + ", " + videowidth + ", " + videoFPS + ", " + optimalvideobitrate + " \n" + + //We need to check for a minimum bitrate + if ((videoheight * videowidth) > minvideopixels4K && optimalvideobitrate < minvideopixels4K) { + response.infoLog += "Video Bitrate calulcated for 4K, " + optimalvideobitrate + ", is below minimum, " + minvideopixels4K +" \n"; + optimalvideobitrate = minvideorate4K; + } else if ((videoheight * videowidth) > minvideopixels2K && optimalvideobitrate < minvideorate2K) { + response.infoLog += "Video Bitrate calulcated for 2K, " + optimalvideobitrate + ", is below minimum, " + minvideorate2K + " \n"; + optimalvideobitrate = minvideorate2K; + } else if ((videoheight * videowidth) > minvideopixelsHD && optimalvideobitrate < minvideorateHD) { + response.infoLog += "Video Bitrate calulcated for HD, " + optimalvideobitrate + ", is below minimum, " + minvideorateHD + " \n"; + optimalvideobitrate = minvideorateHD; + } else if (optimalvideobitrate < minvideorateSD) { + response.infoLog += "Video Bitrate calulcated for SD, " + optimalvideobitrate + ", is below minimum, " + minvideorateSD +" \n"; + optimalvideobitrate = minvideorateSD; + } + + //Check if it is already hvec, if not then we must transcode + if (file.ffProbeData.streams[videoIdx].codec_name != targetvideocodec) { + response.infoLog += "Video existing Codex is " + file.ffProbeData.streams[videoIdx].codec_name + ((bolSource10bit) ? "(10)" : ""); + response.infoLog += ", need to convert to " + targetvideocodec + ((boluse10bit) ? "(10)" : "") + " \n"; + + if (file.ffProbeData.streams[videoIdx].codec_name == "mpeg4") { + boltranscodeSoftwareDecode = true; + response.infoLog += "Video existing Codex is " + file.ffProbeData.streams[videoIdx].codec_name + ", need to decode with software codec \n"; + } else if (file.ffProbeData.streams[videoIdx].codec_name == "h264" && file.ffProbeData.streams[videoIdx].profile.includes("10")) { + //If the source is 10 bit then we must software decode since qsv will not decode 264 10 bit?? + boltranscodeSoftwareDecode = true; + response.infoLog += "Video existing Codex is " + file.ffProbeData.streams[videoIdx].codec_name + " 10 bit, need to decode with software codec \n"; + } + } + + if (videoBR < (optimalvideobitrate * minsizedifffortranscode)) { + //We need to be careful here are else we could produce a bad quality + response.infoLog += "Low source bitrate! \n"; + if (file.ffProbeData.streams[videoIdx].codec_name == targetvideocodec) { + if (bolSource10bit == boluse10bit) { + response.infoLog += "Video existing Bitrate, " + videoBR + ", is close to target Bitrate, " + optimalvideobitrate + ", using existing stream \n"; + boltranscodeVideo = false; + } else { + response.infoLog += "Video existing bit depth is different from target, without a codec change, using using existing bitrate \n"; + optimalvideobitrate = videoBR; + } + } else { + //We have a codec change with not much meat so we need to adjust are target rate + response.infoLog += "Video existing Bitrate, " + videoBR + ", is close to, or lower than, target Bitrate, "; + response.infoLog += optimalvideobitrate + ", with a codec change, using " + Math.floor(targetreductionforcodecswitchonly * 100) + "% of existing \n"; + optimalvideobitrate = Math.floor(videoBR * targetreductionforcodecswitchonly); + boltranscodeVideo = true; + } + } else { + //We already know the existing bitrate has enough meat for a decent transcode + //boltranscodeVideo = true; + response.infoLog += "Video existing Bitrate, " + videoBR + ", is higher than target, " + optimalvideobitrate + ", transcoding \n"; + } + response.infoLog += "Post Video Calc: " + videoheight + ", " + videowidth + ", " + videoFPS + ", " + optimalvideobitrate + " \n" + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + //Audio Decision section + ////////////////////////////////////////////////////////////////////////////////////////////////////// + if (audioIdx == -1) { + if (audioIdxOther != -1) { + response.infoLog += "Using Unknown Audio Track !! \n"; + audioIdx = audioIdxOther; + } else { + response.processFile = false; + response.infoLog += "No Audio Track !! \n"; + return response; + } + } + + var audioBR = file.mediaInfo.track[findMediaInfoItem(file, audioIdx)].BitRate * 1; + + if (file.ffProbeData.streams[audioIdx].channels > targetaudiochannels) { + boldownmixAudio = true; + audionewchannels = targetaudiochannels; + response.infoLog += "Audio existing Channels, " + file.ffProbeData.streams[audioIdx].channels + ", is higher than target, " + targetaudiochannels + " \n"; + } else { + audionewchannels = file.ffProbeData.streams[audioIdx].channels; + } + + var optimalaudiobitrate = audionewchannels * targetaudiobitrateperchannel; + + //Now what are we going todo with the audio part + if (audioBR > (optimalaudiobitrate * 1.1)) { + boltranscodeAudio = true; + response.infoLog += "Audio existing Bitrate, " + audioBR + ", is higher than target, " + optimalaudiobitrate + " \n"; + } + + //If the audio codec is not what we want then we should transcode + if (file.ffProbeData.streams[audioIdx].codec_name != targetaudiocodec) { + boltranscodeAudio = true; + response.infoLog += "Audio Codec, " + file.ffProbeData.streams[audioIdx].codec_name + ", is different than target, " + targetaudiocodec + ", Changing \n"; + } + + //If the source bitrate is less than out target bitrate we should not ever go up + if (audioBR < optimalaudiobitrate) { + response.infoLog += "Audio existing Bitrate, " + audioBR + ", is lower than target, " + optimalaudiobitrate + ", using existing "; + optimalaudiobitrate = audioBR; + if (file.ffProbeData.streams[audioIdx].codec_name != targetaudiocodec) { + response.infoLog += "rate"; + }else{ + response.infoLog += "stream"; + } + response.infoLog += " \n"; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + + // lets assemble our ffmpeg command + ////////////////////////////////////////////////////////////////////////////////////////////////////// + var strtrancodebasehw = " -hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi "; + var strtrancodebasesw = " -vaapi_device /dev/dri/renderD128 "; + var strtranscodevideomapping = " -max_muxing_queue_size 8000 -map 0:{0} "; + var strtranscodevideocopy = " -c:v:0 copy "; + var strtranscodevideotranscoding = " -c:v:0 hevc_vaapi "; + var strtranscodevideooptions = ' -vf "{0}" '; //Used to make the output 10bit, I think the quotes need to be this way for ffmpeg + var strtranscodevideoscaling = "w=-1:h=1080"; //Used when video is above our target of 1080 + var strtranscodeframerate = "fps={0}"; //Used to change the framerate to the target framerate + var strtranscodevideoformathw = "scale_vaapi="; //Used to make the output 10bit + var strtranscodevideoformat = "format={0}"; //Used to add filters to the hardware transcode + var strtranscodevideo10bit = "p010"; //Used to make the output 10bit + var strtranscodevideo8bit = "p008"; //Used to make the output 8bit + var strtranscodevideoswdecode = "hwupload"; //Used to make it use software decode if necessary + var strtranscodevideoswdecode10bit = "nv12|vaapi"; //Used to make it sure the software decode is in the proper pixel format + var strtranscodevideobitrate = " -b:v {0} "; //Used when video is above our target of 1080 + var strtranscodeaudiomapping = " -map 0:{0} "; + var strtranscodeaudiocopy = " -c:a:0 copy "; + var strtranscodeaudiotranscoding = " -c:a:0 ${targetaudiocodec} -b:a {0} "; + var strtranscodeaudiodownmixing = " -ac {0} "; + var strtranscodesubs = " -map 0:s -scodec copy "; + var strtranscodesubsconvert = " -map 0:s -c:s srt "; + var strtranscodesubsnone = " -map -0:s "; + var strtranscodemetadata = " -map_metadata:g -1 -metadata JBDONEVERSION=1 -metadata JBDONEDATE={0} "; + var strtranscodechapters = " -map_chapters {0} "; + + var strtranscodefileoptions = " "; + + var strFFcmd = ""; + if (boltranscodeVideo) { + if (boltranscodeSoftwareDecode) { + strFFcmd += strtrancodebasesw; + } else { + strFFcmd += strtrancodebasehw; + } + } + strFFcmd += strtranscodevideomapping.replace("{0}",videoIdx); + if (boltranscodeVideo) { + strFFcmd += strtranscodevideotranscoding; + + if (bolscaleVideo || boluse10bit || boltranscodeSoftwareDecode || bolchangeframerateVideo) { + var stroptions = ""; + var strformat = ""; + if (bolscaleVideo) { + stroptions += strtranscodevideoscaling; + } + + var strChangeVideoRateString = ""; + if (bolchangeframerateVideo) { + strChangeVideoRateString = strtranscodeframerate.replace("{0}",targetframerate) + ","; + } + + if (strformat.length > 0) { + strformat += "="; + } + + if (boluse10bit && !bolSource10bit) { + strformat += strtranscodevideo10bit; + } + + if (!boluse10bit && bolSource10bit) { + strformat += strtranscodevideo8bit; + } + + + if (boltranscodeSoftwareDecode) { + if (bolSource10bit) { + if (strformat.length > 0) { + strformat += ","; + } + strformat += strtranscodevideoswdecode10bit; + } + if (strformat.length > 0) { + strformat += ","; + } + strformat += strtranscodevideoswdecode; + } + + if (strformat.length > 0) { + if (stroptions.length > 0) { + stroptions += ","; + } + stroptions += strtranscodevideoformat.replace("{0}",strformat); + } + + if (boltranscodeSoftwareDecode) { + strFFcmd += strtranscodevideooptions.replace("{0}",strChangeVideoRateString + stroptions); + } else { + strFFcmd += strtranscodevideooptions.replace("{0}",strChangeVideoRateString + strtranscodevideoformathw + stroptions); + } + } + strFFcmd += strtranscodevideobitrate.replace("{0}",optimalvideobitrate); + + } else { + strFFcmd += strtranscodevideocopy; + } + + strFFcmd += strtranscodeaudiomapping.replace("{0}",audioIdx); + if (boltranscodeAudio) { + strFFcmd += strtranscodeaudiotranscoding.replace("{0}",optimalaudiobitrate).replace("${targetaudiocodec}",targetaudiocodec); + } else { + strFFcmd += strtranscodeaudiocopy; + } + if (boldownmixAudio) { + strFFcmd += strtranscodeaudiodownmixing.replace("{0}",audionewchannels); + } + if (bolforcenosubs) { + strFFcmd += strtranscodesubsnone; + } else if (boldosubs) { + if (boldosubsconvert) { + strFFcmd += strtranscodesubsconvert; + } else { + strFFcmd += strtranscodesubs; + } + } + + strFFcmd += strtranscodemetadata.replace("{0}",new Date().toISOString()); + if (boldochapters) { + strFFcmd += strtranscodechapters.replace("{0}","0"); + } else { + strFFcmd += strtranscodechapters.replace("{0}","-1"); + } + + strFFcmd += strtranscodefileoptions; + ////////////////////////////////////////////////////////////////////////////////////////////////////// + + //response.infoLog += strFFcmd + "\n"; + + response.preset += strFFcmd; + response.processFile = true; + response.infoLog += "File needs work. Transcoding. \n"; + return response; +} + +function findMediaInfoItem(file, index) { + var currMIOrder = 0; + + for (var i = 0; i < file.mediaInfo.track.length; i++) { + if (file.mediaInfo.track[i].StreamOrder != null || file.mediaInfo.track[i].StreamOrder != undefined) { + currMIOrder = file.mediaInfo.track[i].StreamOrder; + } else { + currMIOrder = file.mediaInfo.track[i].ID - 1; + } + + if (currMIOrder == index|| currMIOrder == "0-" + index) { + return i; + } + } + return -1; +} + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js b/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js index b8c9ae3..3f59236 100644 --- a/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js +++ b/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js @@ -1,7 +1,7 @@ ////////////////////////////////////////////////////////////////////////////////////////////////////// // // Author: JarBinks, Zachg99, Jeff47 -// Date: 06/29/2020 +// Date: 04/11/2021 // // This is my attempt to create an all in one routine that will maintain my library in optimal format !!!!FOR MY REQUIREMENTS!!!! // Chances are very good you will need to make some changes to this routine and it's partner in order to make it work for you @@ -84,44 +84,66 @@ // Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile (JB - H265, AAC, MKV, bitrate optimized) // Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix (JB - MKV Stats, Chapters, Audio Language) // -// I am running the docker image provided for Tdarr, however there are some additions that must be added in order for the script to run -// This is to add mediainfo and mkvtoolnix because these are used to get more media info and update the file without running a transcode +// I am running the docker image provided for Tdarr // // Here is my docker config (I am running compose so yours might be a little different) -// Tdarr: -// image: haveagitgat/tdarr_aio:qsv -// container_name: tdarr +// tdarr_server: +// container_name: tdarr_server +// image: haveagitgat/tdarr:latest +// privileged: true // restart: unless-stopped -// network_mode: host -// ports: -// - "8265:8265" -// environment: -// - PUID=${PUID} # default user id, defined in .env -// - PGID=${PGID} # default group id, defined in .env -// - TZ=${TZ} # timezone, defined in .env -// devices: -// - /dev/dri:/dev/dri -// volumes: -// - "${ROOT}/complete:/home/Tdarr/Media:rw" -// - /transtemp:/transtemp -// - "${ROOT}/config/Tdarr:/home/Tdarr/Documents/Tdarr:rw" -// - "/etc/localtime:/etc/localtime:ro" +// environment: +// - PUID=${PUID} # default user id, defined in .env +// - PGID=${PGID} # default group id, defined in .env +// - TZ=${TZ} # timezone, defined in .env +// - serverIP=tdarr_server #using internal docker networking. This should at least work when the nodes are on the same docker compose as the server +// - serverPort=8266 +// - webUIPort=8265 +// volumes: +// - ${ROOT}/tdarr/server:/app/server/Tdarr # Tdarr server files +// - ${ROOT}/tdarr/configs:/app/configs # config files - can be same as NODE (unless separate server) +// - ${ROOT}/tdarr/logs:/app/logs # Tdarr log files +// - ${ROOT}/tdarr/cache:/temp # Cache folder, Should be same path mapped on NODE +// - ${ROOT}/tdarr/testmedia:/home/Tdarr/testmedia # Should be same path mapped on NODE if using a test folder +// - ${ROOT}/tdarr/scripts:/home/Tdarr/scripts # my random way of saving script files +// - /volume1/video:/media # video library Should be same path mapped on NODE +// ports: +// - 8265:8265 #Exposed to access webui externally +// - 8266:8266 #Exposed to allow external nodes to reach the server +// logging: +// options: +// max-size: "2m" +// max-file: "3" // -// I then connect to the docker container by using the following command -// sudo docker exec -it tdarr /bin/bash -// -// Here is the script that I run after the docker container is up and running (This requires a couple of (y)es'es to complete) -// -// //It is important to get mediainfo from a custom repository because it is a newer version that includes JSON output -// sudo apt-get install wget -// sudo wget https://mediaarea.net/repo/deb/repo-mediaarea_1.0-12_all.deb && sudo dpkg -i repo-mediaarea_1.0-12_all.deb && sudo apt-get update -// sudo apt-get install mediainfo -// -// sudo wget -q -O - https://mkvtoolnix.download/gpg-pub-moritzbunkus.txt | sudo apt-key add - -// sudo sh -c 'echo "deb https://mkvtoolnix.download/ubuntu/ bionic main" >> /etc/apt/sources.list.d/bunkus.org.list' -// sudo sh -c 'echo "deb-src https://mkvtoolnix.download/ubuntu/ bionic main" >> /etc/apt/sources.list.d/bunkus.org.list' -// sudo apt update -// sudo apt install mkvtoolnix +// tdarr_node: +// container_name: tdarr_node +// image: haveagitgat/tdarr_node:latest +// privileged: true +// restart: unless-stopped +// devices: +// - /dev/dri:/dev/dri +// environment: +// - PUID=${PUID} # default user id, defined in .env +// - PGID=${PGID} # default group id, defined in .env +// - TZ=${TZ} # timezone, defined in .env +// - serverIP=192.168.x.x #container name of the server, should be modified if server is on another machine +// - serverPort=8266 +// - nodeID=TDARRNODE_2 +// - nodeIP=192.168.x.x #container name of the node +// - nodePort=9267 #not exposed via a "ports: " setting as the server/node communication is done on the internal docker network and can communicate on all ports +// volumes: +// - ${ROOT}/tdarr/configs:/app/configs # config files - can be same as server (unless separate server) +// - ${ROOT}/tdarr/logs:/app/logs # config files - can be same as server (unless separate server) +// - ${ROOT}/tdarr/testmedia:/home/Tdarr/testmedia # Should be same path mapped on server if using a test folder +// - ${ROOT}/tdarr/scripts:/home/Tdarr/scripts # my random way of saving script files +// - ${ROOT}/tdarr/cache:/temp # Cache folder, Should be same path mapped on server +// - /mnt/video:/media # video library Should be same path mapped on server +// ports: +// - 9267:9267 +// logging: +// options: +// max-size: "2m" +// max-file: "3" // ////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -133,7 +155,7 @@ function details() { Type: "Video", Operation: "Transcode", Description: "***You should not use this*** until you read the comments at the top of the code and understand how it works **this does alot** and is 2 of 2 routines you should to run **Part 2** \n", - Version: "1.1", + Version: "2.0", Link: "https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js", Tags: "post-processing,ffmpeg,video" } @@ -165,20 +187,20 @@ function plugin(file, librarySettings, inputs) { } ////////////////////////////////////////////////////////////////////////////////////////////////////// - response.infoLog += "Getting Media Info.\n"; - var objMedInfo = ""; - objMedInfo = JSON.parse(require("child_process").execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); + //response.infoLog += "Getting Media Info.\n"; + //var objMedInfo = ""; + //objMedInfo = JSON.parse(require("child_process").execSync('mediainfo "' + currentfilename + '" --output=JSON').toString()); ////////////////////////////////////////////////////////////////////////////////////////////////////// - if (objMedInfo.media.track[0].extra == undefined || objMedInfo.media.track[0].extra.JBDONEVERSION == undefined || objMedInfo.media.track[0].extra.JBDONEVERSION != "1") { + if (file.mediaInfo.track[0].extra == undefined || file.mediaInfo.track[0].extra.JBDONEVERSION == undefined || file.mediaInfo.track[0].extra.JBDONEVERSION != "1") { response.infoLog += "File not processed by first routine! \n"; return response; } //Run ffprobe with full info and load the results it into an object ////////////////////////////////////////////////////////////////////////////////////////////////////// - response.infoLog += "Getting FFProbe Info.\n"; - var objFFProbeInfo = ""; - objFFProbeInfo = JSON.parse(require("child_process").execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); + //response.infoLog += "Getting FFProbe Info.\n"; + //var objFFProbeInfo = ""; + //objFFProbeInfo = JSON.parse(require("child_process").execSync('ffprobe -v error -print_format json -show_format -show_streams -show_chapters "' + currentfilename + '"').toString()); ////////////////////////////////////////////////////////////////////////////////////////////////////// var datStats = Date.parse(new Date(70, 1).toISOString()) @@ -186,11 +208,12 @@ function plugin(file, librarySettings, inputs) { datStats = Date.parse(file.ffProbeData.streams[0].tags["_STATISTICS_WRITING_DATE_UTC-eng"] + " GMT") } - if (objFFProbeInfo.chapters.length != 0) { - bolHasChapters = true - } else { - response.infoLog += "No Chapters! \n" - } + //Not processing chapters for now + //if (objFFProbeInfo.chapters.length != 0) { + // bolHasChapters = true + //} else { + // response.infoLog += "No Chapters! \n" + //} if (file.ffProbeData.streams[1].tags != undefined && file.ffProbeData.streams[1].tags.language != undefined && file.ffProbeData.streams[1].tags.language == "eng") { bolAudioIsEng = true; @@ -198,8 +221,8 @@ function plugin(file, librarySettings, inputs) { response.infoLog += "Audio not marked as English! \n"; } - if (objMedInfo.media.track[0].extra.JBDONEDATE != undefined) { - var JBDate = Date.parse(objMedInfo.media.track[0].extra.JBDONEDATE); + if (file.mediaInfo.track[0].extra.JBDONEDATE != undefined) { + var JBDate = Date.parse(file.mediaInfo.track[0].extra.JBDONEDATE); response.infoLog += "JBDate:" + JBDate + ", StatsDate:" + datStats + "\n"; if (datStats >= JBDate) { @@ -239,7 +262,7 @@ function plugin(file, librarySettings, inputs) { var intChapNum = 0; var strChapNum = ""; - for (var i = 0; i < objFFProbeInfo.format.duration; i += chapterlengthlong) { + for (var i = 0; i < file.meta.Duration; i += chapterlengthlong) { intChapNum += 1; strChapNum = String(intChapNum).padStart(2, '0'); @@ -253,7 +276,7 @@ function plugin(file, librarySettings, inputs) { intChapNum += 1; strChapNum = String(intChapNum).padStart(2, "0"); - var timeString = new Date((Math.floor(objFFProbeInfo.format.duration) - 1) * 1000).toISOString().substr(11, 12); + var timeString = new Date((Math.floor(file.meta.Duration) - 1) * 1000).toISOString().substr(11, 12); strChapterFile += "CHAPTER" + strChapNum + "=" + timeString + "\n"; strChapterFile += "CHAPTER" + strChapNum + "NAME=CHAPTER " + intChapNum + "\n"; From e3c7eed56d05791085c4827fe7b2c4e3335822cb Mon Sep 17 00:00:00 2001 From: vodkapmp <70449815+vodkapmp@users.noreply.github.com> Date: Fri, 1 Oct 2021 16:55:22 +0200 Subject: [PATCH 02/28] Update Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js Add a way to skip processing SD and UHD content. --- ..._vdka_Tiered_CPU_CRF_Based_Configurable.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js b/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js index 9334699..4ff1965 100644 --- a/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js +++ b/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js @@ -72,6 +72,20 @@ function details() { \\nExample:\\n veryfast`, }, + { + name: 'sdDisabled', + tooltip: `Input "true" if you want to skip SD (480p and 576p) files + + \\nExample:\\n + true`, + }, + { + name: 'uhdDisabled', + tooltip: `Input "true" if you want to skip 4k (UHD) files + + \\nExample:\\n + true`, + }, ], }; } @@ -97,6 +111,22 @@ function plugin(file, librarySettings, inputs) { } response.infoLog += '☑File is a video! \n'; + // check if the file is SD and sdDisable is enabled + // skip this plugin if so + if (['480p', '576p'].includes(file.video_resolution) && inputs.sdDisabled) { + response.processFile = false; + response.infoLog += '☒File is SD, not processing\n'; + return response; + } + + // check if the file is 4k and 4kDisable is enabled + // skip this plugin if so + if (file.video_resolution === '4KUHD' && inputs.uhdDisabled) { + response.processFile = false; + response.infoLog += '☒File is SD, not processing\n'; + return response; + } + // check if the file is already hevc // it will not be transcoded if true and the plugin will be stopped immediately for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { From 99c57cbcc3212403dbb08e93bf075d6b4309213c Mon Sep 17 00:00:00 2001 From: vodkapmp <70449815+vodkapmp@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:12:03 +0200 Subject: [PATCH 03/28] Update Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js I might have done a copy paste and not changed something..... --- .../Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js b/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js index 4ff1965..7647830 100644 --- a/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js +++ b/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js @@ -123,7 +123,7 @@ function plugin(file, librarySettings, inputs) { // skip this plugin if so if (file.video_resolution === '4KUHD' && inputs.uhdDisabled) { response.processFile = false; - response.infoLog += '☒File is SD, not processing\n'; + response.infoLog += '☒File is 4k/UHD, not processing\n'; return response; } From 8db22ecb41c35a9c1fa930d889b8f8f9a10f7fe8 Mon Sep 17 00:00:00 2001 From: Fabian Geiger Date: Mon, 4 Oct 2021 14:57:42 +0200 Subject: [PATCH 04/28] Fix typo --- Tdarr_Plugin_aaaa_Pre_Proc_Example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tdarr_Plugin_aaaa_Pre_Proc_Example.js b/Tdarr_Plugin_aaaa_Pre_Proc_Example.js index 713db5e..f52129e 100644 --- a/Tdarr_Plugin_aaaa_Pre_Proc_Example.js +++ b/Tdarr_Plugin_aaaa_Pre_Proc_Example.js @@ -6,7 +6,7 @@ module.exports.dependencies = [ module.exports.details = function details() { return { id: 'Tdarr_Plugin_aaaa_Pre_Proc_Example', - Stage: 'Pre-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed. + Stage: 'Pre-processing', // Pre-processing or Post-processing. Determines when the plugin will be executed. Name: 'No title meta data ', Type: 'Video', Operation: 'Transcode', From ba7eb90a0be2c00c8466755bc7a7398fbe2e9762 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:19:33 +0100 Subject: [PATCH 05/28] remove fs-extra --- ...r_Plugin_z18s_rename_files_based_on_codec.js | 15 +-------------- ...ename_files_based_on_codec_and_resolution.js | 17 +---------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js index 4c195f9..3a7a036 100644 --- a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js +++ b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js @@ -1,9 +1,5 @@ /* eslint-disable */ -module.exports.dependencies = [ - 'fs-extra', -]; - module.exports.details = function details() { return { id: "Tdarr_Plugin_z18s_rename_files_based_on_codec", @@ -21,15 +17,6 @@ module.exports.details = function details() { module.exports.plugin = function plugin(file, librarySettings, inputs) { try { var fs = require("fs"); - var path = require("path"); - if (fs.existsSync(path.join(process.cwd(), "/npm"))) { - var rootModules = path.join(process.cwd(), "/npm/node_modules/"); - } else { - var rootModules = ""; - } - - var fsextra = require(rootModules + "fs-extra"); - var fileNameOld = file._id; if ( @@ -57,7 +44,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) { } if (fileNameOld != file._id) { - fsextra.moveSync(fileNameOld, file._id, { + fs.rename(fileNameOld, file._id, { overwrite: true, }); diff --git a/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js b/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js index f66635f..1d91d73 100644 --- a/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js +++ b/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js @@ -1,7 +1,3 @@ -module.exports.dependencies = [ - 'fs-extra', -]; - module.exports.details = function details() { return { id: 'Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution', @@ -20,17 +16,6 @@ module.exports.plugin = function plugin(file) { try { // eslint-disable-next-line global-require const fs = require('fs'); - // eslint-disable-next-line global-require - const path = require('path'); - let rootModules; - if (fs.existsSync(path.join(process.cwd(), '/npm'))) { - rootModules = path.join(process.cwd(), '/npm/node_modules/'); - } else { - rootModules = ''; - } - - // eslint-disable-next-line global-require,import/no-dynamic-require - const fsextra = require(`${rootModules}fs-extra`); const fileNameOld = file._id; const resolutions = { @@ -133,7 +118,7 @@ module.exports.plugin = function plugin(file) { file.file = fileName; if (fileNameOld !== file._id) { - fsextra.moveSync(fileNameOld, file._id, { + fs.rename(fileNameOld, file._id, { overwrite: true, }); From 2951fad0238b70efa7c704fdd3554d4108e015e6 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:24:02 +0100 Subject: [PATCH 06/28] renameSync --- Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js | 2 +- ...rr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js index 3a7a036..6f0ff10 100644 --- a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js +++ b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js @@ -44,7 +44,7 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) { } if (fileNameOld != file._id) { - fs.rename(fileNameOld, file._id, { + fs.renameSync(fileNameOld, file._id, { overwrite: true, }); diff --git a/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js b/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js index 1d91d73..6a729bd 100644 --- a/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js +++ b/Community/Tdarr_Plugin_z18t_rename_files_based_on_codec_and_resolution.js @@ -118,7 +118,7 @@ module.exports.plugin = function plugin(file) { file.file = fileName; if (fileNameOld !== file._id) { - fs.rename(fileNameOld, file._id, { + fs.renameSync(fileNameOld, file._id, { overwrite: true, }); From 3b63f8a5b6104de8bf79f2e5966f01343930ed0f Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:40:29 +0100 Subject: [PATCH 07/28] lint --- ...de audio and video with HW (PC and Mac).js | 384 +++++++++--------- 1 file changed, 195 insertions(+), 189 deletions(-) diff --git a/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js b/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js index 2391e19..7ef51e2 100644 --- a/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js +++ b/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js @@ -1,4 +1,10 @@ /* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +/* eslint max-len: 0 */ +/* eslint no-bitwise: 0 */ +/* eslint no-mixed-operators: 0 */ + +const os = require('os'); + function details() { return { id: 'Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac)', @@ -7,10 +13,10 @@ function details() { 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. + 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.`, + 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: [{ @@ -32,7 +38,7 @@ function details() { \\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. @@ -64,7 +70,7 @@ function plugin(file, librarySettings, inputs) { 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'; @@ -79,23 +85,20 @@ function plugin(file, librarySettings, inputs) { return response; } - const os = require('os'); - - -// VIDEO SECTION + // VIDEO SECTION -let bitRateMultiplier = 1.00; -let videoIdx = -1; -let willBeResized = false; -let videoOptions = `-map 0:v -c:v copy `; + 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 + // 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++) { @@ -103,44 +106,50 @@ let videoOptions = `-map 0:v -c:v copy `; if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'video') { // Check if codec of stream is mjpeg/png. If so then remove this "video" stream. // mjpeg/png are usually embedded pictures that can cause havoc with plugins. - if (file.ffProbeData.streams[i].codec_name === 'mjpeg' || file.ffProbeData.streams[i].codec_name === 'png' ) { + if (file.ffProbeData.streams[i].codec_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 = 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' ) { + 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' ) { + 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' ) { + 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 ) { + 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; } - + bitRateMultiplier = 0.5; } - // Increment videoIdx. - videoIdx += 1; } + // Increment videoIdx. + videoIdx += 1; + } -// figure out final bitrate + // 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') { @@ -168,21 +177,21 @@ let videoOptions = `-map 0:v -c:v copy `; response.infoLog += 'Target bitrate could not be calculated. Skipping this plugin. \n'; return response; } - - // Check if inputs.bitrate cutoff has something entered. + + // 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; } + convertVideo = false; + } } + // AUDIO SECTION -// AUDIO SECTION - -// Set up required variables. - let audioOptions = `-map 0:a -c:a copy `; + // Set up required variables. + let audioOptions = '-map 0:a -c:a copy '; let audioIdx = 0; let numberofAudioChannels = 0; let has2Channels = false; @@ -196,197 +205,194 @@ let videoOptions = `-map 0:v -c:v copy `; let type8Channels = ''; let keepAudioIdx = -1; - let keepIGuessAudioIdx = -1; + // const keepIGuessAudioIdx = -1; let encodeAudioIdx = -1; let keepAudioStream = -1; let encodeAudioStream = -1; let originalAudio = ''; - + // Go through each stream in the file. for (let i = 0; i < file.ffProbeData.streams.length; i++) { try { // Go through all audio streams and check if 2,6 & 8 channel tracks exist or not. if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { numberofAudioChannels += 1; - if (file.ffProbeData.streams[i].channels === 2 && has2Channels === false) { - has2Channels = true; - lang2Channels = file.ffProbeData.streams[i].tags.language.toLowerCase(); - type2Channels = file.ffProbeData.streams[i].codec_name.toLowerCase(); + if (file.ffProbeData.streams[i].channels === 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(); + 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(); + 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 + } + + // Are we processing for 6 channels? + if (inputs.audio_channels === 6) { + audioIdx = -1; + for (let i = 0; i < file.ffProbeData.streams.length; i++) { + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + audioIdx += 1; + if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') { + if (file.ffProbeData.streams[i].channels === 6) { + if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') { + // response.infoLog += `Found 6 channel audio in proper language and codec, audio stream ${audioIdx}\n`; + if (keepAudioIdx === -1) { + keepAudioIdx = audioIdx; + keepAudioStream = i; + } + } else if (encodeAudioIdx === -1) { + // response.infoLog += `Found 6 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`; + encodeAudioIdx = audioIdx; + encodeAudioStream = i; + } + } + if (file.ffProbeData.streams[i].channels > 6) { + // response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`; + if (encodeAudioIdx === -1) { + encodeAudioIdx = audioIdx; + encodeAudioStream = i; + } + } + } + } + } catch (err) { + // Error + } } - } - if (keepAudioIdx === -1 && encodeAudioIdx === -1) { // didn't find any 5.1 or better audio streams in proper language, defaulting to using 2 channels - 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 + if (keepAudioIdx === -1 && encodeAudioIdx === -1) { // didn't find any 5.1 or better audio streams in proper language, defaulting to using 2 channels + // eslint-disable-next-line no-param-reassign + inputs.audio_channels = '2'; } - } - } + } + // Are we processing for 2 channels? + if (inputs.audio_channels === 2) { + audioIdx = -1; + for (let i = 0; i < file.ffProbeData.streams.length; i++) { + try { + if (file.ffProbeData.streams[i].codec_type.toLowerCase() === 'audio') { + audioIdx += 1; + if (file.ffProbeData.streams[i].tags.language.toLowerCase() === 'eng' || file.ffProbeData.streams[i].tags.language.toLowerCase() === 'und') { + if (file.ffProbeData.streams[i].channels === 2) { + if (file.ffProbeData.streams[i].codec_name.toLowerCase() === 'aac' || file.ffProbeData.streams[i].codec_name.toLowerCase() === 'ac3') { + // response.infoLog += `Found 2 channel audio in proper language and codec, audio stream ${audioIdx}\n`; + if (keepAudioIdx === -1) { + keepAudioIdx = audioIdx; + keepAudioStream = i; + } + } else if (encodeAudioIdx === -1) { + // response.infoLog += `Found 2 channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`; + encodeAudioIdx = audioIdx; + encodeAudioStream = i; + } + } else if (encodeAudioIdx === -1) { + // response.infoLog += `Found existing multi-channel audio in proper language, need to re-encode, audio stream ${audioIdx}\n`; + encodeAudioIdx = audioIdx; + encodeAudioStream = i; + } + } + // response.infoLog += `a ${audioIdx}. k ${keepAudioIdx}. e ${encodeAudioIdx}\n `; + } + } catch (err) { + // Error + } + } + } let audioMessage = ''; -// selecting channels to keep, only if 2 or 6 channels processed + // 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`;} + // 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 { - 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.`; - } + // 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 + // 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; } + response.infoLog += 'File is processed already, nothing to do'; + response.processFile = false; + return response; + } -// Generate ffmpeg command line arguments in total + // Generate ffmpeg command line arguments in total // few defaults - response.preset = `, -sn `; + 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`; + // 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 (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() === '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 `; - } + 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';} - + outputResolution = '720p'; + } + if (convertVideo === false) { - response.infoLog += `NOT converting video ${file.video_resolution}, ${file.video_codec_name}, bitrate = ${currentBitrate} \n`; + 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`; + 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`; + response.infoLog += `Converting audio, ${audioMessage} ${originalAudio}. \n`; } else { - response.infoLog += `Not converting audio. \n`;} + 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; } From cb5e3de8955ff081a3f8427d322c4aa1fdb8b68b Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:49:21 +0100 Subject: [PATCH 08/28] Filter resolutions --- examples/Tdarr_Plugin_bbbc_Filter_Example.js | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/Tdarr_Plugin_bbbc_Filter_Example.js diff --git a/examples/Tdarr_Plugin_bbbc_Filter_Example.js b/examples/Tdarr_Plugin_bbbc_Filter_Example.js new file mode 100644 index 0000000..20a4128 --- /dev/null +++ b/examples/Tdarr_Plugin_bbbc_Filter_Example.js @@ -0,0 +1,35 @@ +module.exports.details = function details() { + return { + id: 'Tdarr_Plugin_bbbc_Filter_Example', + Stage: 'Pre-processing', + Name: 'Filter resolutions', + Type: 'Video', + Operation: 'Filter', + Description: 'This plugin prevents processing files with specified resolutions \n\n', + Version: '1.00', + Link: '', + Tags: '', + }; +}; + +module.exports.plugin = function plugin(file) { + const response = { + processFile: true, + infoLog: '', + }; + + const resolutionsToSkip = [ + "1080p", + '4KUHD' + ]; + + for (let i = 0; i < resolutionsToSkip.length; i += 1) { + if (file.video_resolution === resolutionsToSkip[i]) { + response.processFile = false; + response.infoLog += `Filter preventing processing. File has resolution ${resolutionsToSkip[i]}`; + break; + } + } + + return response; +}; From c8783e1dc6884649468792168f957eacb424eb4c Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:49:52 +0100 Subject: [PATCH 09/28] move folder --- examples/{ => filters}/Tdarr_Plugin_bbbc_Filter_Example.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{ => filters}/Tdarr_Plugin_bbbc_Filter_Example.js (100%) diff --git a/examples/Tdarr_Plugin_bbbc_Filter_Example.js b/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js similarity index 100% rename from examples/Tdarr_Plugin_bbbc_Filter_Example.js rename to examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js From 99cc5a10f605be92cba1a34ca5ec651efd365738 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:54:30 +0100 Subject: [PATCH 10/28] lint --- examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js b/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js index 20a4128..a40f945 100644 --- a/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js +++ b/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js @@ -19,8 +19,8 @@ module.exports.plugin = function plugin(file) { }; const resolutionsToSkip = [ - "1080p", - '4KUHD' + '1080p', + '4KUHD', ]; for (let i = 0; i < resolutionsToSkip.length; i += 1) { diff --git a/package.json b/package.json index 0cf53f2..677416f 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "lint": "eslint Community methods --ext js", - "lint:fix": "eslint Community methods --ext js --fix" + "lint": "eslint Community methods examples --ext js", + "lint:fix": "eslint Community methods examples --ext js --fix" }, "repository": { "type": "git", From f6f0be97a27b5ea3409b365c024cb284cfa54237 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Wed, 20 Oct 2021 02:02:24 +0100 Subject: [PATCH 11/28] Description update --- Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js index 6f0ff10..7495de0 100644 --- a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js +++ b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js @@ -7,7 +7,7 @@ module.exports.details = function details() { Name: "Rename based on codec", Type: "Video", Operation: "", - Description: `[Contains built-in filter]This plugin renames 264 files to 265 or vice versa depending on codec. \n\n`, + Description: `[Contains built-in filter] If the filename contains '264' or '265', this plugin renames 264 files to 265 or vice versa depending on codec. \n\n`, Version: "1.00", Link: "", Tags: "post-processing", From fae79010932cdb3cd364ff3a8c31a48b4b37940c Mon Sep 17 00:00:00 2001 From: azokeo Date: Tue, 26 Oct 2021 17:08:18 +0200 Subject: [PATCH 12/28] Say use v3 api key in tooltip. (#183) On TMDB api page there is a choice between v3 or v4 api key, specify wich one to use in tooltip, had to figure that one by trial and error. --- Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js index cb13d26..41da4c0 100644 --- a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js +++ b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js @@ -32,7 +32,7 @@ const details = () => ({ }, { name: 'api_key', - tooltip: 'Input your TMDB api key here. (https://www.themoviedb.org/)', + tooltip: 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)', }, { name: 'radarr_api_key', From 4777f2a676c49b5925858351393fa98911ac1e0d Mon Sep 17 00:00:00 2001 From: Zack MacLennan Date: Wed, 3 Nov 2021 19:27:11 -0400 Subject: [PATCH 13/28] Updated to include handling for AVC (#193) Added handling for AVC. Lots of my remuxes from Radarr had AVC in the filename as opposed to h264/x264. This replaces the AVC string with HEVC. --- .../Tdarr_Plugin_z18s_rename_files_based_on_codec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js index 7495de0..1d83aed 100644 --- a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js +++ b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js @@ -26,6 +26,15 @@ module.exports.plugin = function plugin(file, librarySettings, inputs) { file._id = file._id.replace("264", "265"); file.file = file.file.replace("264", "265"); } + + //added handling for files with AVC in the name instead of h264/x264 + if ( + file.ffProbeData.streams[0].codec_name == "hevc" && + file._id.includes("AVC") + ) { + file._id = file._id.replace("AVC", "HEVC"); + file.file = file.file.replace("AVC", "HEVC"); + } if ( file.ffProbeData.streams[0].codec_name == "h264" && From a2ff19b97ba009189212695841598b416c84d5c4 Mon Sep 17 00:00:00 2001 From: Liam Potter Date: Thu, 4 Nov 2021 16:55:05 +0000 Subject: [PATCH 14/28] Use user defined preset in ffmpeg command The preset is verified but never used, this PR uses the user defined preset when converting the video --- Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js b/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js index 2a1abb4..4c44fd6 100644 --- a/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js +++ b/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js @@ -123,7 +123,7 @@ function plugin(file, librarySettings, inputs) { response.preset = ", -map_metadata -1 -map 0:V " + subMap + - " -map 0:a -c:v libx264 -preset medium -c:a aac -strict -2 " + + " -map 0:a -c:v libx264 -preset " + preset + " -c:a aac -strict -2 " + subType; response.reQueueAfter = true; response.processFile = true; From b797c50b31b569c86e5518f4bf478e9fc81110b2 Mon Sep 17 00:00:00 2001 From: Rick Meijer Date: Sun, 7 Nov 2021 16:47:17 +0100 Subject: [PATCH 15/28] Improve regex to actually match IMDB ids (#196) --- Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js index 41da4c0..9dfac6f 100644 --- a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js +++ b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js @@ -10,7 +10,7 @@ const details = () => ({ (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 '[imdb-ttDIGITS] in the filename.`, + falls back to the IMDB id in the filename.`, Version: '1.00', Link: 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/' + 'Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js', @@ -139,7 +139,7 @@ const tmdbApi = async (filename, api_key, axios) => { if (filename.substr(0, 2) === 'tt') { fileName = filename; } else { - const idRegex = /\[imdb-(tt\d*)]/; + const idRegex = /tt\d*/; const fileMatch = filename.match(idRegex); // eslint-disable-next-line prefer-destructuring if (fileMatch) fileName = fileMatch[1]; From 1adf2a2a9118f4b9d26dcc0d53036aa9e3bfcb2f Mon Sep 17 00:00:00 2001 From: Rick Meijer Date: Sun, 7 Nov 2021 17:10:10 +0100 Subject: [PATCH 16/28] I did a brain fart. Update native lang again. (#197) Forgot to add the capture group back in :) --- Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js index 9dfac6f..3926797 100644 --- a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js +++ b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js @@ -139,7 +139,7 @@ const tmdbApi = async (filename, api_key, axios) => { if (filename.substr(0, 2) === 'tt') { fileName = filename; } else { - const idRegex = /tt\d*/; + const idRegex = /(tt\d{7,8})/; const fileMatch = filename.match(idRegex); // eslint-disable-next-line prefer-destructuring if (fileMatch) fileName = fileMatch[1]; From 25b67c2cfac35d10ce770c9f26ca94dd2726131a Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Sat, 13 Nov 2021 15:10:21 -0300 Subject: [PATCH 17/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- ...darr_Plugin_x7ac_Remove_Closed_Captions.js | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 730b884..ec84c9b 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -1,4 +1,3 @@ -/* eslint-disable */ function details() { return { id: "Tdarr_Plugin_x7ac_Remove_Closed_Captions", @@ -16,46 +15,39 @@ function details() { } function plugin(file) { - //Must return this object - - var response = { + const response = { processFile: false, - preset: "", - container: ".mp4", + preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"', + container: "." + file.container, handBrakeMode: false, - FFmpegMode: false, + FFmpegMode: true, reQueueAfter: true, infoLog: "", }; if (file.fileMedium !== "video") { - console.log("File is not video"); - response.infoLog += "☒File is not video \n"; - response.processFile = false; - return response; - } else { - if (file.hasClosedCaptions === true) { - response = { - processFile: true, - preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"', - container: "." + file.container, - handBrakeMode: false, - FFmpegMode: true, - reQueueAfter: true, - infoLog: "☒This file has closed captions \n", - }; - - return response; - } else { - response.infoLog += - "☑Closed captions have not been detected on this file \n"; - response.processFile = false; + } - return response; + //Check if Closed Captions are set at file level + if (file.hasClosedCaptions) { + response.processFile = true; + response.infoLog += '☒This file has closed captions \n'; + return response; + } + //If not, check for Closed Captions in the streams + for (const stream in file.ffProbeData.streams) { + if (stream.closed_captions) { + response.processFile = true; } } + + response.infoLog += + response.processFile ? '☒This file has burnt closed captions \n' + : '☑Closed captions have not been detected on this file \n'; + + return response; } module.exports.details = details; From 04b001baa7f78bfe1975a4fac5c1fbaafabaca4e Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Sat, 13 Nov 2021 18:52:19 -0300 Subject: [PATCH 18/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- ...darr_Plugin_x7ac_Remove_Closed_Captions.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index ec84c9b..be6fc29 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -1,16 +1,16 @@ function details() { return { - id: "Tdarr_Plugin_x7ac_Remove_Closed_Captions", - Stage: "Pre-processing", - Name: "Remove closed captions", - Type: "Video", - Operation: "Remux", + id: 'Tdarr_Plugin_WS_Remove_Closed_Captions', + Stage: 'Pre-processing', + Name: 'Remove burned closed captions', + Type: 'Video', + Operation: 'Remux', Description: - "[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed.", + '[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed. Derived from x7ac Closed Captions plugin.', Version: "1.00", Link: - "https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js", - Tags: "pre-processing,ffmpeg,subtitle only", + 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/', + Tags: 'pre-processing,ffmpeg,subtitle only', }; } @@ -18,25 +18,25 @@ function plugin(file) { const response = { processFile: false, preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"', - container: "." + file.container, + container: '.' + file.container, handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: "", + infoLog: '', }; - if (file.fileMedium !== "video") { - response.infoLog += "☒File is not video \n"; + if (file.fileMedium !== 'video') { + response.infoLog += '☒File is not video \n'; return response; } - //Check if Closed Captions are set at file level + // Check if Closed Captions are set at file level if (file.hasClosedCaptions) { response.processFile = true; response.infoLog += '☒This file has closed captions \n'; return response; } - //If not, check for Closed Captions in the streams + // If not, check for Closed Captions in the streams for (const stream in file.ffProbeData.streams) { if (stream.closed_captions) { response.processFile = true; @@ -51,4 +51,4 @@ function plugin(file) { } module.exports.details = details; -module.exports.plugin = plugin; +module.exports.plugin = plugin; \ No newline at end of file From 2ff96a98ea559ddc10685ee4c02594c4787908f5 Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Mon, 15 Nov 2021 23:33:41 -0300 Subject: [PATCH 19/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index be6fc29..c347271 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -1,15 +1,15 @@ function details() { return { - id: 'Tdarr_Plugin_WS_Remove_Closed_Captions', + id: 'Tdarr_Plugin_x7ac_Remove_Closed_Captions', Stage: 'Pre-processing', Name: 'Remove burned closed captions', Type: 'Video', Operation: 'Remux', Description: - '[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed. Derived from x7ac Closed Captions plugin.', - Version: "1.00", + '[Contains built-in filter] If detected, closed captions (XDS,608,708) will be removed from streams.', + Version: '1.01', Link: - 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/', + 'https://github.com/HaveAGitGat/Tdarr_Plugins/blob/master/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js', Tags: 'pre-processing,ffmpeg,subtitle only', }; } From 943da9aa5f8e7b8627df6fcca564d8df9a8f310f Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Wed, 17 Nov 2021 13:34:04 -0300 Subject: [PATCH 20/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index c347271..4fde8ac 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -43,12 +43,9 @@ function plugin(file) { } } - response.infoLog += - response.processFile ? '☒This file has burnt closed captions \n' - : '☑Closed captions have not been detected on this file \n'; + response.infoLog += response.processFile?'☒This file has burnt closed captions \n':'☑Closed captions have not been detected on this file \n'; return response; } - module.exports.details = details; -module.exports.plugin = plugin; \ No newline at end of file +module.exports.plugin = plugin; From 722f14d3d1e57b28032fa258508be36c8fc629ae Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Thu, 25 Nov 2021 11:21:59 -0300 Subject: [PATCH 21/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- .../Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 4fde8ac..0a0b4be 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -18,18 +18,16 @@ function plugin(file) { const response = { processFile: false, preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"', - container: '.' + file.container, + container: '.${file.container}', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, infoLog: '', }; - if (file.fileMedium !== 'video') { response.infoLog += '☒File is not video \n'; return response; } - // Check if Closed Captions are set at file level if (file.hasClosedCaptions) { response.processFile = true; @@ -37,14 +35,15 @@ function plugin(file) { return response; } // If not, check for Closed Captions in the streams - for (const stream in file.ffProbeData.streams) { + var streams = file.ffProbeData.streams; + for (const stream in streams) { if (stream.closed_captions) { response.processFile = true; + break; } } - - response.infoLog += response.processFile?'☒This file has burnt closed captions \n':'☑Closed captions have not been detected on this file \n'; - + response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' : + '☑Closed captions have not been detected on this file \n'; return response; } module.exports.details = details; From 1456e4548022dcd7f8e03b0232c01edef52d85e0 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:05:30 +0000 Subject: [PATCH 22/28] Add example size check plugin --- .../Tdarr_Plugin_a9he_New_file_size_check.js | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 examples/Tdarr_Plugin_a9he_New_file_size_check.js diff --git a/examples/Tdarr_Plugin_a9he_New_file_size_check.js b/examples/Tdarr_Plugin_a9he_New_file_size_check.js new file mode 100644 index 0000000..15bcee3 --- /dev/null +++ b/examples/Tdarr_Plugin_a9he_New_file_size_check.js @@ -0,0 +1,37 @@ +module.exports.details = function details() { + return { + id: 'Tdarr_Plugin_a9he_New_file_size_check', + Stage: 'Pre-processing', + Name: 'New file size check', + Type: 'Video', + Operation: 'Transcode', + Description: 'Give an error if new file is larger than the original \n\n', + Version: '1.00', + Link: '', + Tags: '', + }; +}; + +module.exports.plugin = function plugin(file, librarySettings, inputs, otherArguments) { + // Must return this object at some point in the function else plugin will fail. + const response = { + processFile: false, + preset: '', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '', + }; + + const newSize = file.file_size; + const oldSize = otherArguments.originalLibraryFile.file_size; + if (newSize > oldSize) { + // Item will be errored in UI + throw new Error(`Error! New file has size ${newSize} which is larger than original file ${oldSize}`); + } else if (newSize < oldSize) { + response.infoLog += `New file has size ${newSize} which is smaller than original file ${oldSize}`; + } + // if file sizes are exactly the same then file has not been transcoded yet + + return response; +}; From 3acaafef5f066e9b3c23e5990035579e2ad517f4 Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Wed, 8 Dec 2021 11:54:23 -0300 Subject: [PATCH 23/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 0a0b4be..843057d 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -17,7 +17,7 @@ function details() { function plugin(file) { const response = { processFile: false, - preset: ',-map 0 -codec copy -bsf:v "filter_units=remove_types=6"', + preset: `,-map 0 -codec copy -bsf:v "filter_units=remove_types=6"`, container: '.${file.container}', handBrakeMode: false, FFmpegMode: true, From 07c75473e0a6554b497b9cf13b8afefd6789f0f5 Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Wed, 8 Dec 2021 12:01:59 -0300 Subject: [PATCH 24/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 843057d..85fd185 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -17,7 +17,7 @@ function details() { function plugin(file) { const response = { processFile: false, - preset: `,-map 0 -codec copy -bsf:v "filter_units=remove_types=6"`, + preset: `,-map 0 -codec copy -bsf:v 'filter_units=remove_types=6'`, container: '.${file.container}', handBrakeMode: false, FFmpegMode: true, @@ -35,7 +35,7 @@ function plugin(file) { return response; } // If not, check for Closed Captions in the streams - var streams = file.ffProbeData.streams; + const streams = file.ffProbeData.streams; for (const stream in streams) { if (stream.closed_captions) { response.processFile = true; From a4014582890b590ac3cd1c83d351bdd5648b9854 Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Wed, 8 Dec 2021 12:03:55 -0300 Subject: [PATCH 25/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 85fd185..66f9a84 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -43,7 +43,7 @@ function plugin(file) { } } response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' : - '☑Closed captions have not been detected on this file \n'; + '☑Closed captions have not been detected on this file \n'; return response; } module.exports.details = details; From 79bce89388da64dc0c91f25c0fcd00aa5585ca84 Mon Sep 17 00:00:00 2001 From: Will Segatto Date: Wed, 8 Dec 2021 17:44:24 -0300 Subject: [PATCH 26/28] Update Tdarr_Plugin_x7ac_Remove_Closed_Captions.js --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 66f9a84..45ad864 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -17,8 +17,8 @@ function details() { function plugin(file) { const response = { processFile: false, - preset: `,-map 0 -codec copy -bsf:v 'filter_units=remove_types=6'`, - container: '.${file.container}', + preset: ',-map 0 -codec copy -bsf:v \"filter_units=remove_types=6\"', + container: `.${file.container}`, handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, @@ -35,15 +35,15 @@ function plugin(file) { return response; } // If not, check for Closed Captions in the streams - const streams = file.ffProbeData.streams; + const {streams} = file.ffProbeData; for (const stream in streams) { if (stream.closed_captions) { response.processFile = true; break; } } - response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' : - '☑Closed captions have not been detected on this file \n'; + response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' + : '☑Closed captions have not been detected on this file \n'; return response; } module.exports.details = details; From a2dd698acd18a9052dc10f8bd158fac11797d355 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Thu, 9 Dec 2021 07:34:59 +0000 Subject: [PATCH 27/28] Fix lint --- Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 45ad864..8918a4e 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -17,6 +17,7 @@ function details() { function plugin(file) { const response = { processFile: false, + // eslint-disable-next-line no-useless-escape preset: ',-map 0 -codec copy -bsf:v \"filter_units=remove_types=6\"', container: `.${file.container}`, handBrakeMode: false, @@ -35,14 +36,14 @@ function plugin(file) { return response; } // If not, check for Closed Captions in the streams - const {streams} = file.ffProbeData; - for (const stream in streams) { + const { streams } = file.ffProbeData; + streams.forEach((stream) => { if (stream.closed_captions) { response.processFile = true; - break; } - } - response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' + }); + + response.infoLog += response.processFile ? '☒This file has burnt closed captions \n' : '☑Closed captions have not been detected on this file \n'; return response; } From c208ace5f2f308a164e8370f7b03e64558b76917 Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Thu, 9 Dec 2021 07:51:19 +0000 Subject: [PATCH 28/28] lint --- Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js index b3ac785..ba47e98 100644 --- a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js +++ b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js @@ -34,7 +34,7 @@ // If the source video is less than this rate the script will either: // Copy the existing stream, if the codec is hevc // Transcode the stream to hevc using 80% of the original streams bitrate -// It could probably be less but if the source is of low bitrate we don't want to compromise too much on the transcode +// It could probably be less but if the source is of low bitrate we don�t want to compromise too much on the transcode // // If the source media bitrate is close, within 10%, of the target bitrate and the codec is hevc, it will copy instead of transcode to preserve quality // @@ -53,7 +53,7 @@ // If the source audio is less than this rate the script will either: // Copy the existing stream, if the codec is aac // Transcode the stream to aac using 100% of the original streams bitrate -// It could probably be less but if the source is of low bitrate but, we don't want to compromise too much on the transcode +// It could probably be less but if the source is of low bitrate but, we don�t want to compromise too much on the transcode // // Subtitles: // All are removed?? (TODO: ensure this is correct and mention the flag to keep them if desired) @@ -761,9 +761,9 @@ function findMediaInfoItem(file, index) { currMIOrder = file.mediaInfo.track[i].ID - 1; } - if (currMIOrder == index|| currMIOrder == "0-" + index) { - return i; - } + if (currMIOrder == index|| currMIOrder == "0-" + index) { + return i; + } } return -1; }