Last active
July 24, 2024 22:18
-
-
Save arabcoders/a2a8d37a509b00b9c7c48d18bdd67254 to your computer and use it in GitHub Desktop.
tdarr boosh qsv plugin without the mkvpropedit and ffmpeg stats call. if you use cloud backend, or have slow drive pool, this is a MUST as the original plugin would cause massive writes for things which aren't really needed. as it was used in the nvidia plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ | |
| const details = () => ({ | |
| id: 'Tdarr_Plugin_AC_HEVC_GPU', | |
| Stage: 'Pre-processing', | |
| Name: 'Transcode a video file', | |
| Type: 'Video', | |
| Operation: 'Transcode', | |
| Description: 'Transcode a video file using ffmpeg. GPU transcoding will be used if possible.', | |
| Version: '1.42', | |
| Tags: 'pre-processing,ffmpeg,video only,nvenc h265,configurable', | |
| Inputs: [ | |
| { | |
| name: 'target_codec', | |
| type: 'string', | |
| defaultValue: 'hevc', | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'hevc', | |
| // 'vp9', | |
| 'h264', | |
| // 'vp8', | |
| ], | |
| }, | |
| tooltip: 'Specify the codec to use', | |
| }, | |
| { | |
| name: 'target_bitrate_multiplier', | |
| type: 'number', | |
| defaultValue: 0.5, | |
| inputUI: { | |
| type: 'text', | |
| }, | |
| tooltip: ` | |
| Specify the multiplier to use to calculate the target bitrate. | |
| Default of 0.5 will roughly half the size of the file. | |
| `, | |
| }, | |
| { | |
| name: 'try_use_gpu', | |
| type: 'boolean', | |
| defaultValue: true, | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'false', | |
| 'true', | |
| ], | |
| }, | |
| tooltip: 'If enabled then will use GPU if possible.', | |
| }, | |
| { | |
| name: 'container', | |
| type: 'string', | |
| defaultValue: 'mkv', | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'mkv', | |
| 'mp4', | |
| 'original', | |
| ], | |
| }, | |
| tooltip: `Specify output container of file. Use original to keep original container. | |
| \\n Ensure that all stream types you may have are supported by your chosen container. | |
| \\n mkv is recommended.`, | |
| }, | |
| { | |
| name: 'bitrate_cutoff', | |
| type: 'number', | |
| defaultValue: 0, | |
| inputUI: { | |
| type: 'text', | |
| }, | |
| tooltip: `Specify bitrate cutoff, files with a current bitrate lower then this will not be transcoded. | |
| \\n Rate is in kbps. | |
| \\n Leave empty to disable. | |
| \\nExample:\\n | |
| 6000 | |
| \\nExample:\\n | |
| 4000`, | |
| }, | |
| { | |
| name: 'enable_10bit', | |
| type: 'boolean', | |
| defaultValue: false, | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'false', | |
| 'true', | |
| ], | |
| }, | |
| tooltip: `Specify if output file should be 10bit. Default is false.`, | |
| }, | |
| { | |
| name: 'bframes_enabled', | |
| type: 'boolean', | |
| defaultValue: false, | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'false', | |
| 'true', | |
| ], | |
| }, | |
| tooltip: `Specify if b frames should be used. Using B frames should decrease file sizes but are only supported on newer GPUs. Default is false.`, | |
| }, | |
| { | |
| name: 'bframes_value', | |
| type: 'number', | |
| defaultValue: 5, | |
| inputUI: { | |
| type: 'text', | |
| }, | |
| tooltip: 'Specify number of bframes to use.', | |
| }, | |
| { | |
| name: 'force_conform', | |
| type: 'boolean', | |
| defaultValue: false, | |
| inputUI: { | |
| type: 'dropdown', | |
| options: [ | |
| 'false', | |
| 'true', | |
| ], | |
| }, | |
| tooltip: `Make the file conform to output containers requirements. | |
| \\n Drop hdmv_pgs_subtitle/eia_608/timed_id3 for MP4. | |
| \\n Drop data streams/eia_608/timed_id3 for MKV. | |
| \\n Default is false`, | |
| }, | |
| { | |
| name: 'exclude_gpus', | |
| type: 'string', | |
| defaultValue: '', | |
| inputUI: { | |
| type: 'text', | |
| }, | |
| tooltip: `Specify the id(s) of any GPUs that needs to be excluded from assigning transcoding tasks. | |
| \\n Seperate with a comma (,). Leave empty to disable. | |
| \\n Get GPU numbers in the node by running 'nvidia-smi' | |
| \\nExample:\\n | |
| 0,1,3,8 | |
| \\nExample:\\n | |
| 3 | |
| \\nExample:\\n | |
| 0`, | |
| }, | |
| { | |
| name: 'allowe_codecs', | |
| type: 'string', | |
| defaultValue: 'vp9,av1', | |
| inputUI: { | |
| type: 'text', | |
| }, | |
| tooltip: `Comma (,) seperated list of video codecs that are allowed to be ignored from processing. Example: vp9,av1`, | |
| }, | |
| ], | |
| }); | |
| const bframeSupport = [ | |
| 'hevc_nvenc', | |
| 'h264_nvenc', | |
| ]; | |
| const conformSpec = { | |
| 'mkv': [ | |
| 'eia_608', 'timed_id3', 'dvb_teletext', 'bin_data' | |
| ], | |
| 'mp4': [ | |
| 'hdmv_pgs_subtitle', 'eia_608', 'subrip', 'timed_id3' | |
| ], | |
| } | |
| const getVideoStreamBitrate = function (file, response) { | |
| let videoBR = 0; | |
| // -- check using media info. | |
| if (file?.mediaInfo?.track) { | |
| for (let i = 0; i < file.mediaInfo.track.length; i += 1) { | |
| let track = file.mediaInfo.track[i]; | |
| try { | |
| if ('video' !== track['@type'].toLowerCase()) { | |
| continue; | |
| } | |
| if (track?.BitRate && Number(track.BitRate) > 0) { | |
| videoBR = Number(track.BitRate); | |
| response.infoLog += `☑ Bitrate ${videoBR / 1000}kbps via mediainfo`; | |
| if (file?.bit_rate) { | |
| response.infoLog += ` & ${Number(file.bit_rate) > 0 ? (file.bit_rate / 1000) : 'NONE'}kbps via file object.\n`; | |
| videoBR = (videoBR + Number(file.bit_rate)) / 2; | |
| response.infoLog += `〽️ AVG Bitrate between them is ${videoBR / 1000}kbps\n`; | |
| } | |
| else { | |
| response.infoLog += `.\n`; | |
| } | |
| return Math.round(Number(videoBR) / Number(1000)); | |
| } | |
| } catch (err) { | |
| response.infoLog += `\n☒ Failed to get bitrate by mediainfo falling back to ffprobe.\n`; | |
| response.infoLog += `☒ ${err.message} - ${err.stack}\n`; | |
| } | |
| } | |
| } | |
| // If MediaInfo fails somehow fallback to ffprobe - Try two types of tags that might exist | |
| if (file?.ffProbeData?.streams) { | |
| for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { | |
| let stream = file.ffProbeData.streams[i]; | |
| try { | |
| if ('video' !== stream.codec_type.toLowerCase()) { | |
| continue; | |
| } | |
| } catch (err) { | |
| continue; | |
| } | |
| try { | |
| if (stream?.tags?.BPS && Number(stream.tags.BPS) > 0) { | |
| videoBR = Number(stream.tags.BP); | |
| response.infoLog += `☑ Bitrate ${videoBR / 1000}kbps via ffprobe.tags.BPS`; | |
| if (file?.bit_rate && Number(file.bit_rate) > 0) { | |
| response.infoLog += ` & ${file.bit_rate / 1000}kbps via file object.\n`; | |
| videoBR = (videoBR + Number(file.bit_rate)) / 2; | |
| response.infoLog += `〽️ AVG Bitrate between them is ${videoBR / 1000}kbps\n`; | |
| } | |
| else { | |
| response.infoLog += `.\n`; | |
| } | |
| return Math.round(Number(videoBR) / 1000); | |
| } | |
| } catch (err) { | |
| response.infoLog += `☒ ${err.message}\n`; | |
| } | |
| try { | |
| if (stream?.tags && 'BPS-eng' in stream.tags && Number(stream.tags['BPS-eng']) > 0) { | |
| videoBR = Number(stream.tags['BPS-eng']); | |
| response.infoLog += `☑ Bitrate ${videoBR / 1000}kbps via stream.tags.BPS-eng`; | |
| if (file?.bit_rate && Number(file.bit_rate) > 0) { | |
| response.infoLog += ` & ${file.bit_rate / 1000}kbps via file object.\n`; | |
| videoBR = (videoBR + Number(file.bit_rate)) / 2; | |
| response.infoLog += `〽️ AVG Bitrate between them is ${videoBR / 1000}kbps\n`; | |
| } | |
| else { | |
| response.infoLog += `.\n`; | |
| } | |
| return Math.round(Number(videoBR) / 1000); | |
| } | |
| } catch (err) { | |
| response.infoLog += `☒ Failed to get bitrate by ffprobe. falling back to file object.\n`; | |
| response.infoLog += `☒ ${err.message}\n`; | |
| } | |
| } | |
| } | |
| if (file?.bit_rate) { | |
| response.infoLog += `☑ Bitrate ${file.bit_rate / 1000}kbps via file object.`; | |
| return Math.round(Number(file.bit_rate) / 1000); | |
| } | |
| // At this stage non of the media tools were used to generate bitrate info | |
| // return null and force | |
| response.infoLog += `☒ Failed to get accurate bitrate. falling back to estimate bitrate calculation.\n`; | |
| return null; | |
| } | |
| const getVideoStream = function (file) { | |
| if (!file?.ffProbeData.stream) { | |
| return null; | |
| } | |
| for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { | |
| let stream = file.ffProbeData.streams[i]; | |
| let codecType = ''; | |
| try { | |
| codecType = stream.codec_type.toLowerCase() | |
| codecName = stream.codec_name.toLowerCase(); | |
| if ('video' !== codecType || ['png', 'bmp', 'mjpeg'].includes(stream.codec_name)) { | |
| continue; | |
| } | |
| return stream; | |
| } catch (err) { | |
| // err | |
| } | |
| } | |
| return null; | |
| } | |
| const is10BitStream = (stream) => (['high 10', 'main 10'].includes(stream?.profile?.toLowerCase()) || stream?.bits_per_raw_sample === '10' || stream?.pix_fmt === 'yuv420p10le') | |
| const hasEncoder = async ({ ffmpegPath, encoder, inputArgs, filter }) => { | |
| const { exec } = require('child_process'); | |
| let isEnabled = false; | |
| try { | |
| isEnabled = await new Promise((resolve) => { | |
| const command = `${ffmpegPath} ${inputArgs || ''} -f lavfi -i color=c=black:s=256x256:d=1:r=30` | |
| + ` ${filter || ''}` | |
| + ` -c:v ${encoder} -f null /dev/null`; | |
| exec(command, ( | |
| error, | |
| // stdout, | |
| // stderr, | |
| ) => { | |
| if (error) { | |
| resolve(false); | |
| return; | |
| } | |
| resolve(true); | |
| }); | |
| }); | |
| } catch (err) { | |
| // eslint-disable-next-line no-console | |
| console.log(err); | |
| } | |
| return isEnabled; | |
| }; | |
| // credit to UNCode101 for this | |
| const getBestNvencDevice = async ({ response, inputs, nvencDevice }) => { | |
| const { execSync } = require('child_process'); | |
| let gpu_num = -1; | |
| let lowest_gpu_util = 100000; | |
| let result_util = 0; | |
| let gpu_count = -1; | |
| let gpu_names = ''; | |
| const gpus_to_exclude = inputs.exclude_gpus === '' ? [] : inputs.exclude_gpus.split(',').map(Number); | |
| try { | |
| gpu_names = execSync('nvidia-smi --query-gpu=name --format=csv,noheader'); | |
| gpu_names = gpu_names.toString().trim(); | |
| gpu_names = gpu_names.split(/\r?\n/); | |
| /* When nvidia-smi returns an error it contains 'nvidia-smi' in the error | |
| Example: Linux: nvidia-smi: command not found | |
| Windows: 'nvidia-smi' is not recognized as an internal or external command, | |
| operable program or batch file. */ | |
| if (!gpu_names[0].includes('nvidia-smi')) { | |
| gpu_count = gpu_names.length; | |
| } | |
| } catch (error) { | |
| response.infoLog += 'Error in reading nvidia-smi output!\n'; | |
| response.infoLog += `${error.message}\n`; | |
| } | |
| if (gpu_count > 0) { | |
| for (let gpui = 0; gpui < gpu_count; gpui++) { | |
| // Check if GPU # is in GPUs to exclude | |
| if (gpus_to_exclude.includes(gpui)) { | |
| response.infoLog += `GPU ${gpui}: ${gpu_names[gpui]} is in exclusion list, will not be used!\n`; | |
| } else { | |
| try { | |
| const cmd_gpu = `nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i ${gpui}`; | |
| result_util = parseInt(execSync(cmd_gpu), 10); | |
| if (!Number.isNaN(result_util)) { // != "No devices were found") { | |
| response.infoLog += `GPU ${gpui} : Utilization ${result_util}%\n`; | |
| if (result_util < lowest_gpu_util) { | |
| gpu_num = gpui; | |
| lowest_gpu_util = result_util; | |
| } | |
| } | |
| } catch (error) { | |
| response.infoLog += `Error in reading GPU ${gpui} Utilization\nError: ${error}\n`; | |
| } | |
| } | |
| } | |
| } | |
| if (gpu_num >= 0) { | |
| // eslint-disable-next-line no-param-reassign | |
| nvencDevice.inputArgs = `-hwaccel_device ${gpu_num}`; | |
| // eslint-disable-next-line no-param-reassign | |
| nvencDevice.outputArgs = `-gpu ${gpu_num}`; | |
| } | |
| return nvencDevice; | |
| }; | |
| const getEncoder = async ({ response, inputs, otherArguments }) => { | |
| if ( | |
| otherArguments.workerType | |
| && otherArguments.workerType.includes('gpu') | |
| && inputs.try_use_gpu && (inputs.target_codec === 'hevc' || inputs.target_codec === 'h264')) { | |
| const gpuEncoders = [ | |
| { | |
| encoder: 'hevc_nvenc', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'hevc_amf', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'hevc_qsv', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'hevc_videotoolbox', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'h264_nvenc', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'h264_amf', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'h264_qsv', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'h264_videotoolbox', | |
| enabled: false, | |
| }, | |
| { | |
| encoder: 'hevc_vaapi', | |
| inputArgs: '-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi', | |
| enabled: false, | |
| filter: '-vf format=nv12,hwupload', | |
| }, | |
| ]; | |
| const filteredGpuEncoders = gpuEncoders.filter((device) => device.encoder.includes(inputs.target_codec)); | |
| // eslint-disable-next-line no-restricted-syntax | |
| for (const gpuEncoder of filteredGpuEncoders) { | |
| // eslint-disable-next-line no-await-in-loop | |
| gpuEncoder.enabled = await hasEncoder({ | |
| ffmpegPath: otherArguments.ffmpegPath, | |
| encoder: gpuEncoder.encoder, | |
| inputArgs: gpuEncoder.inputArgs, | |
| filter: gpuEncoder.filter, | |
| }); | |
| } | |
| const enabledDevices = gpuEncoders.filter((device) => device.enabled === true); | |
| if (enabledDevices.length > 0) { | |
| if (enabledDevices[0].encoder.includes('nvenc')) { | |
| return getBestNvencDevice({ | |
| response, | |
| inputs, | |
| nvencDevice: enabledDevices[0], | |
| }); | |
| } | |
| return enabledDevices[0]; | |
| } | |
| } | |
| if (inputs.target_codec === 'hevc') { | |
| return { | |
| encoder: 'libx265', | |
| inputArgs: '', | |
| }; | |
| } if (inputs.target_codec === 'h264') { | |
| return { | |
| encoder: 'libx264', | |
| inputArgs: '', | |
| }; | |
| } | |
| return { | |
| encoder: '', | |
| inputArgs: '', | |
| }; | |
| }; | |
| // eslint-disable-next-line no-unused-vars | |
| const plugin = async (file, librarySettings, inputs, otherArguments) => { | |
| const lib = require('../methods/lib')(); | |
| // eslint-disable-next-line no-unused-vars,no-param-reassign | |
| inputs = lib.loadDefaultValues(inputs, details); | |
| const response = { | |
| processFile: false, | |
| preset: '', | |
| handBrakeMode: false, | |
| FFmpegMode: true, | |
| reQueueAfter: true, | |
| infoLog: '', | |
| }; | |
| const encoderProperties = await getEncoder({ response, inputs, otherArguments }); | |
| if (['libx264', 'libx265'].includes(encoderProperties.encoder)) { | |
| throw new Error('☒ This plugin does not support CPU encoding. Please use Tdarr_Plugin_AC_HEVC_CPU instead.'); | |
| } | |
| if ('original' === inputs.container) { | |
| response.container = `.${file.container}`; | |
| } else { | |
| response.container = `.${inputs.container}`; | |
| } | |
| // Check if file is a video. If it isn't then exit plugin. | |
| if ('video' !== file.fileMedium) { | |
| response.infoLog += '☒ File is not a video. \n'; | |
| return response; | |
| } | |
| let duration = 0; | |
| // Get duration in seconds | |
| if (parseFloat(file.ffProbeData?.format?.duration) > 0) { | |
| duration = parseFloat(file.ffProbeData?.format?.duration); | |
| } else if (typeof file.meta.Duration !== 'undefined') { | |
| duration = file.meta.Duration; | |
| } else { | |
| duration = getVideoStream(file).duration; | |
| } | |
| // Set up required variables. | |
| let CPU10 = false; | |
| let extraArguments = ''; | |
| let genpts = ''; | |
| let bitrateSettings = ''; | |
| const allowedCodecs = inputs.allowe_codecs.split(','); | |
| // -- Get Video Real Bitrate via media tools. | |
| let currentBitrate = getVideoStreamBitrate(file, response); | |
| // Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/ | |
| const overallBitrate = Math.round(file.file_size / (duration * 0.0075)); | |
| // -- if media tools fails. fallback to overallBitrate. | |
| if (null === currentBitrate || Number.isNaN(currentBitrate) || currentBitrate <= 0 || !Number.isFinite(currentBitrate)) { | |
| currentBitrate = overallBitrate; | |
| } | |
| if (Number.isNaN(currentBitrate) || currentBitrate <= 0 || !Number.isFinite(currentBitrate)) { | |
| response.infoLog += '☒ Video bitrate could not be calculated. Skipping this plugin. \n'; | |
| return response; | |
| } | |
| const targetBitrate = Math.round(currentBitrate * inputs.target_bitrate_multiplier); | |
| const minimumBitrate = Math.round(targetBitrate * 0.75); | |
| const maximumBitrate = Math.round(targetBitrate * 1.25); | |
| // If Container .ts or .avi set genpts to fix unknown timestamp | |
| if ('ts' === inputs.container || 'avi' === inputs.container) { | |
| genpts = '-fflags +genpts'; | |
| } | |
| // If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculated. | |
| // Cancel plugin completely. | |
| if (0 === targetBitrate) { | |
| response.infoLog += '☒ Target bitrate could not be calculated. Skipping this plugin. \n'; | |
| return response; | |
| } | |
| // Check if inputs.bitrate cutoff has something entered. | |
| // (Entered means user actually wants something to happen, empty would disable this). | |
| // Checks if currentBitrate is below inputs.bitrate_cutoff. | |
| // If so then cancel plugin without touching original files. | |
| if (parseInt(inputs.bitrate_cutoff) >= parseInt(targetBitrate)) { | |
| response.infoLog += `☑ File Target bitrate ${targetBitrate}k is below set cutoff of ${inputs.bitrate_cutoff}k. Skipping this plugin. \n`; | |
| return response; | |
| } | |
| // Check if force_conform option is checked. | |
| // If so then check streams and add any extra parameters required to make file conform with output format. | |
| if (true === inputs.force_conform && inputs.container in conformSpec) { | |
| if ('mkv' === inputs.container) { | |
| extraArguments += '-map -0:d '; | |
| } | |
| for (let i = 0; i < file.ffProbeData.streams.length; i++) { | |
| try { | |
| let streamCodec = file.ffProbeData.streams[i].codec_name.toLowerCase(); | |
| if (conformSpec[inputs.container].includes(streamCodec)) { | |
| extraArguments += `-map -0:${i} `; | |
| } | |
| } catch (err) { | |
| response.infoLog += `☒ ${err.message}\n`; | |
| // Error | |
| } | |
| } | |
| } | |
| // Check if 10bit variable is true. | |
| if (true === inputs.enable_10bit || is10BitStream(getVideoStream(file))) { | |
| // If set to true then add 10bit argument | |
| // extraArguments += '-pix_fmt p010le '; | |
| extraArguments += '-profile:v main10 '; | |
| // -- vaapi has problems with pix_fmt | |
| if ('hevc_vaapi' !== encoderProperties.encoder) { | |
| extraArguments += '-pix_fmt p010le '; | |
| } | |
| response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n'; | |
| response.infoLog += `Reason for enabling 10 bit is : ${true === inputs.enable_10bit ? 'User enabled in profile' : 'Due to stream being 10bit encoded.'}\n`; | |
| } | |
| // Check if b frame variable is true. | |
| if (bframeSupport.includes(encoderProperties.encoder) && true === inputs.bframes_enabled) { | |
| // If set to true then add b frames argument | |
| extraArguments += `-bf ${inputs.bframes_value} `; | |
| } | |
| // -- Code that relates to streams handling.. | |
| for (let i = 0; i < file.ffProbeData.streams.length; i++) { | |
| let stream = file.ffProbeData.streams[i]; | |
| let codecType = ''; | |
| let codecName = ''; | |
| try { | |
| codecType = stream.codec_type.toLowerCase(); | |
| codecName = stream.codec_name.toLowerCase(); | |
| } catch (err) { | |
| response.infoLog += `☒ ${err.message}\n`; | |
| continue; | |
| } | |
| if ('mkv' === inputs.container) { | |
| if ('data' === codecType) { | |
| extraArguments += `-map -0:${stream.index} `; | |
| response.infoLog += `☒ Removing Data stream ${stream.index} From final output as ${inputs.container} doesn't support this type.\n`; | |
| } | |
| if ('mov_text' === codecName) { | |
| extraArguments += `-c:${stream.index} text `; | |
| response.infoLog += `☒ Converting ${codecName} to SRT as ${inputs.container} doesn't support this codec.\n`; | |
| } | |
| } | |
| if ('mp4' === inputs.container) { | |
| if ('subrip' === codecName) { | |
| extraArguments += `-c:${stream.index} mov_text `; | |
| response.infoLog += `☒ Converting ${codecName} to mov_text as ${inputs.container} doesn't support this codec.\n`; | |
| } | |
| } | |
| } | |
| // Go through each stream in the file. | |
| for (let i = 0; i < file.ffProbeData.streams.length; i++) { | |
| let stream = file.ffProbeData.streams[i]; | |
| let codecType = ''; | |
| let codecName = ''; | |
| try { | |
| codecType = stream.codec_type.toLowerCase(); | |
| codecName = stream.codec_name.toLowerCase(); | |
| } catch (err) { | |
| response.infoLog += `☒ ${err.message}\n`; | |
| continue; | |
| } | |
| if ('video' !== codecType) { | |
| continue; | |
| } | |
| // 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 (true === ['mjpeg', 'png', 'bmp'].includes(codecName)) { | |
| response.infoLog += `☑ Removing ${codecName}:${stream.index} as it's messes with encoding.\n`; | |
| extraArguments += `-map -0:${stream.index} `; | |
| } | |
| if (inputs.target_codec === codecName || allowedCodecs.includes(codecName)) { | |
| // check if container is in the required format. | |
| if (file.container !== inputs.container) { | |
| response.infoLog += `☒ File is in ${codecName} but ` | |
| + `is not in ${inputs.container} container. Remuxing. \n`; | |
| response.preset = `<io> -map 0 -c copy ${extraArguments}`; | |
| response.processFile = true; | |
| return response; | |
| } | |
| response.infoLog += `☑ File is already ${codecName} and in ${inputs.container}. \n`; | |
| return response; | |
| } | |
| // Check if video stream is HDR or 10bit | |
| if ('hevc' === inputs.target_codec && is10BitStream(stream)) { | |
| CPU10 = true; | |
| } | |
| } | |
| // Set bitrateSettings variable using bitrate information calulcated earlier. | |
| bitrateSettings = `-b:v ${targetBitrate}k -minrate ${minimumBitrate}k ` | |
| + `-maxrate ${maximumBitrate}k -bufsize ${currentBitrate}k`; | |
| // Print to infoLog information around file & bitrate settings. | |
| response.infoLog += `Container for output selected as ${inputs.container}. \n`; | |
| response.infoLog += `Current bitrate = ${currentBitrate}kbps \n`; | |
| response.infoLog += 'Bitrate settings: \n'; | |
| response.infoLog += `Target = ${targetBitrate}kbps \n`; | |
| response.infoLog += `Minimum = ${minimumBitrate}kbps \n`; | |
| response.infoLog += `Maximum = ${maximumBitrate}kbps \n`; | |
| if (encoderProperties.encoder.includes('nvenc')) { | |
| if (file.video_codec_name === 'h263') { | |
| response.preset = '-c:v h263_cuvid'; | |
| } else if (file.video_codec_name === 'h264' && CPU10 === false) { | |
| response.preset = '-c:v h264_cuvid'; | |
| } else if (file.video_codec_name === 'mjpeg') { | |
| response.preset = '-c:v mjpeg_cuvid'; | |
| } else if (file.video_codec_name === 'mpeg1') { | |
| response.preset = '-c:v mpeg1_cuvid'; | |
| } else if (file.video_codec_name === 'mpeg2') { | |
| response.preset = '-c:v mpeg2_cuvid'; | |
| } else if (file.video_codec_name === 'mpeg4') { | |
| response.preset = '-c:v mpeg4_cuvid'; | |
| } else if (file.video_codec_name === 'vc1') { | |
| response.preset = '-c:v vc1_cuvid'; | |
| } else if (file.video_codec_name === 'vp8') { | |
| response.preset = '-c:v vp8_cuvid'; | |
| } | |
| } | |
| const vEncode = `-cq:v 19 ${bitrateSettings}`; | |
| response.preset += ` ${encoderProperties.inputArgs ? encoderProperties.inputArgs : ''} ${genpts} <io>` | |
| + ` -map 0 -c copy -c:v ${encoderProperties.encoder}` | |
| + ` ${encoderProperties.outputArgs ? encoderProperties.outputArgs : ''}` | |
| + ` ${vEncode}` | |
| + ` -spatial_aq:v 1 -rc-lookahead:v 32 -max_muxing_queue_size 9999 ${extraArguments}`; | |
| response.processFile = true; | |
| response.infoLog += `☒ File is not in ${inputs.target_codec}. Transcoding. \n`; | |
| return response; | |
| } | |
| module.exports.details = details; | |
| module.exports.plugin = async (file, librarySettings, inputs, otherArguments) => { | |
| try { | |
| return plugin(file, librarySettings, inputs, otherArguments); | |
| } catch (err) { | |
| return { | |
| processFile: false, | |
| infoLog: `ERROR: ${err.message}\n${JSON.stringify(err.stack, null, 2)}\n`, | |
| }; | |
| } | |
| }; |
Author
@robson90 Hi, i have updated the gist with the last version that i used pre-switching to the new plugins system. Not sure if it still works. please try the new code and see
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, Im getting an error on some files:
When opening the file with "MediaInfo" ist does show a bitrate

You have any idea, how to fix this ?