@ -357,8 +357,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if ( strstreamType === 'video' ) {
if ( strstreamType === 'video' ) {
if ( file . ffProbeData . streams [ i ] . codec _name !== 'mjpeg'
if ( file . ffProbeData . streams [ i ] . codec _name !== 'mjpeg'
&& file . ffProbeData . streams [ i ] . codec _name !== 'png' ) {
&& file . ffProbeData . streams [ i ] . codec _name !== 'png' ) {
// Try checking file stats using Mediainfo first, then ffprobe.
if ( videoBR <= 0 ) { // Process if videoBR is not yet valid
try {
try { // Try checking file stats using Mediainfo first, then ffprobe.
videoBR = Number ( file . mediaInfo . track [ i + 1 ] . BitRate ) / 1000 ;
videoBR = Number ( file . mediaInfo . track [ i + 1 ] . BitRate ) / 1000 ;
if ( videoBR <= 0 || Number . isNaN ( videoBR ) ) {
if ( videoBR <= 0 || Number . isNaN ( videoBR ) ) {
if ( Number ( file . ffProbeData . streams [ i ] . tags . BPS ) > 0 ) {
if ( Number ( file . ffProbeData . streams [ i ] . tags . BPS ) > 0 ) {
@ -371,22 +371,33 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
videoBR = 0 ; // Set videoBR to 0 for safety
videoBR = 0 ; // Set videoBR to 0 for safety
}
}
// Check if duration info is filled, if so convert time format to minutes.
}
if ( Number . isNaN ( file . meta . Duration ) === false ) {
if ( duration <= 0 ) { // Process if duration is not yet valid
// If duration is a number then convert seconds to minutes
try { // Attempt to get duration info
duration = file . meta . Duration / 60 ;
if ( Number . isNaN ( file . meta . Duration ) ) {
} else if ( typeof file . meta . Duration !== 'undefined' ) {
// Get seconds by using a Date & then convert to minutes
duration = file . meta . Duration ;
duration = file . meta . Duration ;
duration = ( new Date ( ` 1970-01-01T ${ duration } Z ` ) . getTime ( ) / 1000 ) / 60 ;
duration = ( new Date ( ` 1970-01-01T ${ duration } Z ` ) . getTime ( ) / 1000 ) / 60 ;
} else { // If not filled then get duration of video stream and do the same.
} else if ( file . meta . Duration > 0 ) {
duration = file . meta . Duration / 60 ;
}
if ( duration <= 0 || Number . isNaN ( duration ) ) {
if ( typeof file . mediaInfo . track [ i + 1 ] . Duration !== 'undefined' ) {
duration = file . mediaInfo . track [ i + 1 ] . Duration ;
duration = ( new Date ( ` 1970-01-01T ${ duration } Z ` ) . getTime ( ) / 1000 ) / 60 ;
} else if ( typeof file . ffProbeData . streams [ i ] . tags . DURATION !== 'undefined' ) {
duration = file . ffProbeData . streams [ i ] . tags . DURATION ;
duration = file . ffProbeData . streams [ i ] . tags . DURATION ;
duration = ( new Date ( ` 1970-01-01T ${ duration } Z ` ) . getTime ( ) / 1000 ) / 60 ;
duration = ( new Date ( ` 1970-01-01T ${ duration } Z ` ) . getTime ( ) / 1000 ) / 60 ;
}
}
if ( videoBR <= 0 || Number . isNaN ( videoBR ) ) {
}
// videoBR not yet valid so Loop
} catch ( err ) {
// Catch error - Ignore & carry on - If check can bomb out if tags don't exist...
duration = 0 ; // Set duration to 0 for safety
}
}
if ( ( videoBR <= 0 || Number . isNaN ( videoBR ) ) || ( duration <= 0 || Number . isNaN ( duration ) ) ) {
// videoBR or duration not yet valid so Loop
} else {
} else {
break ; // Exit loop if videoBR is valid
break ; // Exit loop if both valid
}
}
}
}
}
}
@ -395,9 +406,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if ( Number . isNaN ( videoBR ) || videoBR <= 0 ) {
if ( Number . isNaN ( videoBR ) || videoBR <= 0 ) {
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
// Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)"
currentBitrate = Math . round ( file . file _size / ( duration * 0.0075 ) ) ;
currentBitrate = Math . round ( file . file _size / ( duration * 0.0075 ) ) ;
response . infoLog += '==WARNING== Failed to get an accurate video bitrate, ' ;
response . infoLog += '==WARNING== Failed to get an accurate video bitrate, '
response . infoLog += ` falling back to old method to get OVERALL file bitrate of ${ currentBitrate } kbps. ` ;
+ ` falling back to old method to get OVERALL file bitrate of ${ currentBitrate } kbps. `
response . infoLog += 'Bitrate calculations for video encode will likely be inaccurate... \n';
+ 'Bitrate calculations for video encode will likely be inaccurate... \n';
} else {
} else {
currentBitrate = Math . round ( videoBR ) ;
currentBitrate = Math . round ( videoBR ) ;
response . infoLog += ` ☑ It looks like the current video bitrate is ${ currentBitrate } kbps. \n ` ;
response . infoLog += ` ☑ It looks like the current video bitrate is ${ currentBitrate } kbps. \n ` ;
@ -414,8 +425,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// If targetBitrate or currentBitrate comes out as 0 then something
// If targetBitrate or currentBitrate comes out as 0 then something
// has gone wrong and bitrates could not be calculated.
// has gone wrong and bitrates could not be calculated.
// Cancel plugin completely.
// Cancel plugin completely.
if ( targetBitrate <= 0 || currentBitrate <= 0 ) {
if ( targetBitrate <= 0 || currentBitrate <= 0 || overallBitRate <= 0 ) {
response . infoLog += '☒ Target bitrate could not be calculated. Skipping this plugin. \n';
response . infoLog += '☒ Target bitrate s could not be calculated. Skipping this plugin.\n';
return response ;
return response ;
}
}
@ -423,17 +434,16 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// has gone wrong as that is not what we want.
// has gone wrong as that is not what we want.
// Cancel plugin completely.
// Cancel plugin completely.
if ( targetBitrate >= currentBitrate ) {
if ( targetBitrate >= currentBitrate ) {
response . infoLog += ` ☒ Target bitrate has been calculated as ${ targetBitrate } kbps. This is equal or greater ` ;
response . infoLog += ` ☒ Target bitrate has been calculated as ${ targetBitrate } kbps. This is equal or greater than `
response . infoLog += "than the current bitrate... Something has gone wrong and this shouldn't happen! "
+ "the current bitrate... Something has gone wrong and this shouldn't happen! Skipping this plugin.\n" ;
+ 'Skipping this plugin. \n' ;
return response ;
return response ;
}
}
// Ensure that bitrate_cutoff is set if reconvert_hevc is true since we need some protection against a loop
// Ensure that bitrate_cutoff is set if reconvert_hevc is true since we need some protection against a loop
// Cancel the plugin
// Cancel the plugin
if ( inputs . reconvert _hevc === true && inputs . bitrate _cutoff <= 0 && inputs . hevc _max _bitrate <= 0 ) {
if ( inputs . reconvert _hevc === true && inputs . bitrate _cutoff <= 0 && inputs . hevc _max _bitrate <= 0 ) {
response . infoLog += ` Reconvert HEVC is ${ inputs . reconvert _hevc } , however there is no bitrate cutoff ` ;
response . infoLog += ` Reconvert HEVC is ${ inputs . reconvert _hevc } , however there is no bitrate cutoff or HEVC `
response . infoLog += 'or HEVC specific cutoff set so we have no way to know when to stop processing this file. \n'
+ ' specific cutoff set so we have no way to know when to stop processing this file.\n'
+ 'Either set reconvert_HEVC to false or set a bitrate cutoff and set a hevc_max_bitrate cutoff.\n'
+ 'Either set reconvert_HEVC to false or set a bitrate cutoff and set a hevc_max_bitrate cutoff.\n'
+ '☒ Skipping this plugin.\n' ;
+ '☒ Skipping this plugin.\n' ;
return response ;
return response ;
@ -459,8 +469,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// Checks if targetBitrate is above inputs.max_average_bitrate.
// Checks if targetBitrate is above inputs.max_average_bitrate.
// If so then clamp target bitrate
// If so then clamp target bitrate
if ( targetBitrate > inputs . max _average _bitrate ) {
if ( targetBitrate > inputs . max _average _bitrate ) {
response . infoLog += 'Our target bitrate is above the max_average_bitrate ';
response . infoLog += 'Our target bitrate is above the max_average_bitrate so clamping at max of '
response . infoLog + = ` so clamping at max of ${ inputs . max _average _bitrate } kbps. \n ` ;
+ ` ${ inputs . max _average _bitrate } kbps. \n ` ;
targetBitrate = Math . round ( inputs . max _average _bitrate ) ;
targetBitrate = Math . round ( inputs . max _average _bitrate ) ;
minimumBitrate = Math . round ( targetBitrate * 0.75 ) ;
minimumBitrate = Math . round ( targetBitrate * 0.75 ) ;
maximumBitrate = Math . round ( targetBitrate * 1.25 ) ;
maximumBitrate = Math . round ( targetBitrate * 1.25 ) ;
@ -472,8 +482,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if ( inputs . min _average _bitrate > 0 ) {
if ( inputs . min _average _bitrate > 0 ) {
// Exit the plugin is the cutoff is less than the min average bitrate. Most likely user error
// Exit the plugin is the cutoff is less than the min average bitrate. Most likely user error
if ( inputs . bitrate _cutoff < inputs . min _average _bitrate ) {
if ( inputs . bitrate _cutoff < inputs . min _average _bitrate ) {
response . infoLog += ` ☒ Bitrate cutoff ${ inputs . bitrate _cutoff } k is less than the set minimum
response . infoLog += ` ☒ Bitrate cutoff ${ inputs . bitrate _cutoff } k is less than the set minimum `
average bitrate set of $ { inputs . min _average _bitrate } kbps . We don ' t want this . Cancelling plugin . \ n ` ;
+ ` average bitrate set of ${ inputs . min _average _bitrate } kbps. We don't want this. Cancelling plugin. \n ` ;
return response ;
return response ;
}
}
// Checks if inputs.bitrate_cutoff is below inputs.min_average_bitrate.
// Checks if inputs.bitrate_cutoff is below inputs.min_average_bitrate.
@ -540,13 +550,27 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
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 -0:v: ${ videoIdx } ` ;
extraArguments += ` -map -0:v: ${ videoIdx } ` ;
} else { // Ensure to only do further checks if video stream is valid for use
} else { // Ensure to only do further checks if video stream is valid for use
// Check for HDR in files. If so exit plugin. HDR can be complicated
// Check for HDR in files. Attempt to use same color
// and some aspects are still unsupported in ffmpeg I believe. Likely we don't want to re-encode anything HDR.
if ( ( file . ffProbeData . streams [ i ] . color _space === 'bt2020nc'
if ( file . ffProbeData . streams [ i ] . color _space === 'bt2020nc'
|| file . ffProbeData . streams [ i ] . color _space === 'bt2020n' )
&& file . ffProbeData . streams [ i ] . color _transfer === 'smpte2084'
&& ( file . ffProbeData . streams [ i ] . color _transfer === 'smpte2084'
|| file . ffProbeData . streams [ i ] . color _transfer === 'arib-std-b67' )
&& file . ffProbeData . streams [ i ] . color _primaries === 'bt2020' ) {
&& file . ffProbeData . streams [ i ] . color _primaries === 'bt2020' ) {
response . infoLog += '☒ This looks to be a HDR file. HDR files are unfortunately '
response . infoLog += '==WARNING== This looks to be a HDR file. HDR is supported but '
+ 'not supported by this plugin. Exiting plugin. \n\n' ;
+ 'correct encoding is not guaranteed.\n' ;
extraArguments += ` -color_primaries ${ file . ffProbeData . streams [ i ] . color _primaries } `
+ ` -color_trc ${ file . ffProbeData . streams [ i ] . color _transfer } `
+ ` -colorspace ${ file . ffProbeData . streams [ i ] . color _space } ` ;
}
// Check if codec of stream is HEVC, Vp9 or AV1
// AND check if file.container does NOT match inputs.container. If so remux file.
if ( ( file . ffProbeData . streams [ i ] . codec _name === 'hevc'
|| file . ffProbeData . streams [ i ] . codec _name === 'vp9'
|| file . ffProbeData . streams [ i ] . codec _name === 'av1' ) && file . container !== inputs . container ) {
response . infoLog += ` ☒ File is HEVC, VP9 or AV1 but is not in ${ inputs . container } container. Remuxing. \n ` ;
response . preset = ` <io> -map 0 -c copy ${ extraArguments } ` ;
response . processFile = true ;
return response ;
return response ;
}
}
@ -561,17 +585,6 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
return response ;
return response ;
}
}
// Check if codec of stream is HEVC, Vp9 or AV1
// AND check if file.container does NOT match inputs.container.
// If so remux file.
if ( ( file . ffProbeData . streams [ i ] . codec _name === 'hevc' || file . ffProbeData . streams [ i ] . codec _name === 'vp9'
|| file . ffProbeData . streams [ i ] . codec _name === 'av1' ) && file . container !== inputs . container ) {
response . infoLog += ` ☒ File is HEVC, VP9 or AV1 but is not in ${ inputs . container } container. Remuxing. \n ` ;
response . preset = ` <io> -map 0 -c copy ${ extraArguments } ` ;
response . processFile = true ;
return response ;
}
// New logic for reprocessing HEVC. Mainly done for my own use.
// New logic for reprocessing HEVC. Mainly done for my own use.
// We attempt to get accurate stats earlier - If we can't we fall back onto overall bitrate
// We attempt to get accurate stats earlier - If we can't we fall back onto overall bitrate
// which can be inaccurate. We may inflate the current bitrate check so we don't keep looping this logic.
// which can be inaccurate. We may inflate the current bitrate check so we don't keep looping this logic.
@ -580,14 +593,14 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if ( inputs . hevc _max _bitrate > 0 ) {
if ( inputs . hevc _max _bitrate > 0 ) {
if ( currentBitrate > inputs . hevc _max _bitrate ) {
if ( currentBitrate > inputs . hevc _max _bitrate ) {
// If bitrate is higher then hevc_max_bitrate then need to re-encode
// If bitrate is higher then hevc_max_bitrate then need to re-encode
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, `
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, VP9 or AV1. `
+ ` VP9 or AV1. Using HEVC specific cutoff of ${ inputs . hevc _max _bitrate } kbps. \n ` ;
+ ` Using HEVC specific cutoff of ${ inputs . hevc _max _bitrate } kbps. \n `
response . infoLog += '☒ The file is still above this new cutoff! Reconverting. \n';
+ '☒ The file is still above this new cutoff! Reconverting. \n';
} else {
} else {
// Otherwise we're now below the hevc cutoff and we can exit
// Otherwise we're now below the hevc cutoff and we can exit
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, `
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, VP9 or AV1. `
+ ` VP9 or AV1. Using HEVC specific cutoff of ${ inputs . hevc _max _bitrate } kbps. \n ` ;
+ ` Using HEVC specific cutoff of ${ inputs . hevc _max _bitrate } kbps. \n `
response . infoLog += '☑ The file is NOT above this new cutoff. Exiting plugin. \n';
+ '☑ The file is NOT above this new cutoff. Exiting plugin. \n';
return response ;
return response ;
}
}
@ -595,21 +608,19 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
// looping this plugin. For maximum safety we simply multiply the cutoff by 2.
// looping this plugin. For maximum safety we simply multiply the cutoff by 2.
} else if ( currentBitrate > ( inputs . bitrate _cutoff * 2 ) ) {
} else if ( currentBitrate > ( inputs . bitrate _cutoff * 2 ) ) {
inflatedCutoff = Math . round ( inputs . bitrate _cutoff * 2 ) ;
inflatedCutoff = Math . round ( inputs . bitrate _cutoff * 2 ) ;
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, ` ;
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, VP9 or AV1. `
response . infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, ' ;
+ ` Will use Overall file Bitrate for HEVC files as safety, bitrate is ${ overallBitRate } kbps. \n `
response . infoLog += ` bitrate is ${ overallBitRate } kbps. \n ` ;
+ 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety!\n'
response . infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n' ;
+ ` Cutoff now temporarily ${ inflatedCutoff } kbps. \n `
response . infoLog += ` Cutoff now temporarily ${ inflatedCutoff } kbps. \n ` ;
+ '☒ The file is still above this new cutoff! Reconverting.\n' ;
response . infoLog += '☒ The file is still above this new cutoff! Reconverting. \n' ;
} else {
} else {
// File is below cutoff so we can exit
// File is below cutoff so we can exit
inflatedCutoff = Math . round ( inputs . bitrate _cutoff * 2 ) ;
inflatedCutoff = Math . round ( inputs . bitrate _cutoff * 2 ) ;
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, ` ;
response . infoLog += ` Reconvert_hevc is ${ inputs . reconvert _hevc } & the file is already HEVC, VP9 or AV1. `
response . infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, ' ;
+ ` Will use Overall file Bitrate for HEVC files as safety, bitrate is ${ overallBitRate } kbps. \n `
response . infoLog += ` bitrate is ${ overallBitRate } kbps. \n ` ;
+ 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety!\n'
response . infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n' ;
+ ` Cutoff now temporarily ${ inflatedCutoff } kbps. \n `
response . infoLog += ` Cutoff now temporarily ${ inflatedCutoff } kbps. \n ` ;
+ '☑The file is NOT above this new cutoff. Exiting plugin.\n' ;
response . infoLog += '☑The file is NOT above this new cutoff. Exiting plugin. \n' ;
return response ;
return response ;
}
}
}
}
@ -649,8 +660,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
break ;
break ;
case 'h264' :
case 'h264' :
if ( high10 === true ) {
if ( high10 === true ) {
response . infoLog += ` Input file is ${ file . video _codec _name } High10. Hardware Decode not supported. \n ` ;
swDecode = true ;
swDecode = true ;
response . infoLog += 'Input file is h264 High10. Hardware Decode not supported.\n' ;
}
}
break ;
break ;
case 'mjpeg' :
case 'mjpeg' :
@ -693,11 +704,11 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
bitrateSettings = ` -b:v ${ targetBitrate } k -minrate ${ minimumBitrate } k `
bitrateSettings = ` -b:v ${ targetBitrate } k -minrate ${ minimumBitrate } k `
+ ` -maxrate ${ maximumBitrate } k -bufsize ${ currentBitrate } k ` ;
+ ` -maxrate ${ maximumBitrate } k -bufsize ${ currentBitrate } k ` ;
// Print to infoLog information around file & bitrate settings.
// Print to infoLog information around file & bitrate settings.
response . infoLog += ` Container for output selected as ${ inputs . container } . \n ` ;
response . infoLog += ` Container for output selected as ${ inputs . container } . \n `
response . infoLog += 'Encode variable bitrate settings: \n' ;
+ 'Encode variable bitrate settings:\n'
response . infoLog += ` Target = ${ targetBitrate } k \n ` ;
+ ` Target = ${ targetBitrate } k \n `
response . infoLog += ` Minimum = ${ minimumBitrate } k \n ` ;
+ ` Minimum = ${ minimumBitrate } k \n `
response . infoLog += ` Maximum = ${ maximumBitrate } k \n ` ;
+ ` Maximum = ${ maximumBitrate } k \n ` ;
// START PRESET
// START PRESET
// -fflags +genpts should regenerate timestamps if they end up missing...
// -fflags +genpts should regenerate timestamps if they end up missing...
@ -712,12 +723,12 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
response . preset += '-hwaccel videotoolbox' ;
response . preset += '-hwaccel videotoolbox' ;
break ;
break ;
case 'linux' : // Linux - Full device, should fix child_device_type warnings
case 'linux' : // Linux - Full device, should fix child_device_type warnings
response . preset += ` -hwaccel qsv -hwaccel_output_format qsv
response . preset += '-hwaccel qsv -hwaccel_output_format qsv '
- init _hw _device qsv : hw _any , child _device _type = vaapi ` ;
+ '-init_hw_device qsv:hw_any,child_device_type=vaapi ' ;
break ;
break ;
case 'win32' : // Windows - Full device, should fix child_device_type warnings
case 'win32' : // Windows - Full device, should fix child_device_type warnings
response . preset += ` -hwaccel qsv -hwaccel_output_format qsv
response . preset += '-hwaccel qsv -hwaccel_output_format qsv '
- init _hw _device qsv : hw , child _device _type = d3d11va ` ;
+ '-init_hw_device qsv:hw,child_device_type=d3d11va ' ;
break ;
break ;
default :
default :
response . preset += '-hwaccel qsv -hwaccel_output_format qsv -init_hw_device qsv:hw_any ' ;
response . preset += '-hwaccel qsv -hwaccel_output_format qsv -init_hw_device qsv:hw_any ' ;
@ -728,12 +739,12 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
response . preset += '-hwaccel videotoolbox' ;
response . preset += '-hwaccel videotoolbox' ;
break ;
break ;
case 'linux' : // Linux - Full device, should fix child_device_type warnings
case 'linux' : // Linux - Full device, should fix child_device_type warnings
response . preset += ` -hwaccel_output_format qsv
response . preset += '-hwaccel_output_format qsv '
- init _hw _device qsv : hw _any , child _device _type = vaapi ` ;
+ '-init_hw_device qsv:hw_any,child_device_type=vaapi ' ;
break ;
break ;
case 'win32' : // Windows - Full device, should fix child_device_type warnings
case 'win32' : // Windows - Full device, should fix child_device_type warnings
response . preset += ` -hwaccel_output_format qsv
response . preset += '-hwaccel_output_format qsv '
- init _hw _device qsv : hw , child _device _type = d3d11va ` ;
+ '-init_hw_device qsv:hw,child_device_type=d3d11va ' ;
break ;
break ;
default :
default :
// Default to enabling hwaccel for output only
// Default to enabling hwaccel for output only