@ -2,7 +2,7 @@
//////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Author: JarBinks, Zachg99, Jeff47
// Date: 0 8/19/2020
// Date: 0 4/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
@ -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)
@ -85,46 +85,67 @@
// 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
//
// **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
// 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"
//
// 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
//
//////////////////////////////////////////////////////////////////////////////////////////////////////
@ -136,7 +157,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 1 of 2 routines you should to run **Part 1** \n" ,
Version : " 1.6 ",
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"
}
@ -164,11 +185,13 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//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 = 108 0; //Any thing over this size, I.E. 4K, will be reduced to this
var targetcodeccompression = 0.0 75 ; //This effects the target bitrate by assuming a compre sion ratio
var maxvideoheight = 216 0; //Any thing over this size, I.E. 4K, will be reduced to this
var targetcodeccompression = 0.0 8 ; //This effects the target bitrate by assuming a compre s sion 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 ;
@ -189,7 +212,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
var targetaudiochannels = 6 ; //Any thing above this number of channels will be reduced to it, because I cannot listen to it
//Subtitles
var bolIncludeSubs = fals e;
var bolIncludeSubs = tru e;
//////////////////////////////////////////////////////////////////////////////////////////////////////
var proc = require ( "child_process" ) ;
@ -197,16 +220,16 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//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 ( ) ) ;
//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.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;
@ -221,7 +244,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//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.infoLog += " objFFProbeInfo:" + JSON.stringify(objFFProbeInfo , null, 4) + "\n";
//response.processFile = false;
//return response;
@ -234,21 +257,21 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
//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 ( 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 poss bility the stats were not updated during any previous transcode, lets make sure
//If the existing container is mkv there is a poss i bility 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 ( 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 ( objMedInfo. media . track [ 0 ] . extra != undefined && objMedInfo. media . track [ 0 ] . extra . JBDONEDATE != undefined ) {
var JBDate = Date . parse ( objMedInfo. media . track [ 0 ] . extra . JBDONEDATE ) ;
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 ) {
@ -272,14 +295,15 @@ function plugin(file, librarySettings, inputs, otherArguments) {
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 ( ) ) ;
//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 ;
@ -295,7 +319,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
var audioIdxBitrate = 0 ;
var boldosubs = false ;
var bolforcenosubs = tru e;
var bolforcenosubs = fals e;
var boldosubsconvert = false ;
var boldochapters = true ;
@ -309,43 +333,43 @@ function plugin(file, librarySettings, inputs, otherArguments) {
var strstreamType = "" ;
// Go through each stream in the file.
for ( var i = 0 ; i < objFFProbeInfo . streams . length ; i ++ ) {
for ( var i = 0 ; i < file. ffProbeData . streams . length ; i ++ ) {
strstreamType = objFFProbeInfo . streams [ i ] . codec _type . toLowerCase ( ) ;
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 ( 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 ;
}
}
}
//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 ;
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
@ -358,23 +382,19 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//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 ;
audioChannels = file . ffProbeData . streams [ i ] . channels * 1 ;
audioBitrate = file . mediaInfo . track [ findMediaInfoItem ( file , 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 ( 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 = objFFProbeInfo . streams [ audioIdx ] . channels * 1 ;
audioIdxBitrate = objMedInfo. media . track [ findMediaInfoItem ( objMedInfo , audioIdx ) ] . BitRate ;
audioIdxChannels = file. ffProbeData . streams [ audioIdx ] . channels * 1 ;
audioIdxBitrate = file. mediaInfo . track [ findMediaInfoItem ( file , audioIdx ) ] . BitRate ;
if ( audioChannels > audioIdxChannels ) {
response . infoLog += "More Audio Channels \n" ;
@ -385,14 +405,14 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
}
} else {
response . infoLog += "Audio stream " + i + ":???:" + objFFProbeInfo . streams [ i ] . codec _name + ":" + audioChannels + ":" + audioBitrate + "bps:" ;
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 = objFFProbeInfo . streams [ audioIdxOther ] . channels * 1 ;
audioIdxBitrate = objMedInfo. media . track [ findMediaInfoItem ( objMedInfo , audioIdxOther ) ] . BitRate ;
audioIdxChannels = file. ffProbeData . streams [ audioIdxOther ] . channels * 1 ;
audioIdxBitrate = file. mediaInfo . track [ findMediaInfoItem ( file , audioIdxOther ) ] . BitRate ;
if ( audioChannels > audioIdxChannels ) {
response . infoLog += "More Audio Channels \n" ;
@ -409,9 +429,10 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//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
//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 ( objFFProbeInfo . streams [ i ] . codec _name == "mov_text" ) {
if ( file. ffProbeData . streams [ i ] . codec _name == "mov_text" ) {
boldosubsconvert = true ;
response . infoLog += "SubTitles Found (mov_text), will convert \n" ;
} else {
@ -425,129 +446,123 @@ function plugin(file, librarySettings, inputs, otherArguments) {
//////////////////////////////////////////////////////////////////////////////////////////////////////
}
//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 ++ ) {
// 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
}
// 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
}
}
}
// 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 ;
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" ;
}
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
@ -563,14 +578,14 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
}
var audioBR = objMedInfo. media . track [ findMediaInfoItem ( objMedInfo , audioIdx ) ] . BitRate * 1 ;
var audioBR = file. mediaInfo . track [ findMediaInfoItem ( file , audioIdx ) ] . BitRate * 1 ;
if ( objFFProbeInfo . streams [ audioIdx ] . channels > targetaudiochannels ) {
if ( file. ffProbeData . streams [ audioIdx ] . channels > targetaudiochannels ) {
boldownmixAudio = true ;
audionewchannels = targetaudiochannels ;
response . infoLog += "Audio existing Channels, " + objFFProbeInfo . streams [ audioIdx ] . channels + ", is higher than target, " + targetaudiochannels + " \n" ;
response . infoLog += "Audio existing Channels, " + file. ffProbeData . streams [ audioIdx ] . channels + ", is higher than target, " + targetaudiochannels + " \n" ;
} else {
audionewchannels = objFFProbeInfo . streams [ audioIdx ] . channels ;
audionewchannels = file. ffProbeData . streams [ audioIdx ] . channels ;
}
var optimalaudiobitrate = audionewchannels * targetaudiobitrateperchannel ;
@ -582,16 +597,16 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
//If the audio codec is not what we want then we should transcode
if ( objFFProbeInfo . streams [ audioIdx ] . codec _name != targetaudiocodec ) {
if ( file. ffProbeData . streams [ audioIdx ] . codec _name != targetaudiocodec ) {
boltranscodeAudio = true ;
response . infoLog += "Audio Codec, " + objFFProbeInfo . streams [ audioIdx ] . codec _name + ", is different than target, " + targetaudiocodec + ", Changing \n" ;
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 ( objFFProbeInfo . streams [ audioIdx ] . codec _name != targetaudiocodec ) {
if ( file. ffProbeData . streams [ audioIdx ] . codec _name != targetaudiocodec ) {
response . infoLog += "rate" ;
} else {
response . infoLog += "stream" ;
@ -610,6 +625,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
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
@ -641,21 +657,31 @@ function plugin(file, librarySettings, inputs, otherArguments) {
if ( boltranscodeVideo ) {
strFFcmd += strtranscodevideotranscoding ;
if ( bolscaleVideo || boluse10bit || boltranscodeSoftwareDecode ) {
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 ) {
@ -668,6 +694,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
strformat += strtranscodevideoswdecode ;
}
if ( strformat . length > 0 ) {
if ( stroptions . length > 0 ) {
stroptions += "," ;
@ -676,11 +703,10 @@ function plugin(file, librarySettings, inputs, otherArguments) {
}
if ( boltranscodeSoftwareDecode ) {
strFFcmd += strtranscodevideooptions . replace ( "{0}" , str options) ;
strFFcmd += strtranscodevideooptions . replace ( "{0}" , str ChangeVideoRateString + str options) ;
} else {
strFFcmd += strtranscodevideooptions . replace ( "{0}" , str transcodevideoformathw + stroptions ) ;
strFFcmd += strtranscodevideooptions . replace ( "{0}" , str ChangeVideoRateString + str transcodevideoformathw + stroptions ) ;
}
}
strFFcmd += strtranscodevideobitrate . replace ( "{0}" , optimalvideobitrate ) ;
@ -698,7 +724,7 @@ function plugin(file, librarySettings, inputs, otherArguments) {
strFFcmd += strtranscodeaudiodownmixing . replace ( "{0}" , audionewchannels ) ;
}
if ( bolforcenosubs ) {
strFFcmd += strtranscodesubsnone ;
strFFcmd += strtranscodesubsnone ;
} else if ( boldosubs ) {
if ( boldosubsconvert ) {
strFFcmd += strtranscodesubsconvert ;
@ -725,14 +751,21 @@ function plugin(file, librarySettings, inputs, otherArguments) {
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 ;
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 ;