diff --git a/.eslintrc.json b/.eslintrc.json index 2b6b52b70..d321d960d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,31 +1,87 @@ { - "env": { - "commonjs": true, - "es6": true, - "node": true - }, - "extends": [ - "airbnb-base" + "settings": { + "import/resolver": { + "node": { + "extensions": [ + ".js", + ".jsx", + ".ts", + ".tsx" + ] + } + } + }, + "env": { + "commonjs": true, + "es6": true, + "node": true + }, + "extends": [ + "airbnb-base", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": [ + "prefer-arrow-functions" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018, + "project": [ + "./tsconfig.eslint.json" + ] + }, + "rules": { + "func-style": [ + "error", + "expression" + ], + "prefer-arrow-functions/prefer-arrow-functions": [ + "warn", + { + "classPropertiesAllowed": false, + "disallowPrototype": false, + "returnStyle": "unchanged", + "singleReturnOnly": false + } + ], + "require-await":["error"], + "global-require": 0, + "no-void": 0, + "@typescript-eslint/no-floating-promises": [ + "error" ], - "globals": { - "Atomics": "readonly", - "SharedArrayBuffer": "readonly" - }, - "parserOptions": { - "ecmaVersion": 2020 - }, - "rules": { - "global-require":0, - "class-methods-use-this": 0, - "no-case-declarations": 0, - "camelcase": 0, - "jsx-a11y/click-events-have-key-events": 0, - "no-underscore-dangle": ["error", { "allow": ["_id"] }], - "max-len": [ - "error", - { - "code": 120 - } + "@typescript-eslint/no-var-requires": 0, + "class-methods-use-this": 0, + "no-case-declarations": 0, + "camelcase": 0, + "jsx-a11y/click-events-have-key-events": 0, + "no-underscore-dangle": [ + "error", + { + "allow": [ + "_id" ] } + ], + "max-len": [ + "error", + { + "code": 120 + } + ], + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ] + } } \ No newline at end of file diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index c85eb73f7..3cf9b0fe0 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -8,7 +8,7 @@ jobs: build: strategy: matrix: - node-version: [16.x] + node-version: [18.x] os: [ ["ubuntu-20.04"], @@ -28,7 +28,8 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - run: npm i + - run: npm i + - run: npm i -g typescript && tsc - run: npm run checkPlugins - run: npm run lint - run: npm run test diff --git a/.gitignore b/.gitignore index 32d518cec..1c4bf3485 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ ### VS Code ### .vscode/ + +/Local +/FlowPluginsTs/LocalFlowPlugins +/FlowPlugins/LocalFlowPlugins \ No newline at end of file diff --git a/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js b/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js index 79b766b3e..ef3bacc02 100644 --- a/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js +++ b/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js @@ -65,10 +65,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js b/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js index 05e6f6bcb..f9637e9b1 100644 --- a/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js +++ b/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js @@ -153,10 +153,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js b/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js index 995603e91..a32bce816 100644 --- a/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js +++ b/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js @@ -75,10 +75,10 @@ HandBrake examples: ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js b/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js index 5925da320..c0b3594bf 100644 --- a/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js +++ b/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js @@ -64,10 +64,10 @@ If no specified language track exists, the best untagged/undefined stream will b ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js b/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js index 21881b229..8bbb5bb0d 100644 --- a/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js +++ b/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js @@ -83,10 +83,10 @@ The default order is suitable for most people. ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js b/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js index 4b286f822..b64dd7b8b 100644 --- a/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js +++ b/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js @@ -30,10 +30,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js b/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js new file mode 100644 index 000000000..4c8068990 --- /dev/null +++ b/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js @@ -0,0 +1,104 @@ +const details = () => ({ + id: 'Tdarr_Plugin_00td_action_remove_stream_by_specified_property', + Stage: 'Pre-processing', + Name: 'Remove streams by specified property', + Type: 'Video', + Operation: 'Transcode', + Description: ` + This plugin removes streams based on the specified property. + `, + Version: '1.00', + Tags: 'action', + Inputs: [ + { + name: 'propertyToCheck', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + `Enter one stream property to check. + + \\nExample:\\n + codec_name + `, + }, + { + name: 'valuesToRemove', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + `Enter values of the property above to remove. For example, if removing by codec_name, could enter ac3,aac: + + \\nExample:\\n + ac3,aac + `, + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + const response = { + processFile: false, + preset: '', + container: `.${file.container}`, + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: '', + }; + + if (inputs.propertyToCheck.trim() === '') { + response.infoLog += 'No input propertyToCheck entered in plugin, skipping \n'; + return response; + } + + const propertyToCheck = inputs.propertyToCheck.trim(); + + if (inputs.valuesToRemove.trim() === '') { + response.infoLog += 'No input valuesToRemove entered in plugin, skipping \n'; + return response; + } + + const valuesToRemove = inputs.valuesToRemove.trim().split(','); + + response.preset += ', -map 0 -c copy -max_muxing_queue_size 9999'; + + try { + let streamToRemove = false; + for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { + try { + if (valuesToRemove.includes(String(file.ffProbeData.streams[i][propertyToCheck]))) { + response.preset += ` -map -0:${i} `; + response.infoLog += ` Removing stream ${i} which is has ${propertyToCheck}` + + ` of ${file.ffProbeData.streams[i][propertyToCheck]} \n`; + streamToRemove = true; + } + } catch (err) { + response.infoLog += ` Error reading stream ${i} ${propertyToCheck} \n`; + } + } + + if (streamToRemove === true) { + response.processFile = true; + response.infoLog += ' Files has streams which need to be removed, processing \n'; + } else { + response.infoLog += ' Files does not have streams which need to be removed \n'; + } + } catch (err) { + response.infoLog += ` Error checking streams:${err}`; + } + + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_00td_action_remux_container.js b/Community/Tdarr_Plugin_00td_action_remux_container.js index d43cf8481..0427bdaa3 100644 --- a/Community/Tdarr_Plugin_00td_action_remux_container.js +++ b/Community/Tdarr_Plugin_00td_action_remux_container.js @@ -25,10 +25,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js b/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js index 4d5966871..9d40de96e 100644 --- a/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js +++ b/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js @@ -36,10 +36,10 @@ into the specified codec. Bitrate and channel count are kept the same. ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_action_transcode.js b/Community/Tdarr_Plugin_00td_action_transcode.js new file mode 100644 index 000000000..978c6f515 --- /dev/null +++ b/Community/Tdarr_Plugin_00td_action_transcode.js @@ -0,0 +1,609 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () => ({ + id: 'Tdarr_Plugin_00td_action_transcode', + 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: '3.1', + 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', + 'avi', + 'ts', + 'original', + ], + }, + tooltip: `Specify output container of file. Use 'original' wihout quotes 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. + \\nExample:\\n + true + + \\nExample:\\n + false`, + }, + { + name: 'bframes_enabled', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: `Specify if b frames should be used. + \\n Using B frames should decrease file sizes but are only supported on newer GPUs. + \\n Default is false. + \\nExample:\\n + true + + \\nExample:\\n + 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/subrip/timed_id3 for MP4. + \\n Drop data streams/mov_text/eia_608/timed_id3 for MKV. + \\n Default is false. + \\nExample:\\n + true + + \\nExample:\\n + 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`, + }, + ], +}); + +const bframeSupport = [ + 'hevc_nvenc', + 'h264_nvenc', +]; + +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 = ({ + 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; + } + + 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_vaapi', + inputArgs: '-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi', + enabled: false, + filter: '-vf format=nv12,hwupload', + }, + { + 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, + }, + ]; + + 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 @typescript-eslint/no-unused-vars +const plugin = async (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/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 (inputs.container === 'original') { + response.container = `.${file.container}`; + } else { + response.container = `.${inputs.container}`; + } + + // Check if file is a video. If it isn't then exit plugin. + if (file.fileMedium !== 'video') { + 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 = file.ffProbeData.streams[0].duration; + } + + // Set up required variables. + let videoIdx = 0; + let CPU10 = false; + let extraArguments = ''; + let genpts = ''; + let bitrateSettings = ''; + + // Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/ + + const currentBitrate = (file.file_size * 1024 * 1024 * 8) / duration; + // Use the same calculation used for currentBitrate but divide it in half to get targetBitrate. + // Logic of h265 can be half the bitrate as h264 without losing quality. + + const targetBitrate = currentBitrate * inputs.target_bitrate_multiplier; + // Allow some leeway under and over the targetBitrate. + + const minimumBitrate = (targetBitrate * 0.7); + + const maximumBitrate = (targetBitrate * 1.3); + + // If Container .ts or .avi set genpts to fix unknown timestamp + if (inputs.container === 'ts' || inputs.container === 'avi') { + genpts = '-fflags +genpts'; + } + + // If targetBitrate comes out as 0 then something has gone wrong and bitrates could not be calculated. + // Cancel plugin completely. + if (targetBitrate === 0) { + 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 (currentBitrate <= inputs.bitrate_cutoff) { + response.infoLog += `Current bitrate is below set cutoff of ${inputs.bitrate_cutoff}. Cancelling 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 (inputs.force_conform === true) { + if (inputs.container === 'mkv') { + extraArguments += '-map -0:d '; + for (let i = 0; i < file.ffProbeData.streams.length; i++) { + try { + if ( + file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'mov_text' + || file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'eia_608' + || file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'timed_id3' + ) { + extraArguments += `-map -0:${i} `; + } + } catch (err) { + // Error + } + } + } + if (inputs.container === 'mp4') { + for (let i = 0; i < file.ffProbeData.streams.length; i++) { + try { + if ( + file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'hdmv_pgs_subtitle' + || file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'eia_608' + || file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'subrip' + || file.ffProbeData.streams[i].codec_name + .toLowerCase() === 'timed_id3' + ) { + extraArguments += `-map -0:${i} `; + } + } catch (err) { + // Error + } + } + } + } + + // Check if 10bit variable is true. + if (inputs.enable_10bit === true) { + // If set to true then add 10bit argument + extraArguments += '-pix_fmt p010le '; + } + + // Check if b frame variable is true. + if (bframeSupport.includes(encoderProperties.encoder) && inputs.bframes_enabled === true) { + // If set to true then add b frames argument + extraArguments += `-bf ${inputs.bframes_value} `; + } + + // Go through each stream in the file. + for (let i = 0; i < file.ffProbeData.streams.length; i++) { + // Check if stream is a video. + let codec_type = ''; + try { + codec_type = file.ffProbeData.streams[i].codec_type.toLowerCase(); + } catch (err) { + // err + } + if (codec_type === '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') { + extraArguments += `-map -v:${videoIdx} `; + } + // Check if codec of stream is hevc or vp9 AND check if file.container matches inputs.container. + // If so nothing for plugin to do. + if ( + inputs.target_codec === file.ffProbeData.streams[i].codec_name + && file.container === inputs.container + ) { + response.infoLog += `File is already ${inputs.target_codec} and in ${inputs.container}. \n`; + return response; + } + // Check if codec of stream is hevc or vp9 + // AND check if file.container does NOT match inputs.container. + // If so remux file. + if ( + + inputs.target_codec === file.ffProbeData.streams[i].codec_name + + && file.container !== inputs.container + ) { + response.infoLog += `File is in ${inputs.target_codec} but ` + + `is not in ${inputs.container} container. Remuxing. \n`; + response.preset = ` -map 0 -c copy ${extraArguments}`; + response.processFile = true; + return response; + } + + // Check if video stream is HDR or 10bit + if ( + inputs.target_codec === 'hevc' + && (file.ffProbeData.streams[i].profile === 'High 10' + || file.ffProbeData.streams[i].bits_per_raw_sample === '10') + ) { + CPU10 = true; + } + + // Increment videoIdx. + videoIdx += 1; + } + } + + // Set bitrateSettings variable using bitrate information calulcated earlier. + bitrateSettings = `-b:v ${targetBitrate} -minrate ${minimumBitrate} ` + + `-maxrate ${maximumBitrate} -bufsize ${currentBitrate}`; + // Print to infoLog information around file & bitrate settings. + response.infoLog += `Container for output selected as ${inputs.container}. \n`; + response.infoLog += `Current bitrate = ${currentBitrate} \n`; + response.infoLog += 'Bitrate settings: \n'; + response.infoLog += `Target = ${targetBitrate} \n`; + response.infoLog += `Minimum = ${minimumBitrate} \n`; + response.infoLog += `Maximum = ${maximumBitrate} \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}` + + ` -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 = plugin; diff --git a/Community/Tdarr_Plugin_00td_filter_bit_depth.js b/Community/Tdarr_Plugin_00td_filter_bit_depth.js index b479a0ff9..576568aac 100644 --- a/Community/Tdarr_Plugin_00td_filter_bit_depth.js +++ b/Community/Tdarr_Plugin_00td_filter_bit_depth.js @@ -50,10 +50,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js b/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js index 942de91cf..599f6cef2 100644 --- a/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js +++ b/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js @@ -12,10 +12,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: true, diff --git a/Community/Tdarr_Plugin_00td_filter_by_bitrate.js b/Community/Tdarr_Plugin_00td_filter_by_bitrate.js index 7219b51c2..89d4777e7 100644 --- a/Community/Tdarr_Plugin_00td_filter_by_bitrate.js +++ b/Community/Tdarr_Plugin_00td_filter_by_bitrate.js @@ -31,10 +31,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_by_codec.js b/Community/Tdarr_Plugin_00td_filter_by_codec.js index 2f2d6faf8..3e544062c 100644 --- a/Community/Tdarr_Plugin_00td_filter_by_codec.js +++ b/Community/Tdarr_Plugin_00td_filter_by_codec.js @@ -31,10 +31,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js b/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js index a24a83d97..80a145c14 100644 --- a/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js +++ b/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js @@ -33,10 +33,10 @@ Leave blank if using codecTagStringsToProcess`, ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_by_file_property.js b/Community/Tdarr_Plugin_00td_filter_by_file_property.js new file mode 100644 index 000000000..e0ae80c3f --- /dev/null +++ b/Community/Tdarr_Plugin_00td_filter_by_file_property.js @@ -0,0 +1,217 @@ +const details = () => ({ + id: 'Tdarr_Plugin_00td_filter_by_file_property', + Stage: 'Pre-processing', + Name: 'Filter by file property', + Type: 'Video', + Operation: 'Filter', + Description: `Filter by a top level file property. + For example, container, video_resolution, video_codec_name, fileMedium. + Click on a file name on the Tdarr tab or Search tab to see top-level file properties. + `, + Version: '1.00', + Tags: 'filter', + Inputs: [ + { + name: 'propertyName', + type: 'string', + defaultValue: 'container', + inputUI: { + type: 'text', + }, + tooltip: + 'Enter the file property to check', + }, + { + name: 'propertyValues', + type: 'string', + defaultValue: 'mkv,mp4', + inputUI: { + type: 'text', + }, + tooltip: + 'Enter a comma separated list of values to check for.', + }, + { + name: 'condition', + type: 'string', + defaultValue: '==', + inputUI: { + type: 'dropdown', + options: [ + '==', + '!=', + '>', + '>=', + '<', + '<=', + 'includes', + 'not includes', + ], + }, + tooltip: + 'Specify the condition to use when comparing the property value to the input value. \\n' + + ' The property value is on the left hand side of the comparison. For example \\n' + + ' property value includes input \\n' + + ' property value >= input \\n', + }, + { + name: 'continueIfPropertyFound', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: + 'Specify whether to continue the plugin stack if the property is found.', + }, + ], +}); + +const conditionMet = (response, inputsArr, value, condition) => { + for (let j = 0; j < inputsArr.length; j += 1) { + try { + let v = value; + let i = inputsArr[j]; + + if ( + condition === '>' + || condition === '>=' + || condition === '<' + || condition === '<=' + + ) { + v = Number(value); + i = Number(inputsArr[j]); + } else if ( + condition === '==' + || condition === '!=' + || condition === 'includes' + || condition === 'not includes' + ) { + v = String(value); + i = String(inputsArr[j]); + } + + response.infoLog += ` Checking property value of ${v} ${condition} input value of ${i} \n`; + + switch (condition) { + case '==': + if (v === i) { + return true; + } + break; + case '!=': + if (v !== i) { + return true; + } + break; + case '>': + if (v > i) { + return true; + } + break; + case '>=': + if (v >= i) { + return true; + } + break; + case '<': + if (v < i) { + return true; + } + break; + + case '<=': + if (v <= i) { + return true; + } + break; + case 'includes': + if (v.includes(i)) { + return true; + } + break; + case 'not includes': + if (!v.includes(i)) { + return true; + } + break; + default: + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + } + + return false; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + const response = { + processFile: false, + infoLog: '', + }; + + if (inputs.propertyName.trim() === '') { + response.infoLog += 'No input propertyName entered in plugin, skipping \n'; + return response; + } + + const propertyName = inputs.propertyName.trim(); + + if (inputs.propertyValues.trim() === '') { + response.infoLog += 'No input propertyValues entered in plugin, skipping \n'; + return response; + } + + // legacy + if (inputs.exactMatch === false && inputs.condition === '==') { + // eslint-disable-next-line no-param-reassign + inputs.condition = 'includes'; + } + + const propertyValues = inputs.propertyValues.trim().split(','); + + try { + const isConditionMet = conditionMet(response, propertyValues, file[propertyName], inputs.condition); + + response.infoLog += ` isConditionMet: ${isConditionMet} \n`; + response.infoLog += ` continueIfPropertyFound: ${inputs.continueIfPropertyFound} \n`; + if (inputs.continueIfPropertyFound === true) { + if (isConditionMet === true) { + response.processFile = true; + response.infoLog += 'Continuing to next plugin \n'; + } else { + response.processFile = false; + response.infoLog += 'Breaking out of stack \n'; + } + } else if (inputs.continueIfPropertyFound === false) { + if (isConditionMet === true) { + response.processFile = false; + response.infoLog += 'Breaking out of stack \n'; + } else { + response.processFile = true; + response.infoLog += 'Continuing to next plugin \n'; + } + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + response.infoLog += err; + response.processFile = false; + } + + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_00td_filter_by_resolution.js b/Community/Tdarr_Plugin_00td_filter_by_resolution.js index c63c43524..81fcd8064 100644 --- a/Community/Tdarr_Plugin_00td_filter_by_resolution.js +++ b/Community/Tdarr_Plugin_00td_filter_by_resolution.js @@ -35,10 +35,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_by_size.js b/Community/Tdarr_Plugin_00td_filter_by_size.js index 2e9ae6773..bc750be4e 100644 --- a/Community/Tdarr_Plugin_00td_filter_by_size.js +++ b/Community/Tdarr_Plugin_00td_filter_by_size.js @@ -33,10 +33,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js b/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js new file mode 100644 index 000000000..ca17d0641 --- /dev/null +++ b/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js @@ -0,0 +1,135 @@ +const details = () => ({ + id: 'Tdarr_Plugin_00td_filter_by_stream_tag', + Stage: 'Pre-processing', + Name: 'Filter by stream tag', + Type: 'Video', + Operation: 'Filter', + Description: `Filter by stream tag value. Will check all streams. Useful for when e.g. trying to force transcoding + from hevc to hevc. In this circumstance, newly transcoded files can have say COPYRIGHT tag set to 'processed' using + '-metadata:s:v:0 COPYRIGHT=processed' and this filter will then break out of the plugin stack cycling + after transcoding.`, + Version: '1.00', + Tags: 'filter', + Inputs: [ + { + name: 'tagName', + type: 'string', + defaultValue: 'COPYRIGHT', + inputUI: { + type: 'text', + }, + tooltip: + 'Enter the stream tag to check', + }, + { + name: 'tagValues', + type: 'string', + defaultValue: 'processed', + inputUI: { + type: 'text', + }, + tooltip: + 'Enter a comma separated list of tag values to check for.', + }, + { + name: 'exactMatch', + type: 'boolean', + defaultValue: true, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: + 'Specify true if the property value must be an exact match,' + + ' false if the property value must contain the value.', + }, + { + name: 'continueIfTagFound', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: + 'Specify whether to continue the plugin stack if the tag is found.', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + const { strHasValue } = require('../methods/utils'); + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + const response = { + processFile: false, + infoLog: '', + }; + + if (inputs.tagName.trim() === '') { + response.infoLog += 'No input tagName entered in plugin, skipping \n'; + return response; + } + + const tagName = inputs.tagName.trim(); + + if (inputs.tagValues.trim() === '') { + response.infoLog += 'No input tagValues entered in plugin, skipping \n'; + return response; + } + + const tagValues = inputs.tagValues.trim().split(','); + + let streamContainsTag = false; + try { + try { + for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { + if (strHasValue(tagValues, file.ffProbeData.streams[i]?.tags[tagName], inputs.exactMatch)) { + streamContainsTag = true; + break; + } + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + + const message = `A stream with tag name ${tagName} containing ${tagValues.join(',')} has`; + + if (inputs.continueIfTagFound === true) { + if (streamContainsTag === true) { + response.processFile = true; + response.infoLog += `${message} been found, continuing to next plugin \n`; + } else { + response.processFile = false; + response.infoLog += `${message} not been found, breaking out of stack \n`; + } + } else if (inputs.continueIfTagFound === false) { + if (streamContainsTag === true) { + response.processFile = false; + response.infoLog += `${message} been found, breaking out of stack \n`; + } else { + response.processFile = true; + response.infoLog += `${message} not been found, continuing to next plugin \n`; + } + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + response.infoLog += err; + response.processFile = false; + } + + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js b/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js index e69905014..a12cae60e 100644 --- a/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js +++ b/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js @@ -11,11 +11,11 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object diff --git a/Community/Tdarr_Plugin_075a_Transcode_Customisable.js b/Community/Tdarr_Plugin_075a_Transcode_Customisable.js index 7a144d1d3..887b01cef 100644 --- a/Community/Tdarr_Plugin_075a_Transcode_Customisable.js +++ b/Community/Tdarr_Plugin_075a_Transcode_Customisable.js @@ -150,11 +150,11 @@ const details = () => ({ } ); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js b/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js index 27699a7ee..add16c4ea 100644 --- a/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js +++ b/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js b/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js index 565926daf..f3ddf6896 100644 --- a/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js +++ b/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js b/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js index c17a3a7cb..d6d0bff3f 100644 --- a/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js +++ b/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_076a_re_order_audio_streams.js b/Community/Tdarr_Plugin_076a_re_order_audio_streams.js index 8b3ecc74d..4eb510434 100644 --- a/Community/Tdarr_Plugin_076a_re_order_audio_streams.js +++ b/Community/Tdarr_Plugin_076a_re_order_audio_streams.js @@ -41,11 +41,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js b/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js index 65a3b86a9..0a730246b 100644 --- a/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js +++ b/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js @@ -41,11 +41,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js b/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js index 086dadd82..ba6d7a291 100644 --- a/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js +++ b/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js @@ -55,11 +55,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_078d_Output_embedded_subs_to_SRT_and_remove.js b/Community/Tdarr_Plugin_078d_Output_embedded_subs_to_SRT_and_remove.js index 5a868a508..f83c78dd9 100644 --- a/Community/Tdarr_Plugin_078d_Output_embedded_subs_to_SRT_and_remove.js +++ b/Community/Tdarr_Plugin_078d_Output_embedded_subs_to_SRT_and_remove.js @@ -14,11 +14,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object at some point in the function else plugin will fail. diff --git a/Community/Tdarr_Plugin_43az_add_to_radarr.js b/Community/Tdarr_Plugin_43az_add_to_radarr.js index 8129e0919..7b6715875 100644 --- a/Community/Tdarr_Plugin_43az_add_to_radarr.js +++ b/Community/Tdarr_Plugin_43az_add_to_radarr.js @@ -58,11 +58,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const request = require('request'); const IP = inputs.server_ip; diff --git a/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js b/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js index a52d1885b..61e444684 100644 --- a/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js +++ b/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js @@ -109,7 +109,7 @@ const response = { }; // var response // Finds the first video stream and populates some useful variables -function getMediaInfo(file) { +const getMediaInfo = (file) => { let videoIdx = -1; for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { @@ -128,12 +128,12 @@ function getMediaInfo(file) { } } MediaInfo.overallBR = file.mediaInfo.track[0].OverallBitRate; -} // end getMediaInfo() +}; // end getMediaInfo() -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); if (file.fileMedium !== 'video') { diff --git a/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js b/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js index e8793e6c6..3aadf9f31 100644 --- a/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js +++ b/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js @@ -474,11 +474,11 @@ function buildVideoConfiguration(inputs, file, logger) { } //#endregion -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { container: ".mkv", 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 e52695c49..7a62b7cd5 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 @@ -69,11 +69,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); const os = require('os'); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { container: '.mkv', @@ -172,7 +172,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // 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') { + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) * 0.0166667; + } else if (typeof file.meta.Duration !== 'undefined') { duration = file.meta.Duration * 0.0166667; } else { duration = file.ffProbeData.streams[0].duration * 0.0166667; diff --git a/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js b/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js index 0c5a59fac..df4f6676c 100644 --- a/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js +++ b/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js @@ -26,10 +26,10 @@ const details = () => ({ module.exports.details = details; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { // 320K selected over 384k intentionally diff --git a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js index 2b685a3c9..91421065b 100644 --- a/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js +++ b/Community/Tdarr_Plugin_JB69_JBHEVCQSV_MinimalFile.js @@ -212,7 +212,7 @@ how it works **this does a lot** and is 1 of 2 routines you should to run **Part }], }); -function findMediaInfoItem(file, index) { +const findMediaInfoItem = (file, index) => { let currMIOrder = -1; const strStreamType = file.ffProbeData.streams[index].codec_type.toLowerCase(); @@ -230,13 +230,13 @@ function findMediaInfoItem(file, index) { } } return -1; -} +}; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { // eslint-disable-next-line global-require const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { @@ -396,8 +396,15 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { streamBR = file.mediaInfo.track[MILoc].extra.FromStats_BitRate * 1; } + let duration = 0; + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration); + } else { + duration = file.meta.Duration; + } + response.infoLog - += `Video stream ${i}:${Math.floor(file.meta.Duration / 60)}:` + += `Video stream ${i}:${Math.floor(duration / 60)}:` + `${file.ffProbeData.streams[i].codec_name}${(bolSource10bit) ? '(10)' : ''}`; response.infoLog += `:${streamWidth}x${streamHeight}x${streamFPS}:${streamBR}bps \n`; diff --git a/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js b/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js index 50277de43..736c24a98 100644 --- a/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js +++ b/Community/Tdarr_Plugin_JB69_JBHEVCQSZ_PostFix.js @@ -163,11 +163,11 @@ const details = () => { } } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { @@ -269,7 +269,14 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { var intChapNum = 0; var strChapNum = ""; - for (var i = 0; i < file.meta.Duration; i += chapterlengthlong) { + let duration = 0; + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) + } else { + duration = file.meta.Duration + } + + for (var i = 0; i < duration; i += chapterlengthlong) { intChapNum += 1; strChapNum = String(intChapNum).padStart(2, '0'); @@ -283,7 +290,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { intChapNum += 1; strChapNum = String(intChapNum).padStart(2, "0"); - var timeString = new Date((Math.floor(file.meta.Duration) - 1) * 1000).toISOString().substr(11, 12); + + var timeString = new Date((Math.floor(duration) - 1) * 1000).toISOString().substr(11, 12); strChapterFile += "CHAPTER" + strChapNum + "=" + timeString + "\n"; strChapterFile += "CHAPTER" + strChapNum + "NAME=CHAPTER " + intChapNum + "\n"; diff --git a/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js b/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js index a8e47dd68..7b655fe63 100644 --- a/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js +++ b/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js @@ -19,14 +19,17 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: `Specify output container of file + tooltip: `Specify output container of file. Use 'original' wihout quotes to keep original container. \\n Ensure that all stream types you may have are supported by your chosen container. \\n mkv is recommended. \\nExample:\\n mkv \\nExample:\\n - mp4`, + mp4 + + \\nExample:\\n + original`, }, { name: 'bitrate_cutoff', @@ -106,10 +109,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -128,7 +131,14 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { response.processFile = false; return response; } - response.container = `.${inputs.container}`; + + if (inputs.container === 'original') { + // eslint-disable-next-line no-param-reassign + inputs.container = `${file.container}`; + response.container = `.${file.container}`; + } else { + response.container = `.${inputs.container}`; + } // Check if file is a video. If it isn't then exit plugin. if (file.fileMedium !== 'video') { @@ -139,7 +149,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // 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') { + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) * 0.0166667; + } else if (typeof file.meta.Duration !== 'undefined') { duration = file.meta.Duration * 0.0166667; } else { duration = file.ffProbeData.streams[0].duration * 0.0166667; diff --git a/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js b/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js index 6e68585c0..8d4d579aa 100644 --- a/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js +++ b/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js @@ -18,14 +18,17 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: `Specify output container of file. + tooltip: `Specify output container of file. Use 'original' wihout quotes to keep original container. \\n Ensure that all stream types you may have are supported by your chosen container. \\n mkv is recommended. \\nExample:\\n mkv \\nExample:\\n - mp4`, + mp4 + + \\nExample:\\n + original`, }, { name: 'bitrate_cutoff', @@ -85,10 +88,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -107,7 +110,14 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { response.processFile = false; return response; } - response.container = `.${inputs.container}`; + + if (inputs.container === 'original') { + // eslint-disable-next-line no-param-reassign + inputs.container = `${file.container}`; + response.container = `.${file.container}`; + } else { + response.container = `.${inputs.container}`; + } // Check if file is a video. If it isn't then exit plugin. if (file.fileMedium !== 'video') { @@ -118,7 +128,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // 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') { + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) * 0.0166667; + } else if (typeof file.meta.Duration !== 'undefined') { duration = file.meta.Duration * 0.0166667; } else { duration = file.ffProbeData.streams[0].duration * 0.0166667; diff --git a/Community/Tdarr_Plugin_MC93_Migz1Remux.js b/Community/Tdarr_Plugin_MC93_Migz1Remux.js index 37eed73a3..4d0372351 100644 --- a/Community/Tdarr_Plugin_MC93_Migz1Remux.js +++ b/Community/Tdarr_Plugin_MC93_Migz1Remux.js @@ -6,7 +6,7 @@ const details = () => ({ Type: 'Video', Operation: 'Transcode', Description: 'Files will be remuxed into either mkv or mp4. \n\n', - Version: '1.1', + Version: '1.2', Tags: 'pre-processing,ffmpeg,video only,configurable', Inputs: [{ name: 'container', @@ -48,10 +48,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -80,6 +80,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Set up required variables. let extraArguments = ''; + let genpts = ''; let convert = false; // Check if force_conform option is checked. @@ -135,8 +136,18 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { return response; } + // If Container .ts|.avi|.mpg|.mpeg set genpts to fix unknown timestamp + if ( + file.container.toLowerCase() === 'ts' + || file.container.toLowerCase() === 'avi' + || file.container.toLowerCase() === 'mpg' + || file.container.toLowerCase() === 'mpeg' + ) { + genpts = '-fflags +genpts'; + } + if (convert === true) { - response.preset += `, -map 0 -c copy -max_muxing_queue_size 9999 ${extraArguments}`; + response.preset += `${genpts}, -map 0 -c copy -max_muxing_queue_size 9999 ${extraArguments}`; response.processFile = true; return response; } diff --git a/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js b/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js index 127e159c8..94b33dca4 100644 --- a/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js +++ b/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js @@ -67,10 +67,10 @@ Optional. Only removes titles if they contain at least 3 '.' characters. ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js b/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js index 32bf0d26e..30670294e 100644 --- a/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js +++ b/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js @@ -78,17 +78,17 @@ const details = () => ({ \\nDo NOT use this with mp4, as mp4 does not support title tags. \\nExample:\\n true - + \\nExample:\\n false`, }, ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -127,6 +127,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { ).length; for (let i = 0; i < file.ffProbeData.streams.length; i++) { + let removeTrack = false; // Catch error here incase the language metadata is completely missing. try { // Check if stream is audio @@ -137,12 +138,10 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { file.ffProbeData.streams[i].tags.language.toLowerCase(), ) === -1 ) { - audioStreamsRemoved += 1; - ffmpegCommandInsert += `-map -0:a:${audioIdx} `; response.infoLog += `☒Audio stream 0:a:${audioIdx} has unwanted language tag ${file.ffProbeData.streams[ i ].tags.language.toLowerCase()}, removing. \n`; - convert = true; + removeTrack = true; } } catch (err) { // Error @@ -165,15 +164,18 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { .includes('description') || file.ffProbeData.streams[i].tags.title.toLowerCase().includes('sdh')) ) { - audioStreamsRemoved += 1; - ffmpegCommandInsert += `-map -0:a:${audioIdx} `; + removeTrack = true; response.infoLog += `☒Audio stream 0:a:${audioIdx} detected as being descriptive, removing. \n`; - convert = true; } } catch (err) { // Error } + if (removeTrack) { + audioStreamsRemoved += 1; + ffmpegCommandInsert += `-map -0:a:${audioIdx} `; + convert = true; + } // Check if inputs.tag_language has something entered // (Entered means user actually wants something to happen, empty would disable this) // AND checks that stream is audio. diff --git a/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js b/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js index fe55253ec..a98b496a0 100644 --- a/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js +++ b/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js @@ -60,10 +60,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js b/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js index f3cfd1179..b9deb1c36 100644 --- a/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js +++ b/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js @@ -10,10 +10,14 @@ const details = () => ({ Tags: 'pre-processing,ffmpeg,audio only,configurable', Inputs: [{ name: 'aac_stereo', - type: 'string', - defaultValue: '', + type: 'boolean', + defaultValue: false, inputUI: { - type: 'text', + type: 'dropdown', + options: [ + 'false', + 'true', + ], }, tooltip: `Specify if any 2.0 audio tracks should be converted to aac for maximum compatability with devices. \\nOptional. @@ -25,10 +29,14 @@ const details = () => ({ }, { name: 'downmix', - type: 'string', - defaultValue: '', + type: 'boolean', + defaultValue: false, inputUI: { - type: 'text', + type: 'dropdown', + options: [ + 'false', + 'true', + ], }, tooltip: `Specify if downmixing should be used to create extra audio tracks. \\nI.e if you have an 8ch but no 2ch or 6ch, create the missing audio tracks from the 8 ch. @@ -39,13 +47,28 @@ const details = () => ({ \\nExample:\\n false`, }, + { + name: 'downmix_single_track', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'By default this plugin will downmix each track. ' + + 'So four 6 channel tracks will result in four 2 channel tracks.' + + ' Enable this option to only downmix a single track.', + }, ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -77,8 +100,9 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { let audioIdx = 0; let has2Channel = false; let has6Channel = false; - let has8Channel = false; let convert = false; + let is2channelAdded = false; + let is6channelAdded = false; // Go through each stream in the file. for (let i = 0; i < file.ffProbeData.streams.length; i++) { @@ -91,9 +115,6 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { if (file.ffProbeData.streams[i].channels === 6) { has6Channel = true; } - if (file.ffProbeData.streams[i].channels === 8) { - has8Channel = true; - } } } catch (err) { // Error @@ -107,26 +128,31 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Catch error here incase user left inputs.downmix empty. try { // Check if inputs.downmix is set to true. - if (inputs.downmix.toLowerCase() === 'true') { + if (inputs.downmix === true) { // Check if file has 8 channel audio but no 6 channel, if so then create extra downmix from the 8 channel. if ( - has8Channel === true + file.ffProbeData.streams[i].channels === 8 && has6Channel === false - && file.ffProbeData.streams[i].channels === 8 + && (inputs.downmix_single_track === false + || (inputs.downmix_single_track === true && is6channelAdded === false)) + ) { ffmpegCommandInsert += `-map 0:${i} -c:a:${audioIdx} ac3 -ac 6 -metadata:s:a:${audioIdx} title="5.1" `; response.infoLog += '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n'; convert = true; + is6channelAdded = true; } // Check if file has 6 channel audio but no 2 channel, if so then create extra downmix from the 6 channel. if ( - has6Channel === true + file.ffProbeData.streams[i].channels === 6 && has2Channel === false - && file.ffProbeData.streams[i].channels === 6 + && (inputs.downmix_single_track === false + || (inputs.downmix_single_track === true && is2channelAdded === false)) ) { ffmpegCommandInsert += `-map 0:${i} -c:a:${audioIdx} aac -ac 2 -metadata:s:a:${audioIdx} title="2.0" `; response.infoLog += '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n'; convert = true; + is2channelAdded = true; } } } catch (err) { @@ -136,7 +162,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Catch error here incase user left inputs.downmix empty. try { // Check if inputs.aac_stereo is set to true. - if (inputs.aac_stereo === 'true') { + if (inputs.aac_stereo === true) { // Check if codec_name for stream is NOT aac AND check if channel ammount is 2. if ( file.ffProbeData.streams[i].codec_name !== 'aac' diff --git a/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js b/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js index 1da1993a4..2be21f165 100644 --- a/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js +++ b/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js @@ -11,10 +11,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js b/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js index 6768bad38..bfd0a5d75 100644 --- a/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js +++ b/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js @@ -11,10 +11,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js b/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js index f29ce6f52..3b3f93b11 100644 --- a/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js +++ b/Community/Tdarr_Plugin_MC93_MigzPlex_Autoscan.js @@ -60,10 +60,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // eslint-disable-next-line import/no-unresolved const request = require('request'); diff --git a/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js b/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js index bce6bf417..560a88df1 100644 --- a/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js +++ b/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js @@ -42,11 +42,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { processFile: false, diff --git a/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js b/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js index 701aa7014..1d391151c 100644 --- a/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js +++ b/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js @@ -41,11 +41,11 @@ const details = () => { } } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { processFile: false, @@ -85,7 +85,16 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Bitrate = file size / (stream duration * .0075) // Calculations were made based on the formula from this site: // https://blog.frame.io/2017/03/06/calculate-video-bitrates/ - duration = (file.meta.Duration !== `undefined` ? file.meta.Duration : stream.duration) * 0.0166667; + + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) * 0.0166667; + } else if(file.meta.Duration !== `undefined`){ + duration = file.meta.Duration* 0.0166667; + }else{ + duration = stream.duration * 0.0166667; + } + + currentBitrate = ~~(file.file_size / (duration * 0.0075)); targetBitrate = ~~(currentBitrate / 2); minimumBitrate = ~~(targetBitrate * 0.7); diff --git a/Community/Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation.js b/Community/Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation.js index dac17b640..ac54163f1 100644 --- a/Community/Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation.js +++ b/Community/Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation.js @@ -1,237 +1,254 @@ -/* eslint-disable */ -//PLugin runs multipass loudnorm filter -//first run gets the required details and stores for the next pass -//second pass applies the values +/* eslint-disable no-unused-vars, no-await-in-loop */ +module.exports.dependencies = ['axios@0.27.2']; -//stages +// PLugin runs multipass loudnorm filter +// first run gets the required details and stores for the next pass +// second pass applies the values + +// stages // Determined Loudnorm Values // Applying Normalisation // Normalisation Complete - -//setup global vars - -var secondPass = false; -var logOutFile = ''; - // tdarrSkipTest -const details = () => { - return { - id: "Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation", - Stage: 'Pre-processing', - Name: "2 Pass Loudnorm Volume Normalisation", - Type: "Video", - Operation: "Transcode", - Description: "PLEASE READ FULL DESCRIPTION BEFORE USE \n Uses multiple passes to normalise audio streams of videos using loudnorm.\n\n The first pass will create an log file in the same directory as the video.\nSecond pass will apply the values determined in the first pass to the file.\nOutput will be MKV to allow metadata to be added for tracking normalisation stage.", - Version: "0.1", - Tags: "pre-processing,ffmpeg,configurable", - - Inputs: [ - //(Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI - { - name: "i", - type: 'string', - defaultValue:'-23.0', - inputUI: { - type: 'text', - }, - tooltip: `\"I\" value used in loudnorm pass \n - defaults to -23.0`, //Each line following `Example:` will be clearly formatted. \\n used for line breaks - }, - { - name: "lra", - type: 'string', - defaultValue:'7.0', - inputUI: { - type: 'text', - }, - tooltip: `Desired lra value. \n Defaults to 7.0 +const details = () => ({ + id: 'Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation', + Stage: 'Pre-processing', + Name: '2 Pass Loudnorm Volume Normalisation', + Type: 'Video', + Operation: 'Transcode', + Description: `PLEASE READ FULL DESCRIPTION BEFORE USE + Uses multiple passes to normalise audio streams of videos using loudnorm. +The first pass will create an log file in the same directory as the video. +Second pass will apply the values determined in the first pass to the file. +Output will be MKV to allow metadata to be added for tracking normalisation stage.`, + Version: '0.1', + Tags: 'pre-processing,ffmpeg,configurable', + + Inputs: [ + // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI + { + name: 'i', + type: 'string', + defaultValue: '-23.0', + inputUI: { + type: 'text', + }, + tooltip: `"i" value used in loudnorm pass \\n + defaults to -23.0`, + }, + { + name: 'lra', + type: 'string', + defaultValue: '7.0', + inputUI: { + type: 'text', + }, + tooltip: `Desired lra value. \\n Defaults to 7.0 `, - }, - { - name: "tp", - type: 'string', - defaultValue:'-2.0', - inputUI: { - type: 'text', - }, - tooltip: `Desired \"tp\" value. \n Defaults to -2.0 - `, - }, - { - name: "offset", - type: 'string', - defaultValue:'0.0', - inputUI: { - type: 'text', - }, - tooltip: `Desired "offset" value. \n Defaults to 0.0 + }, + { + name: 'tp', + type: 'string', + defaultValue: '-2.0', + inputUI: { + type: 'text', + }, + tooltip: `Desired "tp" value. \\n Defaults to -2.0 `, - }, - ], - } -} - -// eslint-disable-next-line no-unused-vars -const plugin = (file, librarySettings, inputs, otherArguments) => { - - const lib = require('../methods/lib')(); const fs = require('fs'); - // eslint-disable-next-line no-unused-vars,no-param-reassign - inputs = lib.loadDefaultValues(inputs, details); - - //Must return this object at some point - var response = { - processFile: false, - preset: '', - container: '.mkv', - handBrakeMode: false, - FFmpegMode: true, - reQueueAfter: true, - infoLog: '', - - } - - response.infoLog += "" - //grab the current file being processed and make an out file for the ffmpeg log - let currentfilename = file._id; - logOutFile = currentfilename.substr(0, currentfilename.lastIndexOf(".")) + ".out" - console.log("Log out file: " + logOutFile) - - let probeData; - if (file && file.ffProbeData && file.ffProbeData.format) { - probeData = file.ffProbeData; - } else { - //get an updated version of the file for checking metadata - probeData = JSON.parse(require("child_process").execSync(`ffprobe -v quiet -print_format json -show_format -show_streams "${currentfilename}"`).toString()) - } - - //setup required varibles - var loudNorm_i = -23.0 - var lra = 7.0 - var tp = -2.0 - var offset = 0.0 - - //create local varibles for inputs - if (inputs !== undefined) { - if (inputs.i !== undefined) loudNorm_i = inputs.i - if (inputs.lra !== undefined) lra = inputs.lra - if (inputs.tp !== undefined) tp = inputs.tp - if (inputs.offset !== undefined) offset = inputs.offset - } - - - //check for previous pass tags + }, + + { + name: 'serverIp', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Enter the IP address of the server if plugin having trouble connecting.', + }, + { + name: 'serverPort', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Enter the port number of the server if plugin having trouble connecting.', + }, + ], +}); + +const parseJobName = (text) => { + const parts0 = text.split('.txt'); + const parts1 = parts0[0].split('()'); + return { + jobId: parts1[3], + start: Number(parts1[4]), + }; +}; - if (typeof probeData.format === "undefined" || typeof probeData.format.tags.NORMALISATIONSTAGE === "undefined" || probeData.format.tags.NORMALISATIONSTAGE === "" || file.forceProcessing === true) { +const getloudNormValues = async (inputs, response, file) => { + // { + // eslint-disable-next-line import/no-unresolved + const axios = require('axios'); + + const serverIp = inputs.serverIp ? inputs.serverIp : process.env.serverIp; + const serverPort = inputs.serverPort ? inputs.serverPort : process.env.serverPort; + const serverUrl = `http://${serverIp}:${serverPort}`; + let loudNormValues = false; + let tries = 0; + let error = false; + while (tries < 15) { + try { + tries += 1; + // wait for job report to be updated by server, + await new Promise((resolve) => setTimeout(resolve, 2000)); + + const logFilesReq = await axios.post(`${serverUrl}/api/v2/list-footprintId-reports`, { + data: { + footprintId: file.footprintId, + }, + }); + + if (logFilesReq.status !== 200) { + throw new Error('Failed to get log files, please rerun'); + } + + let logFiles = logFilesReq.data; + + logFiles = logFiles.sort((a, b) => { + const joba = parseJobName(a); + const jobb = parseJobName(b); + return jobb.start - joba.start; + }); + + const latestJob = logFiles[0]; + + const reportReq = await axios.post(`${serverUrl}/api/v2/read-job-file`, { + data: { + footprintId: file.footprintId, + jobId: parseJobName(latestJob).jobId, + jobFileId: latestJob, + }, + }); + + if (reportReq.status !== 200) { + throw new Error('Failed to get read latest log file, please rerun'); + } + + const report = reportReq.data.text; + const lines = report.split('\n'); + + let idx = -1; + + // get last index of Parsed_loudnorm + lines.forEach((line, i) => { + if (line.includes('Parsed_loudnorm')) { + idx = i; + } + }); - //no metadata found first pass is required - console.log("Searching for audio normailisation values") - response.infoLog += "Searching for required normalisation values. \n" - var loudNormInfo = ""; + if (idx === -1) { + throw new Error('Failed to find loudnorm in report, please rerun'); + } - //Do the first pass, output the log to the out file and use a secondary output for an unchanged file to allow Tdarr to track, Set metadata stage - response.preset = `-af loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json -f null NUL -map 0 -c copy -metadata NORMALISATIONSTAGE="FirstPassComplete" 2>"${logOutFile}"` - response.container = '.mkv' - response.handBrakeMode = false - response.FFmpegMode = true - response.reQueueAfter = true; - response.processFile = true - response.infoLog += "Normalisation first pass processing \n" - return response - } - if (probeData.format.tags.NORMALISATIONSTAGE === "FirstPassComplete") { - - //ensure previous out file exists - if (fs.existsSync(logOutFile)) { - secondPass = true; - loudNormInfo = fs.readFileSync(logOutFile).toString(); - - //grab the json from the out file - var startIndex = loudNormInfo.lastIndexOf("{"); - var endIndex = loudNormInfo.lastIndexOf("}"); - - var outValues = loudNormInfo.toString().substr(startIndex, endIndex) - - response.infoLog += "Loudnorm first pass values returned: \n" + outValues - - //parse the JSON - var loudNormValues = JSON.parse(outValues) - - //use parsed values in second pass - response.preset = `-y-af loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:measured_i=${loudNormValues.input_i}:measured_lra=${loudNormValues.input_lra}:measured_tp=${loudNormValues.input_tp}:measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} -c:a aac -b:a 192k -c:s copy -c:v copy -metadata NORMALISATIONSTAGE="Complete"` - response.container = '.mkv' - response.handBrakeMode = false - response.FFmpegMode = true - response.reQueueAfter = true; - response.processFile = true - response.infoLog += "Normalisation pass processing \n" - return response - } else { - response.infoLog += "Previous log output file is missing. Please rerun with force processing to regenerate." - response.processFile = false; - return response + const loudNormDataArr = []; + for (let i = (idx + 1); i < lines.length; i += 1) { + const lineArr = lines[i].split(' '); + lineArr.shift(); + loudNormDataArr.push(lineArr.join(' ')); + if (lines[i].includes('}')) { + break; } + } + + loudNormValues = JSON.parse(loudNormDataArr.join('')); + break; + } catch (err) { + response.infoLog += err; + error = err; } - if(probeData.format.tags.NORMALISATIONSTAGE === "Complete"){ - response.processFile = false; - response.infoLog += "File is already marked as normalised \n" - return response - } else { - //what is this tag? - response.processFile = false; - response.infoLog += "Unknown normalisation stage tag: \n" + probeData.format.tags.NORMALISATIONSTAGE - return response - } + } + if (loudNormValues === false && error) { + throw new Error(error); + } -} - -module.exports.onTranscodeSuccess = function onTranscodeSuccess( - file, - librarySettings, - inputs -) { - const fs = require('fs'); - var response = { - file, - removeFromDB: false, - updateDB: true, - }; - if (secondPass) { - response.infoLog += "Audio normalisation complete. \n" - //remove old out file - if (fs.existsSync(logOutFile)) { - fs.unlinkSync(logOutFile); - } - return response; - } - else { - response.infoLog += "Audio normalisation first pass complete. \n" - return response; - } + return loudNormValues; }; -module.exports.onTranscodeError = function onTranscodeError( - file, - librarySettings, - inputs -) { - console.log("Failed to normalise audio"); - - //Optional response if you need to modify database - var response = { - file, - removeFromDB: false, - updateDB: false, - }; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + // Must return this object at some point + const response = { + processFile: false, + preset: '', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: false, + infoLog: '', + }; + + response.infoLog += ''; + + const probeData = file.ffProbeData; + + // setup required varibles + let loudNorm_i = -23.0; + let lra = 7.0; + let tp = -2.0; + + // create local varibles for inputs + if (inputs !== undefined) { + if (inputs.i !== undefined) loudNorm_i = inputs.i; + if (inputs.lra !== undefined) lra = inputs.lra; + if (inputs.tp !== undefined) tp = inputs.tp; + } + + // check for previous pass tags + if (!probeData?.format?.tags?.NORMALISATIONSTAGE) { + // no metadata found first pass is required + response.infoLog += 'Searching for required normalisation values. \n'; + response.infoLog += 'Normalisation first pass processing \n'; + + // Do the first pass, output the log to the out file and use a secondary output for an unchanged file to + // allow Tdarr to track, Set metadata stage + response.preset = `-af loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json` + + ' -f null NUL -map 0 -c copy -metadata NORMALISATIONSTAGE=FirstPassComplete'; + response.FFmpegMode = true; + response.processFile = true; + return response; + } if ( + probeData.format.tags.NORMALISATIONSTAGE === 'FirstPassComplete' + ) { + const loudNormValues = await getloudNormValues(inputs, response, file); + + response.infoLog += `Loudnorm first pass values returned: \n${JSON.stringify(loudNormValues)}`; + + // use parsed values in second pass + response.preset = `-y-af loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:` + + `measured_i=${loudNormValues.input_i}:` + + `measured_lra=${loudNormValues.input_lra}:` + + `measured_tp=${loudNormValues.input_tp}:` + + `measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} ` + + '-c:a aac -b:a 192k -c:s copy -c:v copy -metadata NORMALISATIONSTAGE=Complete'; + response.FFmpegMode = true; + response.processFile = true; + response.infoLog += 'Normalisation pass processing \n'; + return response; + } if (probeData.format.tags.NORMALISATIONSTAGE === 'Complete') { + response.infoLog += 'File is already marked as normalised \n'; return response; + } + // what is this tag? + throw new Error(`Unknown normalisation stage tag: \n${probeData.format.tags.NORMALISATIONSTAGE}`); }; - - - - module.exports.details = details; -module.exports.plugin = plugin; \ No newline at end of file +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_O8O0dCTlb_Set_File_Permissions_For_UnRaid.js b/Community/Tdarr_Plugin_O8O0dCTlb_Set_File_Permissions_For_UnRaid.js index d083a33a4..141344130 100644 --- a/Community/Tdarr_Plugin_O8O0dCTlb_Set_File_Permissions_For_UnRaid.js +++ b/Community/Tdarr_Plugin_O8O0dCTlb_Set_File_Permissions_For_UnRaid.js @@ -16,11 +16,11 @@ const details = () => { } } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object at some point diff --git a/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js b/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js index eef3529ce..2571214e6 100644 --- a/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js +++ b/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js @@ -55,10 +55,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -68,6 +68,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { reQueueAfter: true, // Leave as true. File will be re-qeued afterwards and pass through the plugin // filter again to make sure it meets conditions. + preset: '', // Initialize with an empty string }; // Check that inputs.container has been configured, else dump out @@ -88,7 +89,13 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { let duration = ''; // Get duration of stream 0 and times it by 0.0166667 to get time in minutes - duration = file.ffProbeData.streams[0].duration * 0.0166667; + if (file.ffProbeData.streams[0].duration) { + duration = file.ffProbeData.streams[0].duration; + } else { + duration = file.ffProbeData.format.duration; + } + + duration *= 0.0166667; // Set up required variables. let videoIdx = 0; @@ -222,7 +229,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { response.preset = '-c:v vp8_cuvid'; } - response.preset += `,-map 0 -c:v h264_nvenc -preset fast -crf 23 -tune film ${bitrateSettings} ` + response.preset += `,-map 0 -c:v h264_nvenc -preset fast -crf 23 ${bitrateSettings} ` + `-c:a copy -c:s copy -max_muxing_queue_size 9999 -pix_fmt yuv420p ${extraArguments}`; response.processFile = true; response.infoLog += 'File is not h264. Transcoding. \n'; diff --git a/Community/Tdarr_Plugin_TD01_TOAD_Autoscan.js b/Community/Tdarr_Plugin_TD01_TOAD_Autoscan.js index 86c6dea23..952713fc5 100644 --- a/Community/Tdarr_Plugin_TD01_TOAD_Autoscan.js +++ b/Community/Tdarr_Plugin_TD01_TOAD_Autoscan.js @@ -75,10 +75,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // eslint-disable-next-line import/no-unresolved,import/no-extraneous-dependencies const request = require('request'); @@ -115,7 +115,7 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { }, url: `${ADDRESS}:${PORT}/triggers/manual?dir=${filepath}`, }, - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars (error, res, body) => { if (error) { // eslint-disable-next-line no-console diff --git a/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js b/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js index 141228111..e05d8d717 100644 --- a/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js +++ b/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js @@ -252,7 +252,7 @@ class Configurator { * @param {string} type the typeo of stream. * @param {function} method the method to call. */ -function loopOverStreamsOfType(file, type, method) { +const loopOverStreamsOfType = (file, type, method) => { let id = 0; for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { if (file.ffProbeData.streams[i].codec_type.toLowerCase() === type) { @@ -260,9 +260,9 @@ function loopOverStreamsOfType(file, type, method) { id += 1; } } -} +}; -function buildAudioConfiguration(inputs, file, logger) { +const buildAudioConfiguration = (inputs, file, logger) => { const configuration = new Configurator(['-c:a copy']); let stream_count = 0; let streams_removing = 0; @@ -351,9 +351,9 @@ function buildAudioConfiguration(inputs, file, logger) { } return configuration; -} +}; -function buildVideoConfiguration(inputs, file, logger) { +const buildVideoConfiguration = (inputs, file, logger) => { const configuration = new Configurator(['-map 0', '-map -0:d', '-c:v copy']); loopOverStreamsOfType(file, 'video', (stream, id) => { @@ -430,9 +430,9 @@ function buildVideoConfiguration(inputs, file, logger) { } return configuration; -} +}; -function buildSubtitleConfiguration(inputs, file, logger) { +const buildSubtitleConfiguration = (inputs, file, logger) => { const configuration = new Configurator(['-c:s copy']); // webvtt @@ -497,12 +497,12 @@ function buildSubtitleConfiguration(inputs, file, logger) { } return configuration; -} +}; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object const response = { diff --git a/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js b/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js index 856536530..a6a33035d 100644 --- a/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js +++ b/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js b/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js index d782b1b1f..cd3bd61a1 100644 --- a/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js +++ b/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js @@ -15,11 +15,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js b/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js index d95a3e43c..20fd46a74 100644 --- a/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js +++ b/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js @@ -15,11 +15,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js b/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js index c09e788d9..7657b2b8e 100644 --- a/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js +++ b/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js @@ -64,11 +64,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_a9he_New_file_size_check.js b/Community/Tdarr_Plugin_a9he_New_file_size_check.js index 32e99d6e3..a03571336 100644 --- a/Community/Tdarr_Plugin_a9he_New_file_size_check.js +++ b/Community/Tdarr_Plugin_a9he_New_file_size_check.js @@ -17,7 +17,7 @@ const details = () => ({ }, tooltip: `Enter the upper bound % size for the new file. For example, if '110' is entered, - then if the new file size is 11% larger than the original, an error will be given.`, + then if the new file size is greater than 110% the size of the original, an error will be given.`, }, { name: 'lowerBound', @@ -35,7 +35,7 @@ const details = () => ({ const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object at some point in the function else plugin will fail. const response = { diff --git a/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js b/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js index 4cda5caef..fcc9561a1 100644 --- a/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js +++ b/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js @@ -38,7 +38,7 @@ const details = () => ({ const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object at some point in the function else plugin will fail. const response = { diff --git a/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js b/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js index c790869b3..0ab778587 100644 --- a/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js +++ b/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js b/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js index 0546d0411..9b9bce2ba 100644 --- a/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js +++ b/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js @@ -27,10 +27,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object diff --git a/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js b/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js index 7035c3d23..c21d1ee11 100644 --- a/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js +++ b/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js @@ -3,8 +3,10 @@ // Extra logic is mainly to control encoder quality/speed & to allow HEVC files to be reprocessed to reduce file size // NOTE - This does not use VAAPI, it is QSV only. So newer intel iGPUs only. 8th+ gen should work. -// Additionally this was designed and tested on UNRAID via docker though there is logic to support use on -// Windows, Linux & Mac +// Additionally this was designed and tested on UNRAID via docker, though there is logic to support use on +// Windows & Linux - Both platforms have now been confirmed working, however there is no way to test all use cases +// Mac is supported, however it does not use QSV. This is because ffmpeg on Mac does not actually leverage QSV and +// instead uses "VideoToolbox" which is more a general video encode accelerator. // White paper from intel regarding QSV performance on linux using FFMPEG here: // eslint-disable-next-line max-len @@ -16,16 +18,22 @@ const details = () => ({ Name: 'Boosh-Transcode using QSV GPU & FFMPEG', Type: 'Video', Operation: 'Transcode', - Description: `This is a QSV specific plugin. 8th+ gen INTEL QSV enabled CPUs are required. VAAPI is NOT used. - Files not in H265/HEVC will be transcoded into H265/HEVC using Quick Sync Video (QSV) - via Intel GPU using FFmpeg. Settings are dependant on file bitrate working by the logic that H265 can support - the same amount of data at half the bitrate of H264. This plugin will skip files already in HEVC, AV1 & VP9 - unless "reconvert_hevc" is marked as true. If it is then these will be reconverted again into HEVC if they - exceed the bitrate specified in "hevc_max_bitrate". Reminder! An INTEL QSV enabled CPU is required. - NOTE - Created for use with UNRAID Docker and while it should support Windows/Mac etc, it may require - a custom version of FFmpeg to work properly.`, - Version: '1.0', - Tags: 'pre-processing,ffmpeg,video only,qsv,h265,hevc,configurable', + Description: `==DETAILS== This is a QSV plugin. 8th+ gen INTEL QSV enabled CPUs are recommended. VAAPI is NOT used. + \n\n==OS SUPPORT== This plugin supports Linux & Windows using QSV. Mac is supported though cannot use QSV and + relies on 'VideoToolBox' - Expect to see different encode speed & quality on Mac compared to other platforms. + Ensure you set your node settings accordingly! + \n\n==LOGIC== Files will be transcoded into H265/HEVC using Quick Sync Video (QSV) via Intel GPU using ffmpeg. + Settings are dependant on file bitrate working by the logic that H265 can support the same amount of data at half + the bitrate of H264. This plugin will skip files already in HEVC, AV1 & VP9 unless "reconvert_hevc" is marked as + true. If it is then these will be reconverted again if they exceed the bitrate specified in "hevc_max_bitrate". + This plugin will also attempt to use mkvpropedit to generate accurate bitrate metadata in MKV files. + It's not required to enable mkvpropedit but highly recommended to ensure accurate bitrates are used when + encoding your media. + \n\n==NOTE== Intel ARC cards are reportedly working successfully with this plugin, however please bare in mind that + I've not officially tested with them yet and your results might vary. Don't just assume it will work and if it does + ensure you properly test your files & workflow!`, + Version: '1.2', + Tags: 'pre-processing,ffmpeg,video only,qsv,h265,hevc,mkvpropedit,configurable', Inputs: [ { name: 'container', @@ -40,11 +48,11 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specifies the output container of the file. - \\n Ensure that all stream types you may have are supported by your chosen container. + \\nSpecifies the output container of the file. + \\nEnsure that all stream types you may have are supported by your chosen container. \\n ==INFO== - \\n Only MP4 & MKV are supported and MKV is recommended. + \\nOnly MP4 & MKV are supported and MKV is recommended. \\nExample:\\n mkv \\nExample:\\n @@ -63,16 +71,42 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Make the file conform to output containers requirements. + \\nMake the file conform to output containers requirements. Use if you need to ensure the encode works from mp4>mkv or mkv>mp4. \\n ==WARNING== \\n This will remove data of certain types so ensure you are happy with that, or use another plugin to convert these data types first! \\n ==INFO== - \\n Drop hdmv_pgs_subtitle/eia_608/subrip/timed_id3 for MP4. - \\n Drop data streams/mov_text/eia_608/timed_id3 for MKV. - \\n Default is false. + \\nDrop hdmv_pgs_subtitle/eia_608/subrip/timed_id3 for MP4. + \\nDrop data streams/mov_text/eia_608/timed_id3 for MKV. + \\nDefault is false. + \\nExample:\\n + true + \\nExample:\\n + false`, + }, + { + name: 'enable_10bit', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: `\\n + ==DESCRIPTION== + \\nSpecify if we want to enable 10bit encoding. + \\nIf this is enabled files will be processed and converted into 10bit + HEVC using main10 profile and with p010le pixel format. \n + If you just want to retain files that are already 10 bit then this can be left as false, as + 10bit to 10bit in ffmpeg should be automatic. + \\n + ==INFO== + \\nDefault is "false". \\nExample:\\n true \\nExample:\\n @@ -96,15 +130,15 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specify the encoder speed/preset to use. + \\nSpecify the encoder speed/preset to use. Slower options mean a slower encode but better quality and faster options mean faster encodes but worse quality. - \\n For more information see intel white paper on FFmpeg results using QSV: \\n` - // eslint-disable-next-line max-len - + `https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/cloud-computing-quicksync-video-ffmpeg-white-paper.pdf + \\nFor more information see intel white paper on ffmpeg results using QSV: \\n` + // eslint-disable-next-line max-len + + `https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/cloud-computing-quicksync-video-ffmpeg-white-paper.pdf \\n ==INFO== - \\n Default is "slow". + \\nDefault is "slow". \\nExample:\\n medium \\nExample:\\n @@ -119,50 +153,32 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Here you can add extra options to the FFmpeg QSV ENCODE cmd. - This does not override the FFmpeg cmd, it just allows additions to it. + \\nHere you can add extra options to the ffmpeg QSV ENCODE cmd. + This does not override the ffmpeg cmd, it just allows additions to it. \\n There are extra QSV options that can be forced on/off as desired. See here for some possible cmds - https://ffmpeg.org/ffmpeg-codecs.html#toc-HEVC-Options-1 \\n ==WARNING== \\n - Just because a cmd is mentioned doesn't mean your installed version of FFmpeg supports it... + Just because a cmd is mentioned doesn't mean your installed version of ffmpeg supports it... Be certain to verify the cmds work before adding to your workflow. \\n - Check Tdarr Help Tab. Enter FFmpeg cmd - "-h encoder=hevc_qsv". This will give a list of supported commands. + Check Tdarr Help Tab. Enter ffmpeg cmd - "-h encoder=hevc_qsv". This will give a list of supported commands. \\n + MAC SPECIFIC - This option is ignored on Mac because videotoolbox is used rather than qsv. \\n ==INFO== - \\n Default is empty but a suggested value is below. If unsure just leave empty. - \\n Ensure to only use cmds valid to encoding QSV as the script handles other FFmpeg cmds relating to + \\nDefault is empty but the first example below has a suggested value. If unsure just leave empty. + \\nEnsure to only use cmds valid to encoding QSV as the script handles other ffmpeg cmds relating to bitrate etc. Anything else entered here might be supported but could cause undesired results. \\nExample:\\n - -extbrc 1 -rdo 1 -mbbrc 1 -b_strategy 1 -adaptive_i 1 -adaptive_b 1`, - }, - { - name: 'enable_10bit', - type: 'boolean', - defaultValue: false, - inputUI: { - type: 'dropdown', - options: [ - 'false', - 'true', - ], - }, - tooltip: `\\n - ==DESCRIPTION== - \\n Specify if we want to enable 10bit encoding. - \\n If this is enabled files will be processed and converted into 10bit - HEVC using main10 profile and with p010le pixel format. \n - If you just want to retain files that are already 10 bit then this can be left as false, as - 10bit to 10bit in FFmpeg should be automatic. - \\n - ==INFO== - \\n Default is "false". + -look_ahead 1 -look_ahead_depth 100 -extbrc 1 -rdo 1 -mbbrc 1 -b_strategy 1 -adaptive_i 1 -adaptive_b 1 + \\n Above enables look ahead, extended bitrate control, b-frames, etc.\\n \\nExample:\\n - true + -vf scale_qsv=w=1280:h=720 + \\nScale video resolution Method 1\\n \\nExample:\\n - false`, + -vf scale_qsv=1280:-1 + \\nScale video resolution Method 2\\n`, }, { name: 'bitrate_cutoff', @@ -173,14 +189,12 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specify bitrate cutoff, files with a total bitrate lower then this will not be processed. \n - Since getting the bitrate of the video from files is unreliable, bitrate here refers to the total - bitrate of the file and not just the video steam. + \\nSpecify bitrate cutoff, files with a video bitrate lower then this will not be processed. \n \\n ==INFO== - \\n Rate is in kbps. - \\n Defaults to 0 which means this is disabled. - \\n Enter a valid number to enable. + \\nRate is in kbps. + \\nDefaults to 0 which means this is disabled. + \\nEnter a valid number to enable. \\nExample:\\n 2500 \\nExample:\\n @@ -195,16 +209,16 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specify a maximum average video bitrate. When encoding we take the current total bitrate and halve it + \\nSpecify a maximum average video bitrate. When encoding we take the current video bitrate and halve it to get an average target. This option sets a upper limit to that average (i.e if you have a video bitrate of 10000, half is 5000, if your maximum desired average bitrate is 4000 then we use that as the target instead of 5000). \\n ==INFO== - \\n Bitrate here is referring to video bitrate as we want to set the video bitrate on encode. - \\n Rate is in kbps. - \\n Defaults to 0 which means this is disabled. - \\n Enter a valid number to enable. + \\nBitrate here is referring to video bitrate as we want to set the video bitrate on encode. + \\nRate is in kbps. + \\nDefaults to 0 which means this is disabled. + \\nEnter a valid number to enable. \\nExample:\\n 4000 \\nExample:\\n @@ -219,16 +233,16 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specify a minimum average video bitrate. When encoding we take the current total bitrate and halve + \\nSpecify a minimum average video bitrate. When encoding we take the current video bitrate and halve it to get an average target. This option sets a lower limit to that average (i.e if you have a video bitrate of 3000, half is 1500, if your minimum desired average bitrate is 2000 then we use that as the target instead of 1500). \\n ==INFO== - \\n Bitrate here is referring to video bitrate as we want to set the video bitrate on encode. - \\n Rate is in kbps. - \\n Defaults to 0 which means this is disabled. - \\n Enter a valid number to enable. + \\nBitrate here is referring to video bitrate as we want to set the video bitrate on encode. + \\nRate is in kbps. + \\nDefaults to 0 which means this is disabled. + \\nEnter a valid number to enable. \\nExample:\\n 2000 \\nExample:\\n @@ -247,21 +261,22 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Specify if we want to reprocess HEVC, VP9 or AV1 files - (i.e reduce bitrate of files already in those codecs). - \\n Since this uses the same logic as normal, halving the current bitrate, this is NOT recommended - unless you know what you are doing, so leave false if unsure. - NEEDS to be used in conjunction with "bitrate_cutoff" or "hevc_max_bitrate" otherwise is ignored. - This is useful in certain situations, perhaps you have a file which is HEVC but has an extremely high + \\nSet to reprocess HEVC, VP9 or AV1 files (i.e reduce bitrate of files already in those codecs). + \\nSince this uses the same logic as normal, halving the current bitrate, this is NOT recommended + unless you know what you are doing, so please leave FALSE if unsure! + \\nNEEDS to be used in conjunction with "bitrate_cutoff" or "hevc_max_bitrate" otherwise is ignored. + \\nThis is useful in certain situations, perhaps you have a file which is HEVC but has an extremely high bitrate and you'd like to reduce it. - \\n Bare in mind that you can convert a file to HEVC and still be above your cutoff meaning it would - be converted again if this is set to true (since it's now HEVC). So if you use this be sure to set - "hevc_max_bitrate" & "max_average_bitrate" to prevent the plugin looping. Also it is highly suggested - that you have your "hevc_max_bitrate" higher than "max_average_bitrate". - \\n Again if you are unsure, please leave this as false! \\n ==WARNING== \\n - IF YOU HAVE VP9 OR AV1 FILES YOU WANT TO KEEP IN THOSE FORMATS THEN DO NOT USE THIS OPTION. + IF YOU HAVE VP9 OR AV1 FILES YOU WANT TO KEEP IN THOSE FORMATS THEN DO NOT USE THIS OPTION. \\n + \\nThis option has the potential to LOOP your encodes! You can encode a file to HEVC and still + be above your cutoff and it would be converted again & again if this is set to true (since it's now HEVC). + So if you use this be sure to set "hevc_max_bitrate" & "max_average_bitrate" to help prevent the plugin looping. + Also it is highly suggested that you have your "hevc_max_bitrate" higher than "max_average_bitrate". + \\nPlease be certain you want this enabled before setting it otherwise leave this as FALSE! + While the plugin will attempt to generate accurate video bitrate metadata, it can not always reliably do so + and will be forced to fall back onto estimates. Please bare this in mind when using the HEVC reprocess option. \\n \\nExample:\\n true @@ -277,21 +292,23 @@ const details = () => ({ }, tooltip: `\\n ==DESCRIPTION== - \\n Has no effect unless "reconvert_hevc" is set to true. This allows you to specify a maximum - allowed average bitrate for HEVC or similar files. Much like the "bitrate_cutoff" option, but + \\nHas no effect unless "reconvert_hevc" is set to true. This allows you to specify a maximum + allowed average OVERALL bitrate for HEVC or similar files. Much like the "bitrate_cutoff" option, but specifically for HEVC files. It should be set HIGHER then your standard cutoff for safety. - \\n Also, it's highly suggested you use the min & max average bitrate options in combination with this. You - will want those to control the bitrate otherwise you may end up repeatedly reprocessing HEVC files. - i.e your file might have a bitrate of 20000, if your hevc cutoff is 5000 then it's going to reconvert - multiple times before it'll fall below that cutoff. While HEVC reprocessing can be useful - this is why it is NOT recommended! - \\n As with the cutoff, getting the bitrate of the video from files is unreliable, so bitrate - here refers to the total bitrate of the file and not just the video steam. + \\nAlso, it's highly suggested you use the min & max average bitrate options in combination with this. You + will want those to control the encoded video bitrate, otherwise you may end up repeatedly reprocessing HEVC files. + i.e your file might have a overall bitrate of 20000, if your hevc cutoff is 5000 then it's going to reconvert + multiple times before it'll fall below that cutoff. While HEVC reprocessing can be useful this is why it is NOT + recommended! + \\n + ==WARNING== \\n + While the plugin will attempt to generate accurate video bitrate metadata, it can not always reliably do so + and will be forced to fall back onto estimates. Please bare this in mind when using the HEVC reprocess option. \\n ==INFO== - \\n Rate is in kbps. - \\n Defaults to 0 which means this is disabled. - \\n Enter a valid number to enable, otherwise we use "bitrate_cutoff" and multiply x2 for a safe limit. + \\nRate is in kbps. + \\nDefaults to 0 which means this is disabled. + \\nEnter a valid number to enable, otherwise we use "bitrate_cutoff" and multiply x2 for a safe limit. \\nExample:\\n 4000 \\nExample:\\n @@ -300,10 +317,29 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// Set up required variables. +let currentBitrate = 0; +let overallBitRate = 0; +let targetBitrate = 0; +let minimumBitrate = 0; +let maximumBitrate = 0; +let duration = ''; +let videoIdx = 0; +let extraArguments = ''; +let bitrateSettings = ''; +let inflatedCutoff = 0; +let main10 = false; +let high10 = false; +let videoBR = 0; + +// Finds the first video stream and get video bitrate + +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { - const lib = require('../methods/lib')(); const os = require('os'); - // eslint-disable-next-line no-unused-vars,no-param-reassign + const lib = require('../methods/lib')(); + const os = require('os'); + const proc = require('child_process'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, @@ -315,39 +351,137 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { container: `.${inputs.container}`, }; - // Set up required variables. - let duration = 0; - let videoIdx = 0; - let extraArguments = ''; - let bitrateSettings = ''; - let inflatedCutoff = 0; - let main10 = false; - - // Check if file is a video. If it isn't then exit plugin. if (file.fileMedium !== 'video') { - response.infoLog += 'File is not a video. \n'; + response.processFile = false; + response.infoLog += `☒ File seems to be ${file.fileMedium} & not video. Exiting \n`; return response; } - // 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. + // MKVPROPEDIT - Refresh video stats + const intStatsDays = 7; // Use 1 week threshold for new stats + let statsUptoDate = false; + const currentFileName = file._id; + let statsError = false; + let metadataEncode = ''; + + // Only process MKV files + if (file.container === 'mkv') { + let datStats = Date.parse(new Date(70, 1).toISOString()); // Placeholder date + metadataEncode = `-map_metadata:g -1 -metadata JBDONEDATE=${datStats}`; + if (file.mediaInfo.track[0].extra !== undefined && file.mediaInfo.track[0].extra.JBDONEDATE !== undefined) { + datStats = Date.parse(file.mediaInfo.track[0].extra.JBDONEDATE); + } else { + try { + if ( + file.mediaInfo.track[0].extra !== undefined + && file.ffProbeData.streams[0].tags['_STATISTICS_WRITING_DATE_UTC-eng'] !== undefined + ) { + // Set stats date to match info inside file + datStats = Date.parse(`${file.ffProbeData.streams[0].tags['_STATISTICS_WRITING_DATE_UTC-eng']} GMT`); + } + } catch (err) { + // Catch error - Ignore & carry on - If check can bomb out if the tag doesn't exist... + } + } + + // Threshold for stats date + const statsThres = Date.parse(new Date(new Date().setDate(new Date().getDate() - intStatsDays)).toISOString()); + + // Strings for easy to read dates in info log + let statsThresString = new Date(statsThres); + statsThresString = statsThresString.toUTCString(); + let datStatsString = new Date(datStats); + datStatsString = datStatsString.toUTCString(); + response.infoLog += `Checking file stats - If stats are older than ${intStatsDays} days we'll grab new stats.\n + Stats threshold: ${statsThresString}\n + Current stats date: ${datStatsString}\n`; + + // Are the stats out of date? + if (datStats >= statsThres) { + statsUptoDate = true; + response.infoLog += '☑ File stats are upto date! - Continuing...\n'; + } else { + response.infoLog += '☒ File stats are out of date! - Will attempt to use mkvpropedit to refresh stats\n'; + try { + if (otherArguments.mkvpropeditPath !== '') { // Try to use mkvpropedit path if it is set + proc.execSync(`"${otherArguments.mkvpropeditPath}" --add-track-statistics-tags "${currentFileName}"`); + } else { // Otherwise just use standard mkvpropedit cmd + proc.execSync(`mkvpropedit --add-track-statistics-tags "${currentFileName}"`); + } + } catch (err) { + response.infoLog += '☒ Error updating file stats - Possible mkvpropedit failure or file issue - ' + + ' Ensure mkvpropedit is set correctly in the node settings & check the filename for unusual characters.\n' + + ' Continuing but file stats will likely be inaccurate...\n'; + statsError = true; + } + if (statsError !== true) { + // File now updated with new stats + response.infoLog += 'Remuxing file to write in updated file stats! \n'; + response.preset += `-fflags +genpts -map 0 -c copy -max_muxing_queue_size 9999 -map_metadata:g -1 + -metadata JBDONEDATE=${new Date().toISOString()}`; + response.processFile = true; + return response; + } + } + } else { + response.infoLog += 'Input file is not MKV so cannot use mkvpropedit to get new file stats. ' + + 'Continuing but file stats will likely be inaccurate...\n'; + } + + for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { + const strstreamType = file.ffProbeData.streams[i].codec_type.toLowerCase(); + videoIdx = -1; + // Check if stream is a video. + if (videoIdx === -1 && strstreamType === 'video') { + videoIdx = i; + videoBR = Number(file.mediaInfo.track[i + 1].BitRate) / 1000; + + // If MediaInfo fails somehow fallback to ffprobe - Try two types of tags that might exist + if (videoBR <= 0) { + if (Number(file.ffProbeData.streams[i].tags.BPS) > 0) { + videoBR = file.ffProbeData.streams[i].tags.BPS / 1000; + } else { + try { + if (Number(file.ffProbeData.streams[i].tags.BPS['-eng']) > 0) { + videoBR = file.ffProbeData.streams[i].tags.BPS['-eng'] / 1000; + } + } catch (err) { + // Catch error - Ignore & carry on - If check can bomb out if tags don't exist... + } + } + } + } + } + + // Check if duration info is filled, if so convert time format to minutes. + // If not filled then get duration of video stream and do the same. if (typeof file.meta.Duration !== 'undefined') { - duration = file.meta.Duration * 0.0166667; + duration = file.meta.Duration; + // Get seconds by using a Date & then convert to minutes + duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60; } else { - duration = file.ffProbeData.streams[0].duration * 0.0166667; + duration = file.ffProbeData.streams[videoIdx].tags.DURATION; + duration = (new Date(`1970-01-01T${duration}Z`).getTime() / 1000) / 60; } - // Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)" - // Used from here https://blog.frame.io/2017/03/06/calculate-video-bitrates/ - const currentBitrate = Math.round(file.file_size / (duration * 0.0075)); - // Use the same calculation used for currentBitrate but divide it in half to get targetBitrate. - // Logic of h265 can be half the bitrate as h264 without losing quality. - let targetBitrate = Math.round((file.file_size / (duration * 0.0075)) / 2); - // Allow some leeway under and over the targetBitrate. - let minimumBitrate = Math.round(targetBitrate * 0.75); - let maximumBitrate = Math.round(targetBitrate * 1.25); + if (Number.isNaN(videoBR) || videoBR <= 0) { + // Work out currentBitrate using "Bitrate = file size / (number of minutes * .0075)" + currentBitrate = Math.round(file.file_size / (duration * 0.0075)); + 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. `; + response.infoLog += 'Bitrate calculations for video encode will likely be inaccurate... \n'; + } else { + currentBitrate = Math.round(videoBR); + response.infoLog += `☑ It looks like the current video bitrate is ${currentBitrate}kbps. \n`; + } - response.infoLog += `☑ It looks like the current bitrate is ${currentBitrate}k. \n`; + // Get overall bitrate for use with HEVC reprocessing + overallBitRate = Math.round(file.file_size / (duration * 0.0075)); + // Halve current bitrate for Target bitrate, in theory h265 can be half the bitrate as h264 without losing quality. + targetBitrate = Math.round(currentBitrate / 2); + // Allow some leeway under and over the targetBitrate. + minimumBitrate = Math.round(targetBitrate * 0.75); + maximumBitrate = Math.round(targetBitrate * 1.25); // If targetBitrate or currentBitrate comes out as 0 then something // has gone wrong and bitrates could not be calculated. @@ -361,18 +495,19 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // has gone wrong as that is not what we want. // Cancel plugin completely. if (targetBitrate >= currentBitrate) { - response.infoLog += `☒ Target bitrate has been calculated as ${targetBitrate}k. This is equal or greater - than the current bitrate... Something has gone wrong and this shouldn't happen! Skipping this plugin. \n`; + response.infoLog += `☒ Target bitrate has been calculated as ${targetBitrate}kbps. This is equal or greater `; + response.infoLog += "than the current bitrate... Something has gone wrong and this shouldn't happen! " + + 'Skipping this plugin. \n'; return response; } // Ensure that bitrate_cutoff is set if reconvert_hevc is true since we need some protection against a loop // Cancel the plugin 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 - or HEVC specific cutoff set so we have no way to know when to stop processing this file. - Either set reconvert_HEVC to false or set a bitrate cutoff and set a hevc_max_bitrate cutoff. - Skipping this plugin. \n`; + response.infoLog += `Reconvert HEVC is ${inputs.reconvert_hevc}, however there is no bitrate cutoff `; + response.infoLog += 'or HEVC 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' + + '☒ Skipping this plugin. \n'; return response; } @@ -382,7 +517,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Checks if currentBitrate is below inputs.bitrate_cutoff. // If so then cancel plugin without touching original files. if (currentBitrate <= inputs.bitrate_cutoff) { - response.infoLog += `☑ Current bitrate is below set cutoff of ${inputs.bitrate_cutoff}k. Cancelling plugin. \n`; + response.infoLog += `☑ Current bitrate is below set cutoff of ${inputs.bitrate_cutoff}kbps. \n` + + 'Cancelling plugin. \n'; return response; } // If above cutoff then carry on @@ -395,8 +531,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // Checks if targetBitrate is above inputs.max_average_bitrate. // If so then clamp target bitrate if (targetBitrate > inputs.max_average_bitrate) { - response.infoLog += `Our target bitrate is above the max_average_bitrate so - target average bitrate clamped at max of ${inputs.max_average_bitrate}k. \n`; + response.infoLog += 'Our target bitrate is above the max_average_bitrate '; + response.infoLog += `so clamping at max of ${inputs.max_average_bitrate}kbps. \n`; targetBitrate = Math.round(inputs.max_average_bitrate); minimumBitrate = Math.round(targetBitrate * 0.75); maximumBitrate = Math.round(targetBitrate * 1.25); @@ -409,13 +545,13 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // 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) { response.infoLog += `☒ Bitrate cutoff ${inputs.bitrate_cutoff}k is less than the set minimum - average bitrate set of ${inputs.min_average_bitrate}k. 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; } // Checks if inputs.bitrate_cutoff is below inputs.min_average_bitrate. // If so then set currentBitrate to the minimum allowed.) if (targetBitrate < inputs.min_average_bitrate) { - response.infoLog += `Target average bitrate clamped at min of ${inputs.min_average_bitrate}k. \n`; + response.infoLog += `Target average bitrate clamped at min of ${inputs.min_average_bitrate}kbps. \n`; targetBitrate = Math.round(inputs.min_average_bitrate); minimumBitrate = Math.round(targetBitrate * 0.75); maximumBitrate = Math.round(targetBitrate * 1.25); @@ -467,14 +603,6 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { } } - // Are we encoding to 10 bit? If so enable correct profile & pixel format. - // With this set we also disable hardware decode for compatibility later - if (inputs.enable_10bit === true) { - main10 = true; - extraArguments += '-profile:v main10 -pix_fmt p010le '; - response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n'; - } - // Go through each stream in the file. for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { // Check if stream is a video. @@ -486,12 +614,12 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { } // Check for HDR in files. If so exit plugin. We assume HDR files have bt2020 color spaces. HDR can be complicated - // and some aspects are still unsupported in FFmpeg I believe. Likely we don't want to re-encode anything HDR. + // 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' && file.ffProbeData.streams[i].color_transfer === 'smpte2084' && file.ffProbeData.streams[i].color_primaries === 'bt2020') { - response.infoLog += `☒ This looks to be a HDR file. HDR files are unfortunately - not supported by this plugin. Exiting plugin. \n\n`; + response.infoLog += '☒ This looks to be a HDR file. HDR files are unfortunately ' + + 'not supported by this plugin. Exiting plugin. \n\n'; return response; } @@ -517,24 +645,28 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { return response; } - // New logic for reprocessing HEVC. Mainly done for my own use. Since we're reprocessing we're checking - // bitrate again and since this can be inaccurate (It calculates overall bitrate not video specific) - // we have to inflate the current bitrate so we don't keep looping this logic. + // 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 + // which can be inaccurate. We may inflate the current bitrate check so we don't keep looping this logic. } else if (inputs.reconvert_hevc === true && (file.ffProbeData.streams[i].codec_name === 'hevc' || file.ffProbeData.streams[i].codec_name === 'vp9' || file.ffProbeData.streams[i].codec_name === 'av1')) { - // If we're using the hevc max bitrate then update the cutoff to use it - + if (statsUptoDate !== true) { + currentBitrate = overallBitRate; // User overall bitrate if we don't have upto date stats + response.infoLog += `☒ Unable to get accurate stats for HEVC so falling back to Overall file Bitrate. + Remux to MKV to allow generation of accurate video bitrate statistics. + File overall bitrate is ${overallBitRate}kbps.\n`; + } if (inputs.hevc_max_bitrate > 0) { if (currentBitrate > inputs.hevc_max_bitrate) { // 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, VP9 or AV1. - Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}k. \n - ☒ The file is still above this new cutoff! Reconverting. \n\n`; + response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, ` + + `VP9 or AV1. Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps. \n`; + response.infoLog += '☒ The file is still above this new cutoff! Reconverting. \n'; } else { // 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, VP9 or AV1. - Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}k. \n - ☑ The file is NOT above this new cutoff. Exiting plugin. \n\n`; + response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, ` + + `VP9 or AV1. Using HEVC specific cutoff of ${inputs.hevc_max_bitrate}kbps. \n`; + response.infoLog += '☑ The file is NOT above this new cutoff. Exiting plugin. \n'; return response; } @@ -542,26 +674,34 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { // looping this plugin. For maximum safety we simply multiply the cutoff by 2. } else if (currentBitrate > (inputs.bitrate_cutoff * 2)) { inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2); - response.infoLog += `☒ Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1. - HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! - Cutoff now temporarily ${inflatedCutoff}k. \n The file is still above this new cutoff! Reconverting. \n\n`; + response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `; + response.infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, '; + response.infoLog += `bitrate is ${overallBitRate}kbps. \n`; + response.infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n'; + response.infoLog += `Cutoff now temporarily ${inflatedCutoff}kbps. \n`; + response.infoLog += '☒ The file is still above this new cutoff! Reconverting. \n'; } else { // File is below cutoff so we can exit inflatedCutoff = Math.round(inputs.bitrate_cutoff * 2); - response.infoLog += `☑ Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, VP9 or AV1 - so bitrate_cutoff is multiplied by 2! Cutoff now temporarily ${inflatedCutoff}k. \n - The file is NOT above this new cutoff. Exiting plugin. \n\n`; + response.infoLog += `Reconvert_hevc is ${inputs.reconvert_hevc} & the file is already HEVC, `; + response.infoLog += 'VP9 or AV1. Will use Overall file Bitrate for HEVC files as safety, '; + response.infoLog += `bitrate is ${overallBitRate}kbps. \n`; + response.infoLog += 'HEVC specific cutoff not set so bitrate_cutoff is multiplied by 2 for safety! \n'; + response.infoLog += `Cutoff now temporarily ${inflatedCutoff}kbps. \n`; + response.infoLog += '☑The file is NOT above this new cutoff. Exiting plugin. \n'; return response; } } - // If files are already 10bit then disable hardware decode to avoid problems with encode - // 10 bit from source file should be retained without extra arguments. - if (file.ffProbeData.streams[i].profile === 'High 10' - || file.ffProbeData.streams[i].profile === 'Main 10' - || file.ffProbeData.streams[i].bits_per_raw_sample === '10') { + // On testing I've found files in the High10 profile don't play nice with hw decoding so mark these + if (file.ffProbeData.streams[i].profile === 'High 10') { + high10 = true; + response.infoLog += 'Input file is 10bit using High10. Disabling hardware decoding to avoid problems. \n'; + } + // If files are 10 bit or the enable_10bit setting is used mark to enable Main10. + if (file.ffProbeData.streams[i].profile === 'Main 10' || file.ffProbeData.streams[i].bits_per_raw_sample === '10' + || inputs.enable_10bit === true) { main10 = true; - response.infoLog += 'Input file is 10bit. Disabling hardware decoding to avoid problems. \n\n'; } // Increment video index. Needed to keep track of video id in case there is more than one video track. @@ -570,81 +710,116 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { } } + // Are we encoding to 10 bit? If so enable correct profile & pixel format. + if (high10 === true) { // This is used if we have High10 files. SW decode and use standard -pix_fmt p010le + extraArguments += '-profile:v main10 -pix_fmt p010le '; + response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n'; + } else if (main10 === true) { // Pixel formate method when using HW decode + extraArguments += '-profile:v main10 -vf scale_qsv=format=p010le '; + response.infoLog += '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n'; + } + // Set bitrateSettings variable using bitrate information calculated 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 += `\nContainer for output selected as ${inputs.container}. \n`; + response.infoLog += `Container for output selected as ${inputs.container}. \n`; response.infoLog += 'Encode variable bitrate settings: \n'; response.infoLog += `Target = ${targetBitrate}k \n`; response.infoLog += `Minimum = ${minimumBitrate}k \n`; response.infoLog += `Maximum = ${maximumBitrate}k \n`; // START PRESET - // DECODE FLAGS // -fflags +genpts should regenerate timestamps if they end up missing... response.preset = '-fflags +genpts '; - // Attempt to enable HW Decoding... - // If source file is 10 bit then bail as this can cause issues. Think it's the -c:v option that can break during 10bit - if (main10 === false) { - // Currently supported HW decode types - switch (file.video_codec_name) { - case 'mpeg2': - response.preset += '-hwaccel qsv -c:v mpeg2_qsv'; - break; - case 'h264': - response.preset += '-hwaccel qsv -c:v h264_qsv'; - break; - case 'vc1': - response.preset += '-hwaccel qsv -c:v vc1_qsv'; - break; - case 'mjpeg': - response.preset += '-hwaccel qsv -c:v mjpeg_qsv'; - break; - case 'vp8': - response.preset += '-hwaccel qsv -c:v vp8_qsv'; + // HW ACCEL FLAGS - I think these are good practice but are they necessary? + // Account for different OS + if (high10 === false) { + // Seems incoming High10 files don't play nice decoding so use software decode + switch (os.platform()) { + case 'darwin': // Mac OS - Enable videotoolbox instead of QSV + response.preset += '-hwaccel videotoolbox'; break; - case 'hevc': - response.preset += '-hwaccel qsv -c:v hevc_qsv'; + case 'linux': // Linux - Full device, should fix child_device_type warnings + response.preset += `-hwaccel qsv -hwaccel_output_format qsv + -init_hw_device qsv:hw_any,child_device_type=vaapi `; break; - case 'vp9': // Should be supported by 8th Gen + - response.preset += '-hwaccel qsv -c:v vp9_qsv'; + case 'win32': // Windows - Full device, should fix child_device_type warnings + response.preset += `-hwaccel qsv -hwaccel_output_format qsv + -init_hw_device qsv:hw_any,child_device_type=d3d11va `; break; default: - response.preset += '-hwaccel qsv'; + response.preset += '-hwaccel qsv -hwaccel_output_format qsv -init_hw_device qsv:hw_any '; + } + } + + // DECODE FLAGS + if (os.platform() !== 'darwin') { + if (high10 === false) { // Don't enable for High10 + switch (file.video_codec_name) { + case 'mpeg2': + response.preset += '-c:v mpeg2_qsv'; + break; + case 'h264': + response.preset += '-c:v h264_qsv'; + break; + case 'vc1': + response.preset += '-c:v vc1_qsv'; + break; + case 'mjpeg': + response.preset += '-c:v mjpeg_qsv'; + break; + case 'vp8': + response.preset += '-c:v vp8_qsv'; + break; + case 'hevc': + response.preset += '-c:v hevc_qsv'; + break; + case 'vp9': // Should be supported by 8th Gen + + response.preset += '-c:v vp9_qsv'; + break; + default: + response.preset += ''; + } } - } else { - response.preset += '-hwaccel qsv'; - // Enable basic hwaccel regardless. Seems to work... } - // ADD ENCODE FLAGS TO PRESET + // ENCODE FLAGS response.preset += ' -map 0 -c:v '; - // Account for different OS setup for QSV. - // FYI Darwin is Mac OS + // Account for different OS setup for QSV HEVC encode. switch (os.platform()) { case 'darwin': response.preset += 'hevc_videotoolbox'; - // hevc_videotoolbox is for Mac but that doesn't seem to be included in Tdarr current Jellyfin FFmpeg - // Likely needs custom FFmpeg installed + // Mac OS & uses hevc_videotoolbox not QSV - Only shows up on Mac installs break; case 'linux': response.preset += 'hevc_qsv'; break; case 'win32': response.preset += 'hevc_qsv -load_plugin hevc_hw'; - // Windows needs the additional -load_plugin plugin. Tested working on a Win 10 - i5-10505 + // Windows needs the additional -load_plugin. Tested working on a Win 10 - i5-10505 break; default: - response.preset += 'hevc_qsv'; + response.preset += 'hevc_qsv'; // Default to QSV } - // Add the rest of the FFmpeg command - response.preset += ` ${bitrateSettings} ` - + `-preset ${inputs.encoder_speedpreset} ${inputs.extra_qsv_options} - -c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments}`; + // Add the rest of the ffmpeg command + switch (os.platform()) { + case 'darwin': + // Mac OS - Don't use extra_qsv_options - These are intended for QSV cmds so videotoolbox causes issues + response.preset += ` ${bitrateSettings} ` + + `-preset ${inputs.encoder_speedpreset} -c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments}`; + response.infoLog += '==ALERT== OS detected as MAC - This will use VIDEOTOOLBOX to encode which is NOT QSV\n' + + 'cmds set in extra_qsv_options will be IGNORED!\n'; + break; + default: + // Normal behavior + response.preset += ` ${bitrateSettings} ` + + `-preset ${inputs.encoder_speedpreset} ${inputs.extra_qsv_options} ` + + `-c:a copy -c:s copy -max_muxing_queue_size 9999 ${extraArguments} ${metadataEncode}`; + } response.processFile = true; response.infoLog += 'File Transcoding... \n'; diff --git a/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js b/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js index bdd492e52..ae412a3b4 100644 --- a/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js +++ b/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js @@ -36,11 +36,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { processFile: false, diff --git a/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js b/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js index 699968bf4..e878b466c 100644 --- a/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js +++ b/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var transcode = 0; //if this var changes to 1 the file will be transcoded var bitrateprobe = 0; //bitrate from ffprobe diff --git a/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js b/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js index c1b9ea4ba..a139ccb5f 100644 --- a/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js +++ b/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var transcode = 0; //if this var changes to 1 the file will be transcoded //default values that will be returned 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 2dc1a3170..32cb0838a 100644 --- a/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js +++ b/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js @@ -56,11 +56,11 @@ function getPreset(preset) { const GOOD = true; const BAD = false; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { diff --git a/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js b/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js index ea8dc7cb2..45ee6c194 100644 --- a/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js +++ b/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js @@ -46,7 +46,7 @@ const details = () => { { name: "wanted_subtitle_languages", type: 'string', - defaultValue: 'eng,fre', + defaultValue: '', inputUI: { type: 'text', }, @@ -142,6 +142,10 @@ class Configurator { * Returns the duration of the file in minutes. */ function getFileDurationInMinutes(file) { + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + return parseFloat(file.ffProbeData?.format?.duration) * 0.0166667; + } + return typeof file.meta.Duration != undefined ? file.meta.Duration * 0.0166667 : file.ffProbeData.streams[0].duration * 0.0166667; @@ -407,11 +411,11 @@ function buildVideoConfiguration(inputs, file, logger) { //#endregion -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { - - const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var response = { container: ".mkv", diff --git a/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js b/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js index 3bb528086..5d350f3ab 100644 --- a/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js +++ b/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js b/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js index 5cca05cb1..887eca9b9 100644 --- a/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js +++ b/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js b/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js index 7ca1c114b..c8d042c7a 100644 --- a/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js +++ b/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_e5c3_CnT_Add_Subtitles.js b/Community/Tdarr_Plugin_e5c3_CnT_Add_Subtitles.js index 7b56fdf41..8a634f438 100644 --- a/Community/Tdarr_Plugin_e5c3_CnT_Add_Subtitles.js +++ b/Community/Tdarr_Plugin_e5c3_CnT_Add_Subtitles.js @@ -34,11 +34,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); const fs = require("fs"); const execSync = require("child_process").execSync; - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //default response var response = { diff --git a/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js b/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js index d36415ed0..7cf5ce4b7 100644 --- a/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js +++ b/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js @@ -43,11 +43,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); const exec = require("child_process").exec; const fs = require("fs"); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); if (inputs.languages == "" || typeof inputs.special == "undefined") { var languages = ["eng", "en"]; //these languages should be kept, named according to ISO 639-2 language scheme diff --git a/Community/Tdarr_Plugin_e5c3_CnT_Remove_Letterbox.js b/Community/Tdarr_Plugin_e5c3_CnT_Remove_Letterbox.js index d4b82663f..84764e2e7 100644 --- a/Community/Tdarr_Plugin_e5c3_CnT_Remove_Letterbox.js +++ b/Community/Tdarr_Plugin_e5c3_CnT_Remove_Letterbox.js @@ -34,11 +34,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); const fs = require("fs"); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); if (inputs.bitrate == "" || inputs.bitrate == "undefined") { var min_bitrate = 6600; @@ -284,7 +284,14 @@ function crop_decider(file, crop_height) { function size_check(file, min_bitrate) { const fs = require("fs"); - var duration = file.meta.Duration; //duration of video in seconds + + let duration = 0; + if (parseFloat(file.ffProbeData?.format?.duration) > 0) { + duration = parseFloat(file.ffProbeData?.format?.duration) + }else{ + duration = file.meta.Duration; //duration of video in seconds + } + var source = file.meta.SourceFile; //source file var stats = fs.statSync(source); var size = stats["size"] / 1000000000; diff --git a/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js b/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js index d2632a1bb..05c42bc44 100644 --- a/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js +++ b/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js @@ -30,10 +30,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { diff --git a/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js b/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js index 49203bedd..2b1b946e5 100644 --- a/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js +++ b/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_goof1_URL_Plex_Refresh.js b/Community/Tdarr_Plugin_goof1_URL_Plex_Refresh.js index 9afa86a48..eb1e0facc 100644 --- a/Community/Tdarr_Plugin_goof1_URL_Plex_Refresh.js +++ b/Community/Tdarr_Plugin_goof1_URL_Plex_Refresh.js @@ -132,7 +132,7 @@ const details = () => ({ ], }); -function checkReply(response, statusCode, urlNoToken) { +const checkReply = (response, statusCode, urlNoToken) => { if (statusCode === 200) { response.infoLog += '☒ Above shown folder scanned in Plex! \n'; } else if (statusCode === 401) { @@ -144,12 +144,12 @@ function checkReply(response, statusCode, urlNoToken) { response.infoLog += `There was an issue reaching Plex. The URL used was ${urlNoToken}[redacted] \n`; } -} +}; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/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 + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const http = require('http'); const https = require('https'); diff --git a/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js b/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js index cf46df8f4..06e8eafb8 100644 --- a/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js +++ b/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js @@ -66,10 +66,10 @@ const details = () => ({ }], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, 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 5a33b295c..e88bdcea5 100644 --- a/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js +++ b/Community/Tdarr_Plugin_henk_Keep_Native_Lang_Plus_Eng.js @@ -12,7 +12,7 @@ const details = () => ({ '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 the IMDB id in the filename.`, - Version: '1.01', + Version: '1.2', Tags: 'pre-processing,configurable', Inputs: [ { @@ -22,7 +22,8 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: 'Input a comma separated list of ISO-639-2 languages. It will still keep English and undefined tracks.' + tooltip: + 'Input a comma separated list of ISO-639-2 languages. It will still keep English and undefined tracks.' + '(https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes 639-2 column)' + '\\nExample:\\n' + 'nld,nor', @@ -34,7 +35,8 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: 'Priority for either Radarr or Sonarr. Leaving it empty defaults to Radarr first.' + tooltip: + 'Priority for either Radarr or Sonarr. Leaving it empty defaults to Radarr first.' + '\\nExample:\\n' + 'sonarr', }, @@ -45,7 +47,8 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)', + tooltip: + 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/)', }, { name: 'radarr_api_key', @@ -63,7 +66,8 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: 'Input your Radarr url here. (Without http://). Do include the port.' + tooltip: + 'Input your Radarr url here. (Without http://). Do include the port.' + '\\nExample:\\n' + '192.168.1.2:7878', }, @@ -83,7 +87,8 @@ const details = () => ({ inputUI: { type: 'text', }, - tooltip: 'Input your Sonarr url here. (Without http://). Do include the port.' + tooltip: + 'Input your Sonarr url here. (Without http://). Do include the port.' + '\\nExample:\\n' + '192.168.1.2:8989', }, @@ -117,7 +122,9 @@ const processStreams = (result, file, user_langs) => { langs.push(languages.alpha2ToAlpha3B(langsTemp)); // Some console reporting for clarification of what the plugin is using and reporting. - response.infoLog += `Original language: ${langsTemp}, Using code: ${languages.alpha2ToAlpha3B(langsTemp)}\n`; + response.infoLog += `Original language: ${langsTemp}, Using code: ${languages.alpha2ToAlpha3B( + langsTemp, + )}\n`; if (user_langs) { langs = langs.concat(user_langs); @@ -133,9 +140,8 @@ const processStreams = (result, file, user_langs) => { response.infoLog = `${response.infoLog.slice(0, -2)}\n`; - for (let i = 0; i < file.ffProbeData.streams.length; i += 1) { - const stream = file.ffProbeData.streams[i]; - + // eslint-disable-next-line no-restricted-syntax + for (const stream of file.ffProbeData.streams) { if (stream.codec_type === 'audio') { if (!stream.tags) { response.infoLog += `☒No tags found on audio track ${streamIndex}. Keeping it. \n`; @@ -150,7 +156,10 @@ const processStreams = (result, file, user_langs) => { } else { tracks.remove.push(streamIndex); response.preset += `-map -0:a:${streamIndex} `; - tracks.remLangs += `${languages.getName(stream.tags.language, 'en')}, `; + tracks.remLangs += `${languages.getName( + stream.tags.language, + 'en', + )}, `; } streamIndex += 1; } else { @@ -166,7 +175,7 @@ const tmdbApi = async (filename, api_key, axios) => { let fileName; // If filename begins with tt, it's already an imdb id if (filename) { - if (filename.substr(0, 2) === 'tt') { + if (filename.slice(0, 2) === 'tt') { fileName = filename; } else { const idRegex = /(tt\d{7,8})/; @@ -177,9 +186,14 @@ const tmdbApi = async (filename, api_key, axios) => { } if (fileName) { - const result = await axios.get(`https://api.themoviedb.org/3/find/${fileName}?api_key=` - + `${api_key}&language=en-US&external_source=imdb_id`) - .then((resp) => (resp.data.movie_results.length > 0 ? resp.data.movie_results[0] : resp.data.tv_results[0])); + const result = await axios + .get( + `https://api.themoviedb.org/3/find/${fileName}?api_key=` + + `${api_key}&language=en-US&external_source=imdb_id`, + ) + .then((resp) => (resp.data.movie_results.length > 0 + ? resp.data.movie_results[0] + : resp.data.tv_results[0])); if (!result) { response.infoLog += '☒No IMDB result was found. \n'; @@ -190,44 +204,24 @@ const tmdbApi = async (filename, api_key, axios) => { }; // eslint-disable-next-line consistent-return -const parseArrResponse = async (body, filePath, arr) => { +const parseArrResponse = (body, filePath, arr) => { // eslint-disable-next-line default-case switch (arr) { case 'radarr': - // filePath = file - for (let i = 0; i < body.length; i += 1) { - if (body[i].movieFile) { - if (body[i].movieFile.relativePath) { - if (body[i].movieFile.relativePath === filePath) { - return body[i]; - } - } - } - } - break; + return body.movie; case 'sonarr': - // filePath = directory the file is in - for (let i = 0; i < body.length; i += 1) { - if (body[i].path) { - const sonarrTemp = body[i].path.replace(/\\/g, '/').split('/'); - const sonarrFolder = sonarrTemp[sonarrTemp.length - 1]; - const tdarrTemp = filePath.split('/'); - const tdarrFolder = tdarrTemp[tdarrTemp.length - 2]; - if (sonarrFolder === tdarrFolder) { - return body[i]; - } - } - } + return body.series; } }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/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 + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // eslint-disable-next-line import/no-unresolved const axios = require('axios').default; + response.container = `.${file.container}`; let prio = ['radarr', 'sonarr']; let radarrResult = null; @@ -240,44 +234,56 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => { } } - for (let i = 0; i < prio.length; i += 1) { + const fileNameEncoded = encodeURIComponent(file.meta.FileName); + + // eslint-disable-next-line no-restricted-syntax + for (const arr of prio) { let imdbId; // eslint-disable-next-line default-case - switch (prio[i]) { + switch (arr) { case 'radarr': if (tmdbResult) break; if (inputs.radarr_api_key) { - radarrResult = await parseArrResponse( - await axios.get(`http://${inputs.radarr_url}/api/v3/movie?apiKey=${inputs.radarr_api_key}`) + radarrResult = parseArrResponse( + await axios + .get( + `http://${inputs.radarr_url}/api/v3/parse?apikey=${inputs.radarr_api_key}&title=${fileNameEncoded}`, + ) .then((resp) => resp.data), - file.meta.FileName, 'radarr', + fileNameEncoded, + 'radarr', ); if (radarrResult) { imdbId = radarrResult.imdbId; response.infoLog += `Grabbed ID (${imdbId}) from Radarr \n`; + // eslint-disable-next-line import/no-unresolved + const languages = require('@cospired/i18n-iso-languages'); + tmdbResult = { original_language: languages.getAlpha2Code(radarrResult.originalLanguage.name, 'en') }; } else { - response.infoLog += 'Couldn\'t grab ID from Radarr \n'; - imdbId = file.meta.FileName; + response.infoLog += "Couldn't grab ID from Radarr \n"; + imdbId = fileNameEncoded; } - tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios); } break; case 'sonarr': if (tmdbResult) break; if (inputs.sonarr_api_key) { - sonarrResult = await parseArrResponse( - await axios.get(`http://${inputs.sonarr_url}/api/series?apikey=${inputs.sonarr_api_key}`) + sonarrResult = parseArrResponse( + await axios.get( + `http://${inputs.sonarr_url}/api/v3/parse?apikey=${inputs.sonarr_api_key}&title=${fileNameEncoded}`, + ) .then((resp) => resp.data), - file.meta.Directory, 'sonarr', + file.meta.Directory, + 'sonarr', ); if (sonarrResult) { imdbId = sonarrResult.imdbId; response.infoLog += `Grabbed ID (${imdbId}) from Sonarr \n`; } else { - response.infoLog += 'Couldn\'t grab ID from Sonarr \n'; - imdbId = file.meta.FileName; + response.infoLog += "Couldn't grab ID from Sonarr \n"; + imdbId = fileNameEncoded; } tmdbResult = await tmdbApi(imdbId, inputs.api_key, axios); } @@ -285,21 +291,29 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => { } if (tmdbResult) { - const tracks = processStreams(tmdbResult, file, inputs.user_langs ? inputs.user_langs.split(',') : ''); + const tracks = processStreams( + tmdbResult, + file, + inputs.user_langs ? inputs.user_langs.split(',') : '', + ); if (tracks.remove.length > 0) { if (tracks.keep.length > 0) { - response.infoLog += `☑Removing tracks with languages: ${tracks.remLangs.slice(0, -2)}. \n`; + response.infoLog += `☑Removing tracks with languages: ${tracks.remLangs.slice( + 0, + -2, + )}. \n`; response.processFile = true; response.infoLog += '\n'; } else { - response.infoLog += '☒Cancelling plugin otherwise all audio tracks would be removed. \n'; + response.infoLog + += '☒Cancelling plugin otherwise all audio tracks would be removed. \n'; } } else { response.infoLog += '☒No audio tracks to be removed. \n'; } } else { - response.infoLog += '☒Couldn\'t find the IMDB id of this file. Skipping. \n'; + response.infoLog += "☒Couldn't find the IMDB id of this file. Skipping. \n"; } return response; }; diff --git a/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js b/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js index 76e51045b..235117c45 100644 --- a/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js +++ b/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js b/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js index 88bd3476c..3954b3ee7 100644 --- a/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js +++ b/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js b/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js index 8baf72e73..6470e3707 100644 --- a/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js +++ b/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js @@ -11,10 +11,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js b/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js index bf5c9a533..06a7ecee6 100644 --- a/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js +++ b/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js b/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js index e380b818a..1f55145f8 100644 --- a/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js +++ b/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js b/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js index fd2242d81..a9653b4ae 100644 --- a/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js +++ b/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var transcode = 0; //if this var changes to 1 the file will be transcoded diff --git a/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js b/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js index 6d236a506..f44c33738 100644 --- a/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js +++ b/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var transcode = 0; //if this var changes to 1 the file will be transcoded diff --git a/Community/Tdarr_Plugin_rr01_drpeppershaker_extract_subs_to_SRT.js b/Community/Tdarr_Plugin_rr01_drpeppershaker_extract_subs_to_SRT.js index 75d5e7744..b8b65eefa 100644 --- a/Community/Tdarr_Plugin_rr01_drpeppershaker_extract_subs_to_SRT.js +++ b/Community/Tdarr_Plugin_rr01_drpeppershaker_extract_subs_to_SRT.js @@ -35,10 +35,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); const fs = require('fs'); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object at some point in the function else plugin will fail. const response = { diff --git a/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js b/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js index 689d40307..b2b9d7d66 100644 --- a/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js +++ b/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_s7x8_winsome_h265.js b/Community/Tdarr_Plugin_s7x8_winsome_h265.js index b1b293f3c..2c1108a2d 100644 --- a/Community/Tdarr_Plugin_s7x8_winsome_h265.js +++ b/Community/Tdarr_Plugin_s7x8_winsome_h265.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js b/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js index d5d23f6ee..fd243f699 100644 --- a/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js +++ b/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js b/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js index f336a5ee9..11ed514f4 100644 --- a/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js +++ b/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_scha_rename_based_on_codec_schadi.js b/Community/Tdarr_Plugin_scha_rename_based_on_codec_schadi.js new file mode 100644 index 000000000..aab6e8144 --- /dev/null +++ b/Community/Tdarr_Plugin_scha_rename_based_on_codec_schadi.js @@ -0,0 +1,206 @@ +/* eslint-disable max-len */ + +// tdarrSkipTest +const details = () => ({ + id: 'Tdarr_Plugin_scha_rename_based_on_codec_schadi', + Stage: 'Post-processing', + Name: 'Rename based on codec Video and Audio', + Type: 'Video', + Operation: 'Transcode', + Description: ` + If the filename contains a codec information like h264, av1 or similar for video and AC3, AAC or trueHD \n\n + the plugin will read the codec info from the file and rename it accordingly. \n\n + It also takes care off addiotnal files deffined in the input Option.\n\n`, + Version: '1.00', + Tags: 'post-processing', + Inputs: [ + { + name: 'rename_audio', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: `Will Rename According to Audio Codec after x264 or x265. + \\nExample:\\n + true + \\nExample:\\n + false`, + }, + { + name: 'rename_video', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: `Will Rename According to Video Codec after x264 or x265. + \\nExample:\\n + true + \\nExample:\\n + false`, + }, + { + name: 'additional_extensions', + type: 'string', + defaultValue: '.nfo,.srt', + inputUI: { + type: 'text', + }, + tooltip: `Additional file extensions to rename (comma-separated). + \\nExample:\\n + .nfo,.srt`, + }, + ], +}); + +// eslint-disable-next-line no-unused-vars +const plugin = (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 fs = require('fs'); + const path = require('path'); + + const fileNameOld = file._id; + + const response = { + file, + removeFromDB: false, + updateDB: true, + infoLog: '', + processFile: false, + }; + + const codecMap = { + aac: 'AAC', + ac3: 'AC3', + av1: 'AV1', + avc: 'h264', + dts: 'DTS', + eac3: 'EAC3', + flac: 'FLAC', + hevc: 'h265', + mp2: 'MP2', + mp3: 'MP3', + mpeg2: 'MPEG2', + truehd: 'TrueHD', + x264: 'h264', + x265: 'h265', + h264: 'h264', + h265: 'h265', + // dts: 'DTS-X', + 'dts-hd ma': 'DTS-HD MA', + 'dts-es': 'DTS-HD ES', + 'dts-hd hra': 'DTS-HD HRA', + 'dts express ': 'DTS Express', + 'dts 96/24': 'DTS', + }; + + let firstVideoStreamCodec; + let firstAudioStreamCodec; + + const videoCodecRegex = /(h264|h265|x264|x265|avc|hevc|mpeg2|av1)/gi; + const audioCodecRegex = /(aac|ac3|eac3|flac|mp2|mp3|truehd|dts[-. ]hd[-. ]ma|dts[-. ]hd[-. ]es|dts[-. ]hd[-. ]hra|dts[-. ]express|dts)/gi; + + const videoStream = file.ffProbeData.streams.find((stream) => stream.codec_type === 'video'); + + if (videoStream && inputs.rename_video) { + const videoCodec = videoStream.codec_name.toLowerCase(); + firstVideoStreamCodec = videoCodec; + + if (videoCodec in codecMap) { + const renamedCodec = codecMap[videoCodec]; + // eslint-disable-next-line no-param-reassign + file._id = file._id.replace(videoCodecRegex, renamedCodec); + // eslint-disable-next-line no-param-reassign + file.file = file.file.replace(videoCodecRegex, renamedCodec); + } + } + + const audioStream = file.ffProbeData.streams.find((stream) => stream.codec_type === 'audio'); + + if (audioStream && inputs.rename_audio) { + const audioCodec = audioStream.codec_name.toLowerCase(); + firstAudioStreamCodec = audioCodec; + + if (audioCodec in codecMap) { + const renamedCodec = codecMap[audioCodec]; + // eslint-disable-next-line no-param-reassign + file._id = file._id.replace(audioCodecRegex, renamedCodec); + // eslint-disable-next-line no-param-reassign + file.file = file.file.replace(audioCodecRegex, renamedCodec); + } + } + + let additionalFilesCount = 0; // Counter for additional files found + + if ((audioStream && inputs.rename_audio) || (videoStream && inputs.rename_video)) { + const filename = path.basename(fileNameOld); + const JustName = path.parse(filename).name; + const popJustnamen = JustName.split('.'); + popJustnamen.splice(popJustnamen.length - 5); + const modJustname = popJustnamen.join('.'); + + const fileDir = path.dirname(fileNameOld); + const directoryPath = fileDir; + + const additionalExtensions = inputs.additional_extensions.split(','); + + const fileList = []; // Array to store the file names + const files = fs.readdirSync(directoryPath); + + files.forEach((supportFile) => { + fileList.push(supportFile); // Add all files to the fileList array + }); + + const extensionList = additionalExtensions.map((extension) => extension.trim()); // Remove leading/trailing spaces from extensions + const regex = new RegExp(`(${extensionList.join('|')})$`, 'i'); + + files.forEach((supportFile) => { + if (supportFile.startsWith(modJustname) && regex.test(supportFile)) { + const renamedFileWithVideoCodec = supportFile.replace(videoCodecRegex, codecMap[firstVideoStreamCodec]); + const renamedFileWithBothCodecs = renamedFileWithVideoCodec.replace(audioCodecRegex, codecMap[firstAudioStreamCodec]); + + fs.renameSync(`${directoryPath}/${supportFile}`, `${directoryPath}/${renamedFileWithBothCodecs}`, { + overwrite: true, + }); + + response.infoLog += `${directoryPath}/${supportFile} renamed to ${directoryPath}/${renamedFileWithBothCodecs}\n`; + additionalFilesCount += 1; // Increment the count for each additional file found + } + }); + + // const textFilePath = path.join(directoryPath, `${modJustname}.txt`); + // fs.writeFileSync(textFilePath, fileList.filter(file => file.startsWith(modJustname) && regex.test(file)).join('\n'), 'utf-8'); + } + + if (fileNameOld !== file._id) { + fs.renameSync(fileNameOld, file._id, { + overwrite: true, + }); + response.infoLog += `Renamed file to: ${file._id}\n`; + if (additionalFilesCount > 0) { + response.infoLog += `and: ${additionalFilesCount} additional Files!\n`; + } + return response; + } + + response.infoLog += 'Video File not renamed!\n'; + if (additionalFilesCount > 0) { + response.infoLog += `But: ${additionalFilesCount} additional Files!\n`; + } + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js b/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js index 00df33be6..261792d4c 100644 --- a/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js +++ b/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js b/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js index 9be9ed1f6..b8778d266 100644 --- a/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js +++ b/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js @@ -14,11 +14,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_tsld_filter_modified_date.js b/Community/Tdarr_Plugin_tsld_filter_modified_date.js new file mode 100644 index 000000000..945819702 --- /dev/null +++ b/Community/Tdarr_Plugin_tsld_filter_modified_date.js @@ -0,0 +1,56 @@ +const details = () => ({ + id: 'Tdarr_Plugin_tsld_filter_modified_date', + Stage: 'Pre-processing', + Name: 'Filter modified date', + Type: 'Video', + Operation: 'Filter', + Description: 'This plugin prevents processing files older than 30 days \n\n', + Version: '1.00', + Tags: '', + Inputs: [ + // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI + { + name: 'minModifiedDaysOld', + type: 'number', + defaultValue: 30, + inputUI: { + type: 'text', + }, + tooltip: `Enter minimum number of days since modified since now file must be. + + \\nExample:\\n + 365 + + \\nExample:\\n + 30`, + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + const lib = require('../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + inputs = lib.loadDefaultValues(inputs, details); + const response = { + processFile: true, + infoLog: '', + }; + // response.infoLog += `Filter preventing processing. File mod time ${file.statSync.mtimeMs}`; + // response.infoLog += ` Now ${Date.now()}`; + const age = Date.now() - file.statSync.mtimeMs; + const reqage = Number(inputs.minModifiedDaysOld) * 86400000; + // response.infoLog += ` Age ${age} Require Min Age: ${reqage}`; + if (reqage < age) { + response.infoLog += 'File modified date old enough. Moving to next plugin.'; + response.processFile = true; + } else { + response.infoLog += 'Skipping, file modified date not old enough'; + response.processFile = false; + } + + return response; +}; + +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js b/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js index 634c9d310..53505d504 100644 --- a/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js +++ b/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object 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 16def024a..6088c1332 100644 --- a/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js +++ b/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js @@ -151,10 +151,10 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); let crf; // default values that will be returned diff --git a/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js b/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js index 4afd5f320..efb064149 100644 --- a/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js +++ b/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js @@ -103,11 +103,11 @@ const details = () => { } } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); var transcode = 0 //if this var changes to 1 the file will be transcoded var subcli = `-c:s copy` diff --git a/Community/Tdarr_Plugin_x7ab_Remove_Subs.js b/Community/Tdarr_Plugin_x7ab_Remove_Subs.js index 7bd12ff3b..3fa5aa991 100644 --- a/Community/Tdarr_Plugin_x7ab_Remove_Subs.js +++ b/Community/Tdarr_Plugin_x7ab_Remove_Subs.js @@ -13,11 +13,11 @@ const details = () => { }; } -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); //Must return this object diff --git a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index f5fe57a35..17ff3266f 100644 --- a/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -11,10 +11,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: false, diff --git a/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js b/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js index 88fc14ee0..5b859ddaa 100644 --- a/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js +++ b/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js @@ -13,10 +13,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object 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 7cff56492..5d159172f 100644 --- a/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js +++ b/Community/Tdarr_Plugin_z18s_rename_files_based_on_codec.js @@ -15,11 +15,11 @@ const details = () => { }; }; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); try { var fs = require("fs"); 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 1a2f75da0..ece12d97e 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 @@ -11,10 +11,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); try { const fs = require('fs'); diff --git a/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js b/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js index fbc9de3b6..0a84a770e 100644 --- a/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js +++ b/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js @@ -13,10 +13,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object diff --git a/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js b/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js index 28ee57917..52c58acee 100644 --- a/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js +++ b/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js @@ -13,10 +13,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object diff --git a/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js b/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js index 597003f8e..7b7623cd3 100644 --- a/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js +++ b/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js @@ -15,10 +15,10 @@ const details = () => ({ }); // eslint-disable-next-line -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object diff --git a/Community/Tdarr_Plugin_z80t_keep_original_date.js b/Community/Tdarr_Plugin_z80t_keep_original_date.js index 936470519..e7000b2ac 100644 --- a/Community/Tdarr_Plugin_z80t_keep_original_date.js +++ b/Community/Tdarr_Plugin_z80t_keep_original_date.js @@ -1,6 +1,4 @@ module.exports.dependencies = [ - 'axios', - 'path-extra', 'touch', ]; @@ -14,107 +12,43 @@ const details = () => ({ Description: 'This plugin copies the original file dates and times to the transcoded file \n\n', Version: '1.10', Tags: 'post-processing,dates,date', - Inputs: [{ - name: 'server', - type: 'string', - defaultValue: '192.168.1.100', - inputUI: { - type: 'text', - }, - tooltip: `IP address or hostname of the server assigned to this node, will be used for API requests. - If you are running nodes within Docker you should use the server IP address rather than the name. - - \\nExample:\\n - tdarrserver - - \\nExample:\\n - 192.168.1.100`, - }, { - name: 'extensions', - type: 'string', - defaultValue: '', - inputUI: { - type: 'text', - }, - tooltip: `When files are trans-coded the file extension may change, - enter a list of extensions to try and match the original file with in the database after trans-coding. - Default is the list of container types from library settings. The list will be searched in order and - the extension of the original file will always be checked first before the list is used. - - \\nExample:\\n - mkv,mp4,avi`, - }, - { - name: 'log', - type: 'boolean', - defaultValue: false, - inputUI: { - type: 'dropdown', - options: [ - 'false', - 'true', - ], - }, - tooltip: `Write log entries to console.log. Default is false. + Inputs: [ + { + name: 'log', + type: 'boolean', + defaultValue: false, + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: `Write log entries to console.log. Default is false. \\nExample:\\n true`, - }, + }, ], }); -// eslint-disable-next-line no-unused-vars -const plugin = async (file, librarySettings, inputs, otherArguments) => { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); - // eslint-disable-next-line import/no-unresolved - const axios = require('axios'); - // eslint-disable-next-line import/no-unresolved + + // eslint-disable-next-line import/no-unresolved,import/no-extraneous-dependencies const touch = require('touch'); - // eslint-disable-next-line import/no-unresolved - const path = require('path-extra'); + const os = require('os'); + const fs = require('fs'); - function log(msg) { + const log = (msg) => { if (inputs.log === true) { // eslint-disable-next-line no-console console.log(msg); } - } - - async function getFileData(filePath, extensions, server) { - const originalExtension = path.extname(filePath).split('.')[1]; - if (extensions.indexOf(originalExtension) > -1) { - extensions.splice(extensions.indexOf(originalExtension), 1); - } - extensions.unshift(originalExtension); - let httpResponse = null; - - for (let i = 0; i < extensions.length; i += 1) { - const fileName = path.replaceExt(filePath, `.${extensions[i]}`); - log(`Fetching file object for ${fileName}...`); - // eslint-disable-next-line no-await-in-loop - httpResponse = await axios.post(`http://${server}:8265/api/v2/search-db`, { - data: { - string: fileName, - lessThanGB: 10000, - greaterThanGB: 0, - }, - }); - - if (httpResponse.status === 200) { - if (httpResponse.data.length > 0) { - log(`Got response for ${fileName}`); - return httpResponse; - } - log(`Response for ${fileName} is empty`); - } else { - log(`API request for ${filePath} failed.`); - } - } - log('Could not get file info from API, giving up.'); - return httpResponse; - } + }; const responseData = { file, @@ -124,30 +58,26 @@ const plugin = async (file, librarySettings, inputs, otherArguments) => { }; try { - if (!inputs.server || inputs.server.trim() === '') { - responseData.infoLog += 'Tdarr server name/IP not configured in library transcode options\n'; - return responseData; - } - - log('Waiting 5 seconds...'); - - let { extensions } = inputs; - if (!extensions || extensions.trim() === '') { - extensions = librarySettings.containerFilter; + log('Changing date...'); + + const { mtimeMs, ctimeMs } = otherArguments.originalLibraryFile.statSync; + if (os.platform() === 'win32') { + fs.utimes( + file._id, + ctimeMs / 1000, + mtimeMs / 1000, + (err) => { + if (err) { + log('Error updating modified date'); + } + }, + ); + } else { + touch.sync(file._id, { mtimeMs, force: true }); } - extensions = extensions.split(','); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - const response = await getFileData(file._id, extensions, inputs.server); - if (response.data.length > 0) { - log('Changing date...'); - touch.sync(file._id, { time: Date.parse(response.data[0].statSync.mtime), force: true }); - log('Done.'); - responseData.infoLog += 'File timestamps updated or match original file\n'; - return responseData; - } - responseData.infoLog += `Could not find file using API using ${inputs.server}\n`; + log('Done.'); + responseData.infoLog += 'File timestamps updated or match original file\n'; return responseData; } catch (err) { log(err); diff --git a/FlowPlugins/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.js new file mode 100644 index 000000000..97d804131 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.js @@ -0,0 +1,73 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Channel Count', + description: 'Check streams for specified channel count', + style: { + borderColor: 'orange', + }, + tags: 'audio', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'channelCount', + type: 'number', + defaultValue: '2', + inputUI: { + type: 'dropdown', + options: [ + '1', + '2', + '6', + '8', + ], + }, + tooltip: 'Specify channel count to check for', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File has stream with specified channel count', + }, + { + number: 2, + tooltip: 'File does not have stream with specified channel count', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var channelCount = Number(args.inputs.channelCount); + var hasSpecifiedChannelCount = false; + args.jobLog("Checking for ".concat(channelCount, " channels")); + if (Array.isArray((_b = (_a = args === null || args === void 0 ? void 0 : args.inputFileObj) === null || _a === void 0 ? void 0 : _a.ffProbeData) === null || _b === void 0 ? void 0 : _b.streams)) { + for (var i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + var stream = args.inputFileObj.ffProbeData.streams[i]; + args.jobLog("Stream ".concat(i, " has ").concat(stream.channels, " channels")); + if (stream.channels === channelCount) { + hasSpecifiedChannelCount = true; + } + } + } + else { + throw new Error('File has no stream data'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: hasSpecifiedChannelCount ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.js new file mode 100644 index 000000000..bd3d957ac --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.js @@ -0,0 +1,199 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Normalize Audio', + description: 'Normalize Audio', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'i', + type: 'string', + defaultValue: '-23.0', + inputUI: { + type: 'text', + }, + tooltip: "\"i\" value used in loudnorm pass \\n\n defaults to -23.0", + }, + { + name: 'lra', + type: 'string', + defaultValue: '7.0', + inputUI: { + type: 'text', + }, + tooltip: "Desired lra value. \\n Defaults to 7.0 \n ", + }, + { + name: 'tp', + type: 'string', + defaultValue: '-2.0', + inputUI: { + type: 'text', + }, + tooltip: "Desired \"tp\" value. \\n Defaults to -2.0 \n ", + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, loudNorm_i, lra, tp, container, outputFilePath, normArgs1, cli, res, lines, idx, parts, infoLine, loudNormValues, normArgs2, cli2, res2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + loudNorm_i = args.inputs.i; + lra = args.inputs.lra; + tp = args.inputs.tp; + container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + outputFilePath = "".concat((0, fileUtils_1.getPluginWorkDir)(args), "/").concat((0, fileUtils_1.getFileName)(args.inputFileObj._id), ".").concat(container); + normArgs1 = [ + '-i', + args.inputFileObj._id, + '-af', + "loudnorm=I=".concat(loudNorm_i, ":LRA=").concat(lra, ":TP=").concat(tp, ":print_format=json"), + '-f', + 'null', + 'NUL', + '-map', + '0', + '-c', + 'copy', + ]; + cli = new cliUtils_1.CLI({ + cli: args.ffmpegPath, + spawnArgs: normArgs1, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: '', + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 1: + res = _a.sent(); + if (res.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + lines = res.errorLogFull; + idx = -1; + // get last index of Parsed_loudnorm + lines.forEach(function (line, i) { + if (line.includes('Parsed_loudnorm')) { + idx = i; + } + }); + if (idx === -1) { + throw new Error('Failed to find loudnorm in report, please rerun'); + } + parts = lines[idx].split(']'); + parts.shift(); + infoLine = parts.join(']'); + infoLine = infoLine.split('\r\n').join('').split('\t').join(''); + loudNormValues = JSON.parse(infoLine); + args.jobLog("Loudnorm first pass values returned: \n".concat(JSON.stringify(loudNormValues))); + normArgs2 = [ + '-i', + args.inputFileObj._id, + '-map', + '0', + '-c', + 'copy', + '-c:a', + 'aac', + '-b:a', + '192k', + '-af', + "loudnorm=print_format=summary:linear=true:I=".concat(loudNorm_i, ":LRA=").concat(lra, ":TP=").concat(tp, ":") + + "measured_i=".concat(loudNormValues.input_i, ":") + + "measured_lra=".concat(loudNormValues.input_lra, ":") + + "measured_tp=".concat(loudNormValues.input_tp, ":") + + "measured_thresh=".concat(loudNormValues.input_thresh, ":offset=").concat(loudNormValues.target_offset, " "), + outputFilePath, + ]; + cli2 = new cliUtils_1.CLI({ + cli: args.ffmpegPath, + spawnArgs: normArgs2, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli2.runCli()]; + case 2: + res2 = _a.sent(); + if (res2.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + return [2 /*return*/, { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.js new file mode 100644 index 000000000..4e6227e80 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.js @@ -0,0 +1,101 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var classicPlugins_1 = require("../../../../FlowHelpers/1.0.0/classicPlugins"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Run Classic Filter Plugin', + description: 'Run one of Tdarr\'s classic plugins that has Operation: Filter', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'pluginSourceId', + type: 'string', + defaultValue: 'Community:Tdarr_Plugin_00td_filter_by_codec', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify the classic plugin ID', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File met conditions, would traditionally continue to next plugin in plugin stack', + }, + { + number: 2, + tooltip: 'File did not meet conditions, would traditionally break out of plugin stack', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, outcome, result, outputNumber; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return [4 /*yield*/, (0, classicPlugins_1.runClassicPlugin)(args, 'filter')]; + case 1: + outcome = _a.sent(); + result = outcome.result; + args.jobLog(JSON.stringify(result, null, 2)); + outputNumber = (result === null || result === void 0 ? void 0 : result.processFile) ? 1 : 2; + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.js new file mode 100644 index 000000000..5ae18c6af --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.js @@ -0,0 +1,238 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +var classicPlugins_1 = require("../../../../FlowHelpers/1.0.0/classicPlugins"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Run Classic Transcode Plugin', + description: 'Run one of Tdarr\'s classic plugins that has Operation: Transcode', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'pluginSourceId', + type: 'string', + defaultValue: 'Community:Tdarr_Plugin_MC93_Migz1FFMPEG', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify the classic plugin ID', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +var replaceContainer = function (filePath, container) { + var parts = filePath.split('.'); + parts[parts.length - 1] = container.split('.').join(''); + return parts.join('.'); +}; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, outcome, result, absolutePath, cacheFilePath, cliPath_1, customArgs, isCustomConfig, presetSplit, workerCommand, cliPath, cli, res; + var _a, _b, _c; + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return [4 /*yield*/, (0, classicPlugins_1.runClassicPlugin)(args, 'transcode')]; + case 1: + outcome = _d.sent(); + result = outcome.result, absolutePath = outcome.absolutePath; + cacheFilePath = outcome.cacheFilePath; + args.jobLog(JSON.stringify(result, null, 2)); + if (!result) { + args.jobLog('No result from classic plugin. Continuing to next flow plugin.'); + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + // --- Backwards compatibility------------ + if (result.handBrakeMode) { + result.handbrakeMode = result.handBrakeMode; + } + if (result.FFmpegMode) { + result.ffmpegMode = result.FFmpegMode; + } + //---------------------------------------- + if (result.ffmpegMode) { + result.cliToUse = 'ffmpeg'; + } + else if (result.handbrakeMode) { + result.cliToUse = 'handbrake'; + } + else if (typeof ((_a = result === null || result === void 0 ? void 0 : result.custom) === null || _a === void 0 ? void 0 : _a.cliPath) === 'string') { + cliPath_1 = result.custom.cliPath; + if (cliPath_1.toLowerCase().includes('ffmpeg')) { + result.cliToUse = 'ffmpeg'; + } + else if (cliPath_1.toLowerCase().includes('handbrake')) { + result.cliToUse = 'handbrake'; + } + else if (cliPath_1.toLowerCase().includes('editready')) { + result.cliToUse = 'editready'; + } + else if (cliPath_1.toLowerCase().includes('av1an')) { + result.cliToUse = 'av1an'; + } + } + result.workerLog = result.transcodeSettingsLog; + args.jobLog(JSON.stringify(result, null, 2)); + if (result.error) { + throw new Error("Plugin ".concat(absolutePath, " failed: ").concat(result.error)); + } + if (result.processFile !== true) { + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + customArgs = (_b = result === null || result === void 0 ? void 0 : result.custom) === null || _b === void 0 ? void 0 : _b.args; + isCustomConfig = (Array.isArray(customArgs) && customArgs.length > 0) + || (typeof customArgs === 'string' + // @ts-expect-error length + && customArgs.length + > 0); + if (!isCustomConfig) { + cacheFilePath = replaceContainer(cacheFilePath, result.container); + } + else { + // @ts-expect-error type + cacheFilePath = result.custom.outputPath; + } + if (result.preset.includes('')) { + presetSplit = result.preset.split(''); + } + else { + presetSplit = result.preset.split(','); + } + workerCommand = []; + cliPath = ''; + if (isCustomConfig) { + // @ts-expect-error cliPath + cliPath = (_c = result === null || result === void 0 ? void 0 : result.custom) === null || _c === void 0 ? void 0 : _c.cliPath; + if (Array.isArray(customArgs)) { + workerCommand = customArgs; + } + else { + workerCommand = __spreadArray([], args.deps.parseArgsStringToArgv(customArgs, '', ''), true); + } + } + else { + // working on windows with '` and spaces + // working on unix with ' + switch (true) { + case result.cliToUse === 'handbrake': + workerCommand = __spreadArray([ + '-i', + "".concat(args.inputFileObj._id), + '-o', + "".concat(cacheFilePath) + ], args.deps.parseArgsStringToArgv(result.preset, '', ''), true); + cliPath = "".concat(args.handbrakePath); + break; + case result.cliToUse === 'ffmpeg': + workerCommand = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], args.deps.parseArgsStringToArgv(presetSplit[0], '', ''), true), [ + '-i', + "".concat(args.inputFileObj._id) + ], false), args.deps.parseArgsStringToArgv(presetSplit[1], '', ''), true), [ + "".concat(cacheFilePath), + ], false); + cliPath = "".concat(args.ffmpegPath); + break; + default: + } + } + cli = new cliUtils_1.CLI({ + cli: cliPath, + spawnArgs: workerCommand, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: cacheFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 2: + res = _d.sent(); + if (res.cliExitCode !== 0) { + args.jobLog("Running ".concat(cliPath, " failed")); + throw new Error("Running ".concat(cliPath, " failed")); + } + args.logOutcome('tSuc'); + return [2 /*return*/, { + outputFileObj: { + _id: cacheFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.js new file mode 100644 index 000000000..e38eaa732 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: '10 Bit Video', + description: 'Set 10 Bit Video', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + for (var i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + var stream = args.variables.ffmpegCommand.streams[i]; + if (stream.codec_type === 'video') { + stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'main10'); + if (stream.outputArgs.includes('qsv')) { + stream.outputArgs.push('-vf', 'scale_qsv=format=p010le'); + } + else { + stream.outputArgs.push('-pix_fmt:v:{outputTypeIndex}', 'p010le'); + } + } + } + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.js new file mode 100644 index 000000000..20439f7e3 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Crop Black Bars', + description: 'Crop Black Bars', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.js new file mode 100644 index 000000000..d88dcdb33 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.js @@ -0,0 +1,65 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Custom Arguments', + description: 'Set FFmpeg custome input and output arguments', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'inputArguments', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Specify input arguments', + }, + { + name: 'outputArguments', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Specify output arguments', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var inputArguments = String(args.inputs.inputArguments); + var outputArguments = String(args.inputs.outputArguments); + if (inputArguments) { + (_a = args.variables.ffmpegCommand.overallInputArguments).push.apply(_a, inputArguments.split(' ')); + } + if (outputArguments) { + (_b = args.variables.ffmpegCommand.overallOuputArguments).push.apply(_b, outputArguments.split(' ')); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.js new file mode 100644 index 000000000..11a40664f --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Ensure Audio Stream', + description: 'Ensure that the file has an audio stream with set codec and channel count', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.js new file mode 100644 index 000000000..57d6fb29b --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.js @@ -0,0 +1,201 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Execute', + description: 'Execute the created FFmpeg command', + style: { + borderColor: 'green', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: 2, + icon: 'faPlay', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +var getOuputStreamIndex = function (streams, stream) { + var index = -1; + for (var idx = 0; idx < streams.length; idx += 1) { + if (!stream.removed) { + index += 1; + } + if (streams[idx].index === stream.index) { + break; + } + } + return index; +}; +var getOuputStreamTypeIndex = function (streams, stream) { + var index = -1; + for (var idx = 0; idx < streams.length; idx += 1) { + if (!stream.removed && streams[idx].codec_type === stream.codec_type) { + index += 1; + } + if (streams[idx].index === stream.index) { + break; + } + } + return index; +}; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, cliArgs, _a, shouldProcess, streams, inputArgs, _loop_1, i, idx, outputFilePath, spawnArgs, cli, res; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + cliArgs = []; + cliArgs.push('-y'); + cliArgs.push('-i'); + cliArgs.push(args.inputFileObj._id); + _a = args.variables.ffmpegCommand, shouldProcess = _a.shouldProcess, streams = _a.streams; + if (args.variables.ffmpegCommand.overallInputArguments.length > 0) { + shouldProcess = true; + } + inputArgs = __spreadArray([], args.variables.ffmpegCommand.overallInputArguments, true); + streams = streams.filter(function (stream) { + if (stream.removed) { + shouldProcess = true; + } + return !stream.removed; + }); + _loop_1 = function (i) { + var stream = streams[i]; + stream.outputArgs = stream.outputArgs.map(function (arg) { + if (arg.includes('{outputIndex}')) { + // eslint-disable-next-line no-param-reassign + arg = arg.replace('{outputIndex}', String(getOuputStreamIndex(streams, stream))); + } + if (arg.includes('{outputTypeIndex}')) { + // eslint-disable-next-line no-param-reassign + arg = arg.replace('{outputTypeIndex}', String(getOuputStreamTypeIndex(streams, stream))); + } + return arg; + }); + cliArgs.push.apply(cliArgs, stream.mapArgs); + if (stream.outputArgs.length === 0) { + cliArgs.push("-c:".concat(getOuputStreamIndex(streams, stream)), 'copy'); + } + else { + cliArgs.push.apply(cliArgs, stream.outputArgs); + } + inputArgs.push.apply(inputArgs, stream.inputArgs); + }; + for (i = 0; i < streams.length; i += 1) { + _loop_1(i); + } + idx = cliArgs.indexOf('-i'); + cliArgs.splice.apply(cliArgs, __spreadArray([idx, 0], inputArgs, false)); + if (args.variables.ffmpegCommand.overallOuputArguments.length > 0) { + cliArgs.push.apply(cliArgs, args.variables.ffmpegCommand.overallOuputArguments); + shouldProcess = true; + } + if (!shouldProcess) { + args.jobLog('No need to process file, already as required'); + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + outputFilePath = "".concat((0, fileUtils_1.getPluginWorkDir)(args), "/").concat((0, fileUtils_1.getFileName)(args.inputFileObj._id)) + + ".".concat(args.variables.ffmpegCommand.container); + cliArgs.push(outputFilePath); + spawnArgs = cliArgs.map(function (row) { return row.trim(); }).filter(function (row) { return row !== ''; }); + args.jobLog('Processing file'); + args.jobLog(JSON.stringify({ + spawnArgs: spawnArgs, + outputFilePath: outputFilePath, + })); + args.updateWorker({ + CLIType: args.ffmpegPath, + preset: spawnArgs.join(' '), + }); + cli = new cliUtils_1.CLI({ + cli: args.ffmpegPath, + spawnArgs: spawnArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 1: + res = _b.sent(); + if (res.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + args.logOutcome('tSuc'); + return [2 /*return*/, { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.js new file mode 100644 index 000000000..6cda6f2b0 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.js @@ -0,0 +1,42 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'HDR to SDR', + description: 'Convert HDR to SDR', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + if (stream.codec_type === 'video') { + stream.outputArgs.push('-vf', 'zscale=t=linear:npl=100,format=yuv420p'); + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.js new file mode 100644 index 000000000..26fcf9ca4 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Normalize Audio', + description: 'Normalize Audio', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.js new file mode 100644 index 000000000..dea876d78 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.js @@ -0,0 +1,43 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Remove Data Streams', + description: 'Remove Data Streams ', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + if (stream.codec_type === 'data') { + stream.removed = true; + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.js new file mode 100644 index 000000000..81e5294fc --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.js @@ -0,0 +1,100 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Remove Stream By Property', + description: 'Remove Stream By Property', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'propertyToCheck', + type: 'string', + defaultValue: 'codec_name', + inputUI: { + type: 'text', + }, + tooltip: "\n Enter one stream property to check.\n \n \\nExample:\\n\n codec_name\n\n \\nExample:\\n\n tags.language\n ", + }, + { + name: 'valuesToRemove', + type: 'string', + defaultValue: 'aac', + inputUI: { + type: 'text', + }, + tooltip: "\n Enter values of the property above to remove. For example, if removing by codec_name, could enter ac3,aac:\n \n \\nExample:\\n\n ac3,aac\n ", + }, + { + name: 'condition', + type: 'string', + defaultValue: 'includes', + inputUI: { + type: 'dropdown', + options: [ + 'includes', + 'not_includes', + ], + }, + tooltip: "\n Specify whether to remove streams that include or do not include the values above.\n ", + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var propertyToCheck = String(args.inputs.propertyToCheck).trim(); + var valuesToRemove = String(args.inputs.valuesToRemove).trim().split(','); + var condition = String(args.inputs.condition); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + var _a; + var target = ''; + if (propertyToCheck.includes('.')) { + var parts = propertyToCheck.split('.'); + target = (_a = stream[parts[0]]) === null || _a === void 0 ? void 0 : _a[parts[1]]; + } + else { + target = stream[propertyToCheck]; + } + if (target) { + var prop = String(target).toLowerCase(); + for (var i = 0; i < valuesToRemove.length; i += 1) { + var val = valuesToRemove[i].toLowerCase(); + var prefix = "Removing stream index ".concat(stream.index, " because ").concat(propertyToCheck, " of ").concat(prop); + if (condition === 'includes' && prop.includes(val)) { + args.jobLog("".concat(prefix, " includes ").concat(val, "\n")); + // eslint-disable-next-line no-param-reassign + stream.removed = true; + } + else if (condition === 'not_includes' && !prop.includes(val)) { + args.jobLog("".concat(prefix, " not_includes ").concat(val, "\n")); + // eslint-disable-next-line no-param-reassign + stream.removed = true; + } + } + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.js new file mode 100644 index 000000000..49b4a0b9f --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.js @@ -0,0 +1,43 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Remove Subtitles', + description: 'Remove subtitles from the file', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + if (stream.codec_type === 'subtitle') { + stream.removed = true; + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.js new file mode 100644 index 000000000..e1aa8f6d8 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.js @@ -0,0 +1,173 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Reorder Streams', + description: 'Reorder Streams', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'processOrder', + type: 'string', + defaultValue: 'codecs,channels,languages,streamTypes', + inputUI: { + type: 'text', + }, + tooltip: "Specify the process order.\nFor example, if 'languages' is first, the streams will be ordered based on that first.\nSo put the most important properties last.\nThe default order is suitable for most people.\n\n \\nExample:\\n\n codecs,channels,languages,streamTypes\n ", + }, + { + name: 'languages', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: "Specify the language tags order, separated by commas. Leave blank to disable.\n \\nExample:\\n\n eng,fre\n ", + }, + { + name: 'channels', + type: 'string', + defaultValue: '7.1,5.1,2,1', + inputUI: { + type: 'text', + }, + tooltip: "Specify the channels order, separated by commas. Leave blank to disable.\n \n \\nExample:\\n\n 7.1,5.1,2,1", + }, + { + name: 'codecs', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: "Specify the codec order, separated by commas. Leave blank to disable.\n \n \\nExample:\\n\n aac,ac3", + }, + { + name: 'streamTypes', + type: 'string', + defaultValue: 'video,audio,subtitle', + inputUI: { + type: 'text', + }, + tooltip: "Specify the streamTypes order, separated by commas. Leave blank to disable.\n \\nExample:\\n\n video,audio,subtitle\n ", + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var streams = JSON.parse(JSON.stringify(args.variables.ffmpegCommand.streams)); + var originalStreams = JSON.stringify(streams); + streams.forEach(function (stream, index) { + // eslint-disable-next-line no-param-reassign + stream.typeIndex = index; + }); + var sortStreams = function (sortType) { + var items = sortType.inputs.split(','); + items.reverse(); + for (var i = 0; i < items.length; i += 1) { + var matchedStreams = []; + for (var j = 0; j < streams.length; j += 1) { + if (String(sortType.getValue(streams[j])) === String(items[i])) { + if (streams[j].codec_long_name + && (streams[j].codec_long_name.includes('image') + || streams[j].codec_name.includes('png'))) { + // do nothing, ffmpeg bug, doesn't move image streams + } + else { + matchedStreams.push(streams[j]); + streams.splice(j, 1); + j -= 1; + } + } + } + streams = matchedStreams.concat(streams); + } + }; + var processOrder = String(args.inputs.processOrder); + var _a = args.inputs, languages = _a.languages, codecs = _a.codecs, channels = _a.channels, streamTypes = _a.streamTypes; + var sortTypes = { + languages: { + getValue: function (stream) { + var _a; + if ((_a = stream === null || stream === void 0 ? void 0 : stream.tags) === null || _a === void 0 ? void 0 : _a.language) { + return stream.tags.language; + } + return ''; + }, + inputs: languages, + }, + codecs: { + getValue: function (stream) { + try { + return stream.codec_name; + } + catch (err) { + // err + } + return ''; + }, + inputs: codecs, + }, + channels: { + getValue: function (stream) { + var chanMap = { + 8: '7.1', + 6: '5.1', + 2: '2', + 1: '1', + }; + if ((stream === null || stream === void 0 ? void 0 : stream.channels) && chanMap[stream.channels]) { + return chanMap[stream.channels]; + } + return ''; + }, + inputs: channels, + }, + streamTypes: { + getValue: function (stream) { + if (stream.codec_type) { + return stream.codec_type; + } + return ''; + }, + inputs: streamTypes, + }, + }; + var processOrderArr = processOrder.split(','); + for (var k = 0; k < processOrderArr.length; k += 1) { + if (sortTypes[processOrderArr[k]] && sortTypes[processOrderArr[k]].inputs) { + sortStreams(sortTypes[processOrderArr[k]]); + } + } + if (JSON.stringify(streams) !== originalStreams) { + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.shouldProcess = true; + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.streams = streams; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.js new file mode 100644 index 000000000..6065dfc80 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.js @@ -0,0 +1,104 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Set Container', + description: 'Set the container of the output file', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'container', + type: 'string', + defaultValue: 'mkv', + inputUI: { + type: 'dropdown', + options: [ + 'mkv', + 'mp4', + ], + }, + tooltip: 'Specify the container to use', + }, + { + name: 'forceConform', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: "\nSpecify if you want to force conform the file to the new container,\nThis is useful if not all streams are supported by the new container. \nFor example mkv does not support data streams.\n ", + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var newContainer = String(args.inputs.container); + var forceConform = args.inputs.forceConform; + if ((0, fileUtils_1.getContainer)(args.inputFileObj._id) !== newContainer) { + args.variables.ffmpegCommand.container = newContainer; + args.variables.ffmpegCommand.shouldProcess = true; + if (forceConform === true) { + for (var i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + var stream = args.variables.ffmpegCommand.streams[i]; + try { + var codecType = stream.codec_type.toLowerCase(); + var codecName = stream.codec_name.toLowerCase(); + if (newContainer === 'mkv') { + if (codecType === 'data' + || [ + 'mov_text', + 'eia_608', + 'timed_id3', + ].includes(codecName)) { + stream.removed = true; + } + } + if (newContainer === 'mp4') { + if ([ + 'hdmv_pgs_subtitle', + 'eia_608', + 'timed_id3', + 'subrip', + ].includes(codecName)) { + stream.removed = true; + } + } + } + catch (err) { + // Error + } + } + } + } + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.js new file mode 100644 index 000000000..210686e02 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Set Video Framerate', + description: 'Set Video Framerate. If the original framerate is lower than the' + + ' specified framerate, the original framerate will be used.', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'framerate', + type: 'number', + defaultValue: '30', + inputUI: { + type: 'text', + }, + tooltip: 'Specify framerate value', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var desiredFrameRate = Number(args.inputs.framerate); + args.jobLog("Desired framerate: ".concat(desiredFrameRate)); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + if (stream.codec_type === 'video') { + var fileFramerateUsed = false; + if (stream.avg_frame_rate) { + var parts = stream.avg_frame_rate.split('/'); + if (parts.length === 2) { + var numerator = parseInt(parts[0], 10); + var denominator = parseInt(parts[1], 10); + if (numerator > 0 && denominator > 0) { + var fileFramerate = numerator / denominator; + args.jobLog("File framerate: ".concat(fileFramerate)); + if (fileFramerate < desiredFrameRate) { + args.jobLog('File framerate is lower than desired framerate. Using file framerate.'); + stream.outputArgs.push('-r', "".concat(String(fileFramerate))); + fileFramerateUsed = true; + } + else { + args.jobLog('File framerate is greater than desired framerate. Using desired framerate.'); + } + } + } + } + if (!fileFramerateUsed) { + args.jobLog('Using desired framerate.'); + stream.outputArgs.push('-r', "".concat(String(desiredFrameRate))); + } + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.js new file mode 100644 index 000000000..8606365ab --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.js @@ -0,0 +1,85 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Set Video Resolution', + description: 'Change video resolution', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'targetResolution', + type: 'string', + defaultValue: '1080p', + inputUI: { + type: 'dropdown', + options: [ + '480p', + '720p', + '1080p', + '1440p', + '4KUHD', + ], + }, + tooltip: 'Specify the codec to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +var getVfScale = function (targetResolution) { + switch (targetResolution) { + case '480p': + return ['-vf', 'scale=720:-2']; + case '576p': + return ['-vf', 'scale=720:-2']; + case '720p': + return ['-vf', 'scale=1280:-2']; + case '1080p': + return ['-vf', 'scale=1920:-2']; + case '1440p': + return ['-vf', 'scale=2560:-2']; + case '4KUHD': + return ['-vf', 'scale=3840:-2']; + default: + return ['-vf', 'scale=1920:-2']; + } +}; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + for (var i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + var stream = args.variables.ffmpegCommand.streams[i]; + if (stream.codec_type === 'video') { + var targetResolution = String(args.inputs.targetResolution); + if (targetResolution !== args.inputFileObj.video_resolution) { + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.shouldProcess = true; + var scaleArgs = getVfScale(targetResolution); + (_a = stream.outputArgs).push.apply(_a, scaleArgs); + } + } + } + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.js new file mode 100644 index 000000000..992a31f8f --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.js @@ -0,0 +1,54 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Set Video Bitrate', + description: 'Set Video Bitrate', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'bitrate', + type: 'string', + defaultValue: '5000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify bitrate in kbps', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + args.variables.ffmpegCommand.streams.forEach(function (stream) { + if (stream.codec_type === 'video') { + var ffType = (0, fileUtils_1.getFfType)(stream.codec_type); + stream.outputArgs.push("-b:".concat(ffType, ":{outputTypeIndex}"), "".concat(String(args.inputs.bitrate), "k")); + } + }); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.js new file mode 100644 index 000000000..45ab166ea --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.js @@ -0,0 +1,228 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var hardwareUtils_1 = require("../../../../FlowHelpers/1.0.0/hardwareUtils"); +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Set Video Encoder', + description: 'Set the video encoder for all streams', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'outputCodec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + // 'vp9', + 'h264', + // 'vp8', + 'av1', + ], + }, + tooltip: 'Specify codec of the output file', + }, + { + name: 'ffmpegPreset', + type: 'string', + defaultValue: 'fast', + inputUI: { + type: 'dropdown', + options: [ + 'veryslow', + 'slower', + 'slow', + 'medium', + 'fast', + 'faster', + 'veryfast', + 'superfast', + 'ultrafast', + ], + }, + tooltip: 'Specify ffmpeg preset', + }, + { + name: 'ffmpegQuality', + type: 'number', + defaultValue: '25', + inputUI: { + type: 'text', + }, + tooltip: 'Specify ffmpeg quality', + }, + { + name: 'hardwareEncoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to use hardware encoding if available', + }, + { + name: 'hardwareType', + type: 'string', + defaultValue: 'auto', + inputUI: { + type: 'dropdown', + options: [ + 'auto', + 'nvenc', + 'qsv', + 'vaapi', + 'videotoolbox', + ], + }, + tooltip: 'Specify codec of the output file', + }, + { + name: 'hardwareDecoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to use hardware decoding if available', + }, + { + name: 'forceEncoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to force encoding if stream already has the target codec', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, hardwareDecoding, hardwareType, i, stream, targetCodec, ffmpegPreset, ffmpegQuality, forceEncoding, hardwarEncoding, encoderProperties; + var _a, _b; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + hardwareDecoding = args.inputs.hardwareDecoding === true; + hardwareType = String(args.inputs.hardwareType); + args.variables.ffmpegCommand.hardwareDecoding = hardwareDecoding; + i = 0; + _c.label = 1; + case 1: + if (!(i < args.variables.ffmpegCommand.streams.length)) return [3 /*break*/, 4]; + stream = args.variables.ffmpegCommand.streams[i]; + if (!(stream.codec_type === 'video')) return [3 /*break*/, 3]; + targetCodec = String(args.inputs.outputCodec); + ffmpegPreset = String(args.inputs.ffmpegPreset); + ffmpegQuality = String(args.inputs.ffmpegQuality); + forceEncoding = args.inputs.forceEncoding === true; + hardwarEncoding = args.inputs.hardwareEncoding === true; + if (!(forceEncoding + || stream.codec_name !== targetCodec)) return [3 /*break*/, 3]; + args.variables.ffmpegCommand.shouldProcess = true; + return [4 /*yield*/, (0, hardwareUtils_1.getEncoder)({ + targetCodec: targetCodec, + hardwareEncoding: hardwarEncoding, + hardwareType: hardwareType, + args: args, + })]; + case 2: + encoderProperties = _c.sent(); + stream.outputArgs.push('-c:{outputIndex}', encoderProperties.encoder); + if (encoderProperties.isGpu) { + stream.outputArgs.push('-qp', ffmpegQuality); + } + else { + stream.outputArgs.push('-crf', ffmpegQuality); + } + if (targetCodec !== 'av1' && ffmpegPreset) { + stream.outputArgs.push('-preset', ffmpegPreset); + } + if (hardwareDecoding) { + (_a = stream.inputArgs).push.apply(_a, encoderProperties.inputArgs); + } + if (encoderProperties.outputArgs) { + (_b = stream.outputArgs).push.apply(_b, encoderProperties.outputArgs); + } + _c.label = 3; + case 3: + i += 1; + return [3 /*break*/, 1]; + case 4: return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.js new file mode 100644 index 000000000..d00a4acd8 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.js @@ -0,0 +1,65 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Begin Command', + description: 'Begin creating the FFmpeg command for the current working file.' + + ' Should be used before any other FFmpeg command plugins.', + style: { + borderColor: 'green', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: 1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + var ffmpegCommand = { + inputFiles: [], + streams: JSON.parse(JSON.stringify(args.inputFileObj.ffProbeData.streams)).map(function (stream) { return (__assign(__assign({}, stream), { removed: false, mapArgs: [ + '-map', + "0:".concat(stream.index), + ], inputArgs: [], outputArgs: [] })); }), + container: container, + hardwareDecoding: false, + shouldProcess: false, + overallInputArguments: [], + overallOuputArguments: [], + }; + args.variables.ffmpegCommand = ffmpegCommand; + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.js new file mode 100644 index 000000000..a7dcf0123 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.js @@ -0,0 +1,82 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fs_1 = __importDefault(require("fs")); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check File Exists', + description: 'Check file Exists', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'fileToCheck', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '${fileName}_720p.${container}', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify file to check using templating e.g. ${fileName}_720p.${container}', + }, + { + name: 'directory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify directory to check. Leave blank to use working directory.' + + ' Put below Input File plugin to check original file directory.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File exists', + }, + { + number: 2, + tooltip: 'File does not exist', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var directory = String(args.inputs.directory).trim() || (0, fileUtils_1.getFileAbosluteDir)(args.inputFileObj._id); + var fileName = (0, fileUtils_1.getFileName)(args.inputFileObj._id); + var fileToCheck = String(args.inputs.fileToCheck).trim(); + fileToCheck = fileToCheck.replace(/\${fileName}/g, fileName); + fileToCheck = fileToCheck.replace(/\${container}/g, (0, fileUtils_1.getContainer)(args.inputFileObj._id)); + fileToCheck = "".concat(directory, "/").concat(fileToCheck); + var fileExists = false; + if (fs_1.default.existsSync(fileToCheck)) { + fileExists = true; + args.jobLog("File exists: ".concat(fileToCheck)); + } + else { + args.jobLog("File does not exist: ".concat(fileToCheck)); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: fileExists ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.js new file mode 100644 index 000000000..1311d3a40 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check File Extension', + description: 'Check file extension', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'extensions', + type: 'string', + defaultValue: 'mkv,mp4', + inputUI: { + type: 'text', + }, + tooltip: 'A comma separated list of extensions to check', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File is one of extensions', + }, + { + number: 2, + tooltip: 'File is not one of extensions', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var extensions = String(args.inputs.extensions); + var extensionArray = extensions.trim().split(','); + var extension = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + var extensionMatch = false; + if (extensionArray.includes(extension)) { + extensionMatch = true; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: extensionMatch ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.js new file mode 100644 index 000000000..dd6095258 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check File Medium', + description: 'Check if file is video, audio or other type of file', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File medium is a Video', + }, + { + number: 2, + tooltip: 'File medium is an Audio', + }, + { + number: 3, + tooltip: 'File medium is Other', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var outputNumber = 1; + switch (args.inputFileObj.fileMedium) { + case 'video': + outputNumber = 1; + break; + case 'audio': + outputNumber = 2; + break; + case 'other': + outputNumber = 3; + break; + default: + throw new Error('File has no fileMedium!'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.js new file mode 100644 index 000000000..692f6b717 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.js @@ -0,0 +1,63 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check File Name Includes', + description: 'Check if a file name includes specific terms. Only needs to match one term', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'terms', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '_720p,_1080p', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify terms to check for in file name using comma seperated list e.g. _720p,_1080p', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File name contains terms', + }, + { + number: 2, + tooltip: 'File name does not contains terms', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var fileName = "".concat((0, fileUtils_1.getFileName)(args.inputFileObj._id), ".").concat((0, fileUtils_1.getContainer)(args.inputFileObj._id)); + var terms = String(args.inputs.terms).trim().split(','); + var containsTerms = false; + for (var i = 0; i < terms.length; i++) { + if (fileName.includes(terms[i])) { + containsTerms = true; + break; + } + } + return { + outputFileObj: args.inputFileObj, + outputNumber: containsTerms ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.js new file mode 100644 index 000000000..72dcfd709 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.js @@ -0,0 +1,94 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check File Size', + description: 'Check size of working file', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'GB', + inputUI: { + type: 'dropdown', + options: [ + 'B', + 'KB', + 'MB', + 'GB', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isWithinRange = false; + var greaterThanBytes = Number(args.inputs.greaterThan); + var lessThanBytes = Number(args.inputs.lessThan); + var fileSizeBytes = args.inputFileObj.file_size * 1000 * 1000; + if (args.inputs.unit === 'KB') { + greaterThanBytes *= 1000; + lessThanBytes *= 1000; + } + else if (args.inputs.unit === 'MB') { + greaterThanBytes *= 1000000; + lessThanBytes *= 1000000; + } + else if (args.inputs.unit === 'GB') { + greaterThanBytes *= 1000000000; + lessThanBytes *= 1000000000; + } + if (fileSizeBytes >= greaterThanBytes && fileSizeBytes <= lessThanBytes) { + isWithinRange = true; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.js new file mode 100644 index 000000000..6e2b40906 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Compare File Duration Ratio', + description: 'Compare file duration ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '99.5', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound.' + + 'Default value is 99.5% so new file duration must be at least 40% of original file duration.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '100.5', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 100.5% so new file duration must be at most 100.5% of original file duration.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file duration % is within range', + }, + { + number: 2, + tooltip: 'Working file duration % is smaller than lower bound', + }, + { + number: 3, + tooltip: 'Working file duration % is larger than upper bound', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var getData = function (obj) { + var _a, _b; + try { + if ((_b = (_a = obj === null || obj === void 0 ? void 0 : obj.ffProbeData) === null || _a === void 0 ? void 0 : _a.format) === null || _b === void 0 ? void 0 : _b.duration) { + var dur = Number(obj.ffProbeData.format.duration); + if (dur > 0) { + return dur; + } + } + } + catch (err) { + // err + } + return 0; + }; + var newFileDuration = getData(args.inputFileObj); + var origFileDuration = getData(args.originalLibraryFile); + args.jobLog("newFileDuration: ".concat(newFileDuration)); + args.jobLog("origFileDuration: ".concat(origFileDuration)); + var greaterThanPerc = Number(args.inputs.greaterThan); + var lessThanPerc = Number(args.inputs.lessThan); + var ratio = (newFileDuration / origFileDuration) * 100; + var durationText = "New file has duration ".concat(newFileDuration.toFixed(3), " which is ").concat(ratio, "% ") + + "of original file duration: ".concat(origFileDuration.toFixed(3)); + var getBound = function (bound) { return (bound / 100) * origFileDuration; }; + var outputNumber = 1; + var errText = 'New file duration not within limits.'; + if (newFileDuration > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(durationText, ". upperBound is ").concat(lessThanPerc, "%")); + outputNumber = 3; + } + else if (newFileDuration < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(durationText, ". lowerBound is ").concat(greaterThanPerc, "%")); + outputNumber = 2; + } + else { + args.jobLog(durationText); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.js new file mode 100644 index 000000000..e403445b7 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.js @@ -0,0 +1,55 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Compare File Size', + description: 'Compare file size of working file compared to original file', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Working file is smaller than original file', + }, + { + number: 2, + tooltip: 'Working file is same size as original file', + }, + { + number: 3, + tooltip: 'Working file is larger than original file', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var outputNumber = 1; + if (args.inputFileObj.file_size < args.originalLibraryFile.file_size) { + outputNumber = 1; + } + else if (args.inputFileObj.file_size === args.originalLibraryFile.file_size) { + outputNumber = 2; + } + else if (args.inputFileObj.file_size > args.originalLibraryFile.file_size) { + outputNumber = 3; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.js new file mode 100644 index 000000000..ea8b567ec --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.js @@ -0,0 +1,84 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Compare File Size Ratio', + description: 'Compare file size ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '40', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound. ' + + 'Default value is 40% so new file size must be at least 40% of original file size.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '110', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 110% so new file size must be at most 110% of original file size.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file size % is within range', + }, + { + number: 2, + tooltip: 'Working file size % is not within range', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isWithinRange = false; + var newFileSizeBytes = args.inputFileObj.file_size; + var origFileSizeBytes = args.originalLibraryFile.file_size; + var greaterThanPerc = Number(args.inputs.greaterThan); + var lessThanPerc = Number(args.inputs.lessThan); + var ratio = (newFileSizeBytes / origFileSizeBytes) * 100; + var sizeText = "New file has size ".concat(newFileSizeBytes.toFixed(3), " MB which is ").concat(ratio, "% ") + + "of original file size: ".concat(origFileSizeBytes.toFixed(3), " MB"); + var getBound = function (bound) { return (bound / 100) * origFileSizeBytes; }; + var errText = 'New file size not within limits.'; + if (newFileSizeBytes > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(sizeText, ". upperBound is ").concat(lessThanPerc, "%")); + } + else if (newFileSizeBytes < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(sizeText, ". lowerBound is ").concat(greaterThanPerc, "%")); + } + else { + args.jobLog(sizeText); + isWithinRange = true; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.js new file mode 100644 index 000000000..7e03b9fe6 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.js @@ -0,0 +1,89 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Compare File Size Ratio', + description: 'Compare file size ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '40', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound.' + + 'Default value is 40% so new file size must be at least 40% of original file size.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '110', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 110% so new file size must be at most 110% of original file size.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file size % is within range', + }, + { + number: 2, + tooltip: 'Working file size % is smaller than lower bound', + }, + { + number: 3, + tooltip: 'Working file size % is larger than upper bound', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var newFileSizeBytes = args.inputFileObj.file_size; + var origFileSizeBytes = args.originalLibraryFile.file_size; + var greaterThanPerc = Number(args.inputs.greaterThan); + var lessThanPerc = Number(args.inputs.lessThan); + var ratio = (newFileSizeBytes / origFileSizeBytes) * 100; + var sizeText = "New file has size ".concat(newFileSizeBytes.toFixed(3), " MB which is ").concat(ratio, "% ") + + "of original file size: ".concat(origFileSizeBytes.toFixed(3), " MB"); + var getBound = function (bound) { return (bound / 100) * origFileSizeBytes; }; + var outputNumber = 1; + var errText = 'New file size not within limits.'; + if (newFileSizeBytes > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(sizeText, ". upperBound is ").concat(lessThanPerc, "%")); + outputNumber = 3; + } + else if (newFileSizeBytes < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog("".concat(errText, " ").concat(sizeText, ". lowerBound is ").concat(greaterThanPerc, "%")); + outputNumber = 2; + } + else { + args.jobLog(sizeText); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.js new file mode 100644 index 000000000..545a96a2f --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.js @@ -0,0 +1,171 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fs_1 = require("fs"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +var normJoinPath_1 = __importDefault(require("../../../../FlowHelpers/1.0.0/normJoinPath")); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Copy to Directory', + description: 'Copy the working file to a directory', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'outputDirectory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify ouput directory', + }, + { + name: 'keepRelativePath', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to keep the relative path', + }, + { + name: 'makeWorkingFile', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Make the copied file the working file', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, _a, keepRelativePath, makeWorkingFile, outputDirectory, originalFileName, newContainer, outputPath, subStem, ouputFilePath, workingFile; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + _a = args.inputs, keepRelativePath = _a.keepRelativePath, makeWorkingFile = _a.makeWorkingFile; + outputDirectory = String(args.inputs.outputDirectory); + originalFileName = (0, fileUtils_1.getFileName)(args.originalLibraryFile._id); + newContainer = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + outputPath = ''; + if (keepRelativePath) { + subStem = (0, fileUtils_1.getSubStem)({ + inputPathStem: args.librarySettings.folder, + inputPath: args.originalLibraryFile._id, + }); + outputPath = (0, normJoinPath_1.default)({ + upath: args.deps.upath, + paths: [ + outputDirectory, + subStem, + ], + }); + } + else { + outputPath = outputDirectory; + } + ouputFilePath = (0, normJoinPath_1.default)({ + upath: args.deps.upath, + paths: [ + outputPath, + "".concat(originalFileName, ".").concat(newContainer), + ], + }); + workingFile = args.inputFileObj._id; + if (makeWorkingFile) { + workingFile = ouputFilePath; + } + args.jobLog("Input path: ".concat(args.inputFileObj._id)); + args.jobLog("Output path: ".concat(outputPath)); + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping copy.'); + return [2 /*return*/, { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + args.deps.fsextra.ensureDirSync(outputPath); + return [4 /*yield*/, fs_1.promises.copyFile(args.inputFileObj._id, ouputFilePath)]; + case 1: + _b.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: workingFile, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.js new file mode 100644 index 000000000..a586cdc4a --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.js @@ -0,0 +1,114 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fs_1 = require("fs"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +var normJoinPath_1 = __importDefault(require("../../../../FlowHelpers/1.0.0/normJoinPath")); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Copy to Working Directory', + description: 'Copy the working file to the working directory of the Tdarr worker. ' + + 'Useful if you want to copy the file to the library cache before transcoding begins', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, originalFileName, newContainer, outputPath, ouputFilePath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + originalFileName = (0, fileUtils_1.getFileName)(args.originalLibraryFile._id); + newContainer = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + outputPath = args.workDir; + ouputFilePath = (0, normJoinPath_1.default)({ + upath: args.deps.upath, + paths: [ + outputPath, + "".concat(originalFileName, ".").concat(newContainer), + ], + }); + args.jobLog("Input path: ".concat(args.inputFileObj._id)); + args.jobLog("Output path: ".concat(outputPath)); + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping copy.'); + return [2 /*return*/, { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + args.deps.fsextra.ensureDirSync(outputPath); + return [4 /*yield*/, fs_1.promises.copyFile(args.inputFileObj._id, ouputFilePath)]; + case 1: + _a.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.js new file mode 100644 index 000000000..f5c7446f1 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Move To Directory', + description: 'Move working file to directory.', + style: { + borderColor: 'green', + opacity: 0.5, + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.js new file mode 100644 index 000000000..78954bdc2 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.js @@ -0,0 +1,157 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +var normJoinPath_1 = __importDefault(require("../../../../FlowHelpers/1.0.0/normJoinPath")); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Move To Directory', + description: 'Move working file to directory.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'outputDirectory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify ouput directory', + }, + { + name: 'keepRelativePath', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to keep the relative path', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, keepRelativePath, outputDirectory, originalFileName, newContainer, outputPath, subStem, ouputFilePath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + keepRelativePath = args.inputs.keepRelativePath; + outputDirectory = String(args.inputs.outputDirectory); + originalFileName = (0, fileUtils_1.getFileName)(args.originalLibraryFile._id); + newContainer = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + outputPath = ''; + if (keepRelativePath) { + subStem = (0, fileUtils_1.getSubStem)({ + inputPathStem: args.librarySettings.folder, + inputPath: args.originalLibraryFile._id, + }); + outputPath = (0, normJoinPath_1.default)({ + upath: args.deps.upath, + paths: [ + outputDirectory, + subStem, + ], + }); + } + else { + outputPath = outputDirectory; + } + ouputFilePath = (0, normJoinPath_1.default)({ + upath: args.deps.upath, + paths: [ + outputPath, + "".concat(originalFileName, ".").concat(newContainer), + ], + }); + args.jobLog("Input path: ".concat(args.inputFileObj._id)); + args.jobLog("Output path: ".concat(ouputFilePath)); + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping move.'); + return [2 /*return*/, { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + args.deps.fsextra.ensureDirSync(outputPath); + return [4 /*yield*/, (0, fileUtils_1.moveFileAndValidate)({ + inputPath: args.inputFileObj._id, + outputPath: ouputFilePath, + args: args, + })]; + case 1: + _a.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.js new file mode 100644 index 000000000..06e3df4d9 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.js @@ -0,0 +1,103 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Move To Original Directory', + description: 'Move working file original directory.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, fileName, container, outputDir, ouputFilePath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + fileName = (0, fileUtils_1.getFileName)(args.inputFileObj._id); + container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + outputDir = (0, fileUtils_1.getFileAbosluteDir)(args.originalLibraryFile._id); + ouputFilePath = "".concat(outputDir, "/").concat(fileName, ".").concat(container); + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping move.'); + return [2 /*return*/, { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + return [4 /*yield*/, (0, fileUtils_1.moveFileAndValidate)({ + inputPath: args.inputFileObj._id, + outputPath: ouputFilePath, + args: args, + })]; + case 1: + _a.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/renameFile/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/renameFile/1.0.0/index.js new file mode 100644 index 000000000..4fe65edad --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/renameFile/1.0.0/index.js @@ -0,0 +1,117 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Rename File', + description: 'Rename a file', + style: { + borderColor: 'green', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'fileRename', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '${fileName}_720p.${container}', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify file to check using templating e.g. ${fileName}_720p.${container}', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, fileName, newName, fileDir, newPath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + fileName = (0, fileUtils_1.getFileName)(args.inputFileObj._id); + newName = String(args.inputs.fileRename).trim(); + newName = newName.replace(/\${fileName}/g, fileName); + newName = newName.replace(/\${container}/g, (0, fileUtils_1.getContainer)(args.inputFileObj._id)); + fileDir = (0, fileUtils_1.getFileAbosluteDir)(args.inputFileObj._id); + newPath = "".concat(fileDir, "/").concat(newName); + if (args.inputFileObj._id === newPath) { + args.jobLog('Input and output path are the same, skipping rename.'); + return [2 /*return*/, { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + return [4 /*yield*/, (0, fileUtils_1.moveFileAndValidate)({ + inputPath: args.inputFileObj._id, + outputPath: newPath, + args: args, + })]; + case 1: + _a.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: newPath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.js new file mode 100644 index 000000000..4ec9bff01 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.js @@ -0,0 +1,130 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Replace Original File', + description: 'Replace the original file. If the file hasn\'t changed then no action is taken.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var fs, lib, currentPath, orignalFolder, fileName, container, newPath, newPathTmp; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + fs = require('fs'); + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + if (args.inputFileObj._id === args.originalLibraryFile._id + && args.inputFileObj.file_size === args.originalLibraryFile.file_size) { + args.jobLog('File has not changed, no need to replace file'); + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + args.jobLog('File has changed, replacing original file'); + currentPath = args.inputFileObj._id; + orignalFolder = (0, fileUtils_1.getFileAbosluteDir)(args.originalLibraryFile._id); + fileName = (0, fileUtils_1.getFileName)(args.inputFileObj._id); + container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + newPath = "".concat(orignalFolder, "/").concat(fileName, ".").concat(container); + newPathTmp = "".concat(newPath, ".tmp"); + args.jobLog(JSON.stringify({ + currentPath: currentPath, + newPath: newPath, + newPathTmp: newPathTmp, + })); + return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })]; + case 1: + _a.sent(); + return [4 /*yield*/, (0, fileUtils_1.moveFileAndValidate)({ + inputPath: currentPath, + outputPath: newPathTmp, + args: args, + })]; + case 2: + _a.sent(); + // delete original file + if (fs.existsSync(args.originalLibraryFile._id) + && args.originalLibraryFile._id !== currentPath) { + args.jobLog("Deleting original file:".concat(args.originalLibraryFile._id)); + fs.unlinkSync(args.originalLibraryFile._id); + } + return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 2000); })]; + case 3: + _a.sent(); + return [4 /*yield*/, (0, fileUtils_1.moveFileAndValidate)({ + inputPath: newPathTmp, + outputPath: newPath, + args: args, + })]; + case 4: + _a.sent(); + return [2 /*return*/, { + outputFileObj: { + _id: newPath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.js new file mode 100644 index 000000000..4db30c296 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.js @@ -0,0 +1,39 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Set Original File', + description: 'Set the working file to the original file', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: { + _id: args.originalLibraryFile._id, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/file/unpack/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/file/unpack/1.0.0/index.js new file mode 100644 index 000000000..d4bc94a3a --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/file/unpack/1.0.0/index.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Unpack File', + description: 'Unpack a file', + style: { + borderColor: 'green', + opacity: 0.5, + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.js new file mode 100644 index 000000000..173d49d56 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.js @@ -0,0 +1,172 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fs_1 = require("fs"); +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'HandBrake Custom Arguments', + description: 'HandBrake Custom Arguments', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'customArguments', + type: 'string', + defaultValue: '-Z "Fast 1080p30" --all-subtitles', + inputUI: { + type: 'text', + }, + tooltip: 'Specify HandBrake arguments', + }, + { + name: 'jsonPreset', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Paste a HandBrake JSON preset here. Leave blank to disable.', + }, + { + name: 'container', + type: 'string', + defaultValue: 'mkv', + inputUI: { + type: 'dropdown', + options: [ + 'original', + 'mkv', + 'mp4', + 'm4v', + 'avi', + 'mov', + 'mpg', + 'mpeg', + ], + }, + tooltip: 'Specify HandBrake arguments', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, customArguments, container, outputFilePath, presetString, cliArgs, presetPath, preset, cli, res; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + customArguments = String(args.inputs.customArguments); + container = String(args.inputs.container); + if (container === 'original') { + container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + } + outputFilePath = "".concat((0, fileUtils_1.getPluginWorkDir)(args), "/").concat((0, fileUtils_1.getFileName)(args.inputFileObj._id), ".").concat(container); + presetString = String(args.inputs.jsonPreset); + cliArgs = [ + '-i', + "".concat(args.inputFileObj._id), + '-o', + "".concat(outputFilePath), + ]; + presetPath = "".concat(args.workDir, "/preset.json"); + if (!(presetString.trim() !== '')) return [3 /*break*/, 2]; + preset = JSON.parse(presetString); + return [4 /*yield*/, fs_1.promises.writeFile(presetPath, JSON.stringify(preset, null, 2))]; + case 1: + _a.sent(); + cliArgs.push('--preset-import-file'); + cliArgs.push(presetPath); + cliArgs.push('-Z'); + cliArgs.push(preset.PresetList[0].PresetName); + return [3 /*break*/, 3]; + case 2: + cliArgs.push.apply(cliArgs, args.deps.parseArgsStringToArgv(customArguments, '', '')); + _a.label = 3; + case 3: + args.updateWorker({ + CLIType: args.handbrakePath, + preset: cliArgs.join(' '), + }); + cli = new cliUtils_1.CLI({ + cli: args.handbrakePath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 4: + res = _a.sent(); + if (res.cliExitCode !== 0) { + args.jobLog('Running HandBrake failed'); + throw new Error('Running HandBrake failed'); + } + args.logOutcome('tSuc'); + return [2 /*return*/, { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/input/inputFile/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/input/inputFile/1.0.0/index.js new file mode 100644 index 000000000..7e1903e1e --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/input/inputFile/1.0.0/index.js @@ -0,0 +1,190 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var fs_1 = require("fs"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Input File', + description: 'Start the flow with an input file', + style: { + borderColor: 'pink', + }, + tags: '', + isStartPlugin: true, + pType: 'start', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'fileAccessChecks', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Will check if input file and cache are readable and writable', + }, + { + name: 'pauseNodeIfAccessChecksFail', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'This will pause the node if the file access checks fail', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, orignalFolder, _a, fileAccessChecks, pauseNodeIfAccessChecksFail, nodeID, _b, serverIP, serverPort, url, pauseNode, checkReadWrite; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + orignalFolder = (0, fileUtils_1.getFileAbosluteDir)(args.originalLibraryFile._id); + _a = args.inputs, fileAccessChecks = _a.fileAccessChecks, pauseNodeIfAccessChecksFail = _a.pauseNodeIfAccessChecksFail; + nodeID = process.argv[8]; + _b = args.deps.configVars.config, serverIP = _b.serverIP, serverPort = _b.serverPort; + url = "http://".concat(serverIP, ":").concat(serverPort, "/api/v2/update-node"); + pauseNode = function () { return __awaiter(void 0, void 0, void 0, function () { + var requestConfig; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + args.jobLog('Pausing node'); + requestConfig = { + method: 'post', + url: url, + headers: {}, + data: { + data: { + nodeID: nodeID, + nodeUpdates: { + nodePaused: true, + }, + }, + }, + }; + return [4 /*yield*/, args.deps.axios(requestConfig)]; + case 1: + _a.sent(); + args.jobLog('Node paused'); + return [2 /*return*/]; + } + }); + }); }; + checkReadWrite = function (location) { return __awaiter(void 0, void 0, void 0, function () { + var err_1, err_2; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + _a.trys.push([0, 2, , 5]); + return [4 /*yield*/, fs_1.promises.access(location, fs_1.promises.constants.R_OK)]; + case 1: + _a.sent(); + return [3 /*break*/, 5]; + case 2: + err_1 = _a.sent(); + args.jobLog(JSON.stringify(err_1)); + if (!pauseNodeIfAccessChecksFail) return [3 /*break*/, 4]; + return [4 /*yield*/, pauseNode()]; + case 3: + _a.sent(); + _a.label = 4; + case 4: throw new Error("Location not readable:".concat(location)); + case 5: + _a.trys.push([5, 7, , 10]); + return [4 /*yield*/, fs_1.promises.access(location, fs_1.promises.constants.W_OK)]; + case 6: + _a.sent(); + return [3 /*break*/, 10]; + case 7: + err_2 = _a.sent(); + args.jobLog(JSON.stringify(err_2)); + if (!pauseNodeIfAccessChecksFail) return [3 /*break*/, 9]; + return [4 /*yield*/, pauseNode()]; + case 8: + _a.sent(); + _a.label = 9; + case 9: throw new Error("Location not writeable:".concat(location)); + case 10: return [2 /*return*/]; + } + }); + }); }; + if (!fileAccessChecks) return [3 /*break*/, 3]; + args.jobLog('Checking file access'); + return [4 /*yield*/, checkReadWrite(orignalFolder)]; + case 1: + _c.sent(); + return [4 /*yield*/, checkReadWrite(args.librarySettings.cache)]; + case 2: + _c.sent(); + return [3 /*break*/, 4]; + case 3: + args.jobLog('Skipping file access checks'); + _c.label = 4; + case 4: return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.js new file mode 100644 index 000000000..737604a6d --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.js @@ -0,0 +1,125 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Flow Variable', + description: 'Check Flow Variable', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'variable', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Variable to check. For example args.librarySettings._id', + }, + { + name: 'condition', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [ + '==', + '!=', + ], + }, + tooltip: 'Check condition', + }, + { + name: 'value', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Value of variable to check', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'The variable matches the condition', + }, + { + number: 2, + tooltip: 'The variable does not match the condition', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var variable = String(args.inputs.variable); + var condition = String(args.inputs.condition); + var value = String(args.inputs.value); + // variable could be e.g. args.librarySettings._id or args.inputFileObj._id + // condition could be e.g. '==' or '!=' + var variableParts = variable.split('.'); + var targetValue; + switch (variableParts.length) { + case 1: + targetValue = args; + break; + case 2: + // @ts-expect-error index + targetValue = args[variableParts[1]]; + break; + case 3: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]]; + break; + case 4: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]][variableParts[3]]; + break; + case 5: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]][variableParts[3]][variableParts[4]]; + break; + default: + throw new Error("Invalid variable: ".concat(variable)); + } + targetValue = String(targetValue); + var outputNumber = 1; + if (condition === '==') { + if (targetValue === value) { + args.jobLog("Variable ".concat(variable, " of value ").concat(targetValue, " matches condition ").concat(condition, " ").concat(value)); + outputNumber = 1; + } + else { + args.jobLog("Variable ".concat(variable, " of value ").concat(targetValue, " does not match condition ").concat(condition, " ").concat(value)); + outputNumber = 2; + } + } + else if (condition === '!=') { + if (targetValue !== value) { + args.jobLog("Variable ".concat(variable, " of value ").concat(targetValue, " matches condition ").concat(condition, " ").concat(value)); + outputNumber = 1; + } + else { + args.jobLog("Variable ".concat(variable, " of value ").concat(targetValue, " does not match condition ").concat(condition, " ").concat(value)); + outputNumber = 2; + } + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.js new file mode 100644 index 000000000..fbd3e377c --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.js @@ -0,0 +1,112 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var hardwareUtils_1 = require("../../../../FlowHelpers/1.0.0/hardwareUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Node Hardware Encoder', + description: "\n Check if node hardware encoder is available. Can also be used to check for specific hardware.\n For example:\n\n hevc_nvenc = Nvidia\n hevc_amf = AMD\n hevc_vaapi = Intel\n hevc_qsv = Intel\n hevc_videotoolbox = Apple\n ", + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'hardwareEncoder', + type: 'string', + defaultValue: 'hevc_nvenc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc_nvenc', + 'hevc_amf', + 'hevc_vaapi', + 'hevc_qsv', + 'hevc_videotoolbox', + ], + }, + tooltip: 'Specify hardware (based on encoder) to check for', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Node has hardware', + }, + { + number: 2, + tooltip: 'Node does not have hardware', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, hardwareEncoder, encoderProperties, nodeHasHardware; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + hardwareEncoder = args.inputs.hardwareEncoder; + return [4 /*yield*/, (0, hardwareUtils_1.getEncoder)({ + targetCodec: 'hevc', + hardwareEncoding: true, + hardwareType: 'auto', + args: args, + })]; + case 1: + encoderProperties = _a.sent(); + nodeHasHardware = encoderProperties.enabledDevices.some(function (row) { return row.encoder === hardwareEncoder; }); + args.jobLog("Node has hardwareEncoder ".concat(hardwareEncoder, ": ").concat(nodeHasHardware)); + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: nodeHasHardware ? 1 : 2, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/comment/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/comment/1.0.0/index.js new file mode 100644 index 000000000..50cdaeac8 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/comment/1.0.0/index.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Comment', + nameUI: { + type: 'textarea', + style: { + height: '250px', + }, + }, + description: "Add a comment to your flow. Can place anywhere and link together.\n Any file input into the comment will be passed straight through.", + style: { + borderColor: 'white', + borderRadius: '10px', + backgroundColor: '#043775', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faComment', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/failFlow/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/failFlow/1.0.0/index.js new file mode 100644 index 000000000..471a3cef8 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/failFlow/1.0.0/index.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Fail Flow', + description: 'Force the flow to fail and be move to the error table', + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faExclamationTriangle', + inputs: [], + outputs: [], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + throw new Error('Forcing flow to fail!'); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.js new file mode 100644 index 000000000..33795c354 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.js @@ -0,0 +1,43 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Go To Flow', + description: 'Go to a different flow', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'flowId', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify flow ID to go to', + }, + ], + outputs: [], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.js new file mode 100644 index 000000000..70b76e991 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.js @@ -0,0 +1,52 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Go To Flow', + description: 'Go to a different flow', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.14.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'flowId', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify flow ID to go to', + }, + { + name: 'pluginId', + type: 'string', + defaultValue: 'start', + inputUI: { + type: 'text', + }, + tooltip: 'Specify plugin ID to go to', + }, + ], + outputs: [], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.js new file mode 100644 index 000000000..8e3d98ef3 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.js @@ -0,0 +1,175 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var details = function () { return ({ + name: 'Notify Radarr or Sonarr', + description: 'Notify Radarr or Sonarr to refresh after file change', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faBell', + inputs: [ + { + name: 'arr', + type: 'string', + defaultValue: 'radarr', + inputUI: { + type: 'dropdown', + options: ['radarr', 'sonarr'], + }, + tooltip: 'Specify which arr to use', + }, + { + name: 'arr_api_key', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Input your arr api key here', + }, + { + name: 'arr_host', + type: 'string', + defaultValue: 'http://192.168.1.1:7878', + inputUI: { + type: 'text', + }, + tooltip: 'Input your arr host here.' + + '\\nExample:\\n' + + 'http://192.168.1.1:7878\\n' + + 'http://192.168.1.1:8989\\n' + + 'https://radarr.domain.com\\n' + + 'https://sonarr.domain.com\\n', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, _a, arr, arr_api_key, arr_host, meta, arrHost, headers, requestConfig, res, movieId, requestConfig2, requestConfig, res, seriesId, requestConfig2; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + _a = args.inputs, arr = _a.arr, arr_api_key = _a.arr_api_key; + arr_host = String(args.inputs.arr_host).trim(); + meta = args.inputFileObj.meta; + arrHost = arr_host.endsWith('/') ? arr_host.slice(0, -1) : arr_host; + headers = { + 'Content-Type': 'application/json', + 'X-Api-Key': arr_api_key, + Accept: 'application/json', + }; + args.jobLog('Going to force scan'); + if (!(arr === 'radarr')) return [3 /*break*/, 3]; + args.jobLog('Refreshing Radarr...'); + requestConfig = { + method: 'get', + url: "".concat(arrHost, "/api/v3/parse?title=").concat(encodeURIComponent((meta === null || meta === void 0 ? void 0 : meta.FileName) || '')), + headers: headers, + }; + return [4 /*yield*/, args.deps.axios(requestConfig)]; + case 1: + res = _b.sent(); + movieId = res.data.movie.movieFile.movieId; + requestConfig2 = { + method: 'post', + url: "".concat(arrHost, "/api/v3/command"), + headers: headers, + data: JSON.stringify({ + name: 'RefreshMovie', + movieIds: [movieId], + }), + }; + return [4 /*yield*/, args.deps.axios(requestConfig2)]; + case 2: + _b.sent(); + args.jobLog("\u2714 Refreshed movie ".concat(movieId, " in Radarr.")); + return [3 /*break*/, 7]; + case 3: + if (!(arr === 'sonarr')) return [3 /*break*/, 6]; + args.jobLog('Refreshing Sonarr...'); + requestConfig = { + method: 'get', + url: "".concat(arrHost, "/api/v3/parse?title=").concat(encodeURIComponent((meta === null || meta === void 0 ? void 0 : meta.FileName) || '')), + headers: headers, + }; + return [4 /*yield*/, args.deps.axios(requestConfig)]; + case 4: + res = _b.sent(); + seriesId = res.data.series.id; + requestConfig2 = { + method: 'post', + url: "".concat(arrHost, "/api/v3/command"), + headers: headers, + data: JSON.stringify({ + name: 'RefreshSeries', + seriesId: seriesId, + }), + }; + return [4 /*yield*/, args.deps.axios(requestConfig2)]; + case 5: + _b.sent(); + args.jobLog("\u2714 Refreshed series ".concat(seriesId, " in Sonarr.")); + return [3 /*break*/, 7]; + case 6: + args.jobLog('No arr specified in plugin inputs.'); + _b.label = 7; + case 7: return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.js new file mode 100644 index 000000000..57603b559 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'On Flow Error', + description: "Runs if an error occurs in this specific flow. \n Won't run if error occurs in after going to a different flow (unless that flow comes back to this one).", + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: 'onFlowError', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/requireReview/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/requireReview/1.0.0/index.js new file mode 100644 index 000000000..9f5c7777e --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/requireReview/1.0.0/index.js @@ -0,0 +1,37 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Require Review', + description: "Makes the flow pause.\n The file will stay in the staging section on the Tdarr tab until the user clicks the \"Reviewed\" button.\n ", + style: { + borderColor: 'yellow', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faHand', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.js new file mode 100644 index 000000000..fe9b528f6 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.js @@ -0,0 +1,39 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Reset Flow Error', + description: "After a flow error occurs, this plugin will reset the flow\nerror so that the flow will not go to error status at the end of the flow.", + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faUndo', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + // eslint-disable-next-line no-param-reassign + args.variables.flowFailed = false; + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.js new file mode 100644 index 000000000..16cd80601 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.js @@ -0,0 +1,102 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Run MKVPropEdit', + description: 'Run MKVPropEdit on a file to update metadata which' + + ' FFmpeg doesn\'t typically update such as stream bitrate.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, cliArgs, cli, res; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + cliArgs = [ + '--add-track-statistics-tags', + args.inputFileObj._id, + ]; + cli = new cliUtils_1.CLI({ + cli: args.mkvpropeditPath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: '', + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 1: + res = _a.sent(); + if (res.cliExitCode !== 0) { + args.jobLog('Running MKVPropEdit failed'); + throw new Error('Running MKVPropEdit failed'); + } + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.js new file mode 100644 index 000000000..2ecd43e7d --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.js @@ -0,0 +1,133 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Wait', + description: 'Wait for a specified amount of time before continuing to the next plugin', + style: { + borderColor: 'yellow', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faClock', + inputs: [ + { + name: 'amount', + type: 'string', + defaultValue: '1', + inputUI: { + type: 'text', + }, + tooltip: 'Specify the amount of time to wait', + }, + { + name: 'unit', + type: 'string', + defaultValue: 'seconds', + inputUI: { + type: 'dropdown', + options: [ + 'seconds', + 'minutes', + 'hours', + ], + }, + tooltip: 'Specify the unit of time to wait', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, _a, amount, unit, amountNum, multiplier, waitTime, finished, logWait; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + _a = args.inputs, amount = _a.amount, unit = _a.unit; + amountNum = Number(amount); + if (Number.isNaN(amountNum)) { + throw new Error('Amount must be a number'); + } + multiplier = 1; + if (unit === 'seconds') { + multiplier = 1000; + } + else if (unit === 'minutes') { + multiplier = 60000; + } + else if (unit === 'hours') { + multiplier = 3600000; + } + waitTime = amountNum * multiplier; + args.jobLog("Waiting for ".concat(amount, " ").concat(unit)); + args.jobLog("Waiting for ".concat(waitTime, " milliseconds")); + finished = false; + logWait = function () { + if (!finished) { + args.jobLog('Waiting...'); + setTimeout(logWait, 5000); + } + }; + logWait(); + return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, waitTime); })]; + case 1: + _b.sent(); + finished = true; + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/tools/webRequest/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/tools/webRequest/1.0.0/index.js new file mode 100644 index 000000000..a20adff4f --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/tools/webRequest/1.0.0/index.js @@ -0,0 +1,151 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Send Web Request', + description: 'Send Web Request', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'method', + type: 'string', + defaultValue: 'post', + inputUI: { + type: 'dropdown', + options: [ + 'get', + 'post', + 'put', + 'delete', + ], + }, + tooltip: 'Specify request method', + }, + { + name: 'requestUrl', + type: 'string', + defaultValue: 'http://example.com', + inputUI: { + type: 'text', + }, + tooltip: 'Specify request URL', + }, + { + name: 'requestHeaders', + type: 'string', + defaultValue: "{\n \"Content-Type\": \"application/json\"\n}", + inputUI: { + type: 'textarea', + style: { + height: '100px', + }, + }, + tooltip: 'Specify request URL', + }, + { + name: 'requestBody', + type: 'string', + defaultValue: "{\n \"test\": \"test\"\n}", + inputUI: { + type: 'textarea', + style: { + height: '100px', + }, + }, + tooltip: 'Specify request body', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, method, requestUrl, requestHeaders, requestBody, requestConfig, res, err_1; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + method = String(args.inputs.method); + requestUrl = String(args.inputs.requestUrl); + requestHeaders = JSON.parse(String(args.inputs.requestHeaders)); + requestBody = JSON.parse(String(args.inputs.requestBody)); + requestConfig = { + method: method, + url: requestUrl, + headers: requestHeaders, + data: requestBody, + }; + _a.label = 1; + case 1: + _a.trys.push([1, 3, , 4]); + return [4 /*yield*/, args.deps.axios(requestConfig)]; + case 2: + res = _a.sent(); + args.jobLog("Web request succeeded: Status Code: ".concat(res.status)); + return [3 /*break*/, 4]; + case 3: + err_1 = _a.sent(); + args.jobLog('Web Request Failed'); + args.jobLog(JSON.stringify(err_1)); + throw new Error('Web Request Failed'); + case 4: return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.js new file mode 100644 index 000000000..8fcc656c2 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.js @@ -0,0 +1,79 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Video Framerate', + description: 'Check if video framerate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound of fps', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '60', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound fps', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isWithinRange = false; + var greaterThanFps = Number(args.inputs.greaterThan); + var lessThanFps = Number(args.inputs.lessThan); + var VideoFrameRate = (_b = (_a = args.inputFileObj) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.VideoFrameRate; + if (VideoFrameRate) { + if (VideoFrameRate >= greaterThanFps && VideoFrameRate <= lessThanFps) { + isWithinRange = true; + } + } + else { + throw new Error('Video framerate not found'); + } + if (isWithinRange) { + args.jobLog("Video framerate of ".concat(VideoFrameRate, " is within range of ").concat(greaterThanFps, " and ").concat(lessThanFps)); + } + else { + args.jobLog("Video framerate of ".concat(VideoFrameRate, " is not within range of ").concat(greaterThanFps, " and ").concat(lessThanFps)); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/check10Bit/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/check10Bit/1.0.0/index.js new file mode 100644 index 000000000..0c6c4ba98 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/check10Bit/1.0.0/index.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check 10 Bit Video', + description: 'Check if a file is 10 bit video', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is 10 bit video', + }, + { + number: 2, + tooltip: 'File is not 10 bit video', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var is10Bit = false; + if (Array.isArray((_b = (_a = args === null || args === void 0 ? void 0 : args.inputFileObj) === null || _a === void 0 ? void 0 : _a.ffProbeData) === null || _b === void 0 ? void 0 : _b.streams)) { + for (var i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + var stream = args.inputFileObj.ffProbeData.streams[i]; + if (stream.codec_type === 'video' + && (stream.bits_per_raw_sample === 10 + || stream.pix_fmt === 'yuv420p10le')) { + is10Bit = true; + } + } + } + else { + throw new Error('File has not stream data'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: is10Bit ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkHdr/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkHdr/1.0.0/index.js new file mode 100644 index 000000000..ca0617fb3 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkHdr/1.0.0/index.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check HDR Video', + description: 'Check if video is HDR', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is HDR', + }, + { + number: 2, + tooltip: 'File is not HDR', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isHdr = false; + if (Array.isArray((_b = (_a = args === null || args === void 0 ? void 0 : args.inputFileObj) === null || _a === void 0 ? void 0 : _a.ffProbeData) === null || _b === void 0 ? void 0 : _b.streams)) { + for (var i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + var stream = args.inputFileObj.ffProbeData.streams[i]; + if (stream.codec_type === 'video' + && stream.color_transfer === 'smpte2084' + && stream.color_primaries === 'bt2020' + && stream.color_range === 'tv') { + isHdr = true; + } + } + } + else { + throw new Error('File has not stream data'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isHdr ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.js new file mode 100644 index 000000000..7994cd37a --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.js @@ -0,0 +1,94 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Overall Bitrate', + description: 'Check if overall file bitrate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'kbps', + inputUI: { + type: 'dropdown', + options: [ + 'bps', + 'kbps', + 'mbps', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isWithinRange = false; + var greaterThanBits = Number(args.inputs.greaterThan); + var lessThanBits = Number(args.inputs.lessThan); + if (args.inputs.unit === 'kbps') { + greaterThanBits *= 1000; + lessThanBits *= 1000; + } + else if (args.inputs.unit === 'mbps') { + greaterThanBits *= 1000000; + lessThanBits *= 1000000; + } + args.jobLog("File bitrate is ".concat(args.inputFileObj.bit_rate, " bps")); + args.jobLog("Checking if bitrate is within range ".concat(greaterThanBits, " bps and ").concat(lessThanBits, " bps")); + if (args.inputFileObj.bit_rate >= greaterThanBits && args.inputFileObj.bit_rate <= lessThanBits) { + isWithinRange = true; + args.jobLog('File bitrate is within range'); + } + else { + args.jobLog('File bitrate is not within range'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.js new file mode 100644 index 000000000..14959da32 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.js @@ -0,0 +1,103 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Video Bitrate', + description: 'Check if video bitrate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'kbps', + inputUI: { + type: 'dropdown', + options: [ + 'bps', + 'kbps', + 'mbps', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var _a, _b; + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var isWithinRange = false; + var greaterThanBits = Number(args.inputs.greaterThan); + var lessThanBits = Number(args.inputs.lessThan); + if (args.inputs.unit === 'kbps') { + greaterThanBits *= 1000; + lessThanBits *= 1000; + } + else if (args.inputs.unit === 'mbps') { + greaterThanBits *= 1000000; + lessThanBits *= 1000000; + } + var hasVideoBitrate = false; + if ((_b = (_a = args.inputFileObj) === null || _a === void 0 ? void 0 : _a.mediaInfo) === null || _b === void 0 ? void 0 : _b.track) { + args.inputFileObj.mediaInfo.track.forEach(function (stream) { + if (stream['@type'].toLowerCase() === 'video') { + if (stream.BitRate) { + hasVideoBitrate = true; + args.jobLog("Found video bitrate: ".concat(stream.BitRate)); + } + if (stream.BitRate >= greaterThanBits && stream.BitRate <= lessThanBits) { + isWithinRange = true; + } + } + }); + } + if (!hasVideoBitrate) { + throw new Error('Video bitrate not found'); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.js new file mode 100644 index 000000000..bf6d14e2e --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.js @@ -0,0 +1,65 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Video Codec', + description: 'Check if a file has a specific video codec', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'codec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + 'vp9', + 'h264', + 'vp8', + ], + }, + tooltip: 'Specify the codec check for', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File has codec', + }, + { + number: 2, + tooltip: 'File does not have codec', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var hasCodec = false; + if (args.inputFileObj.ffProbeData.streams) { + args.inputFileObj.ffProbeData.streams.forEach(function (stream) { + if (stream.codec_type === 'video' && stream.codec_name === args.inputs.codec) { + hasCodec = true; + } + }); + } + return { + outputFileObj: args.inputFileObj, + outputNumber: hasCodec ? 1 : 2, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.js new file mode 100644 index 000000000..bb965c448 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.js @@ -0,0 +1,98 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Video Resolution', + description: 'Check is video is 480p,576p,720p,1080p,1440p,4KUHD,DCI4K,8KUHD,Other', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is 480p', + }, + { + number: 2, + tooltip: 'File is 576p', + }, + { + number: 3, + tooltip: 'File is 720p', + }, + { + number: 4, + tooltip: 'File is 1080p', + }, + { + number: 5, + tooltip: 'File is 1440p', + }, + { + number: 6, + tooltip: 'File is 4KUHD', + }, + { + number: 7, + tooltip: 'File is DCI4K', + }, + { + number: 8, + tooltip: 'File is 8KUHD', + }, + { + number: 9, + tooltip: 'File is Other', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var outputNumber = 9; + switch (args.inputFileObj.video_resolution) { + case '480p': + outputNumber = 1; + break; + case '576p': + outputNumber = 2; + break; + case '720p': + outputNumber = 3; + break; + case '1080p': + outputNumber = 4; + break; + case '1440p': + outputNumber = 5; + break; + case '4KUHD': + outputNumber = 6; + break; + case 'DCI4K': + outputNumber = 7; + break; + case '8KUHD': + outputNumber = 8; + break; + default: + outputNumber = 9; + } + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.js new file mode 100644 index 000000000..dbd649eda --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.js @@ -0,0 +1,57 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Check Video Streams Count', + description: 'This plugin checks if the number of video streams is 1 or more.', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File has one video stream', + }, + { + number: 2, + tooltip: 'File has more than one video stream', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var ffProbeData = args.inputFileObj.ffProbeData; + if (!ffProbeData || !ffProbeData.streams) { + throw new Error('ffProbeData or ffProbeData.streams is not available.'); + } + var videoStreams = ffProbeData.streams.filter(function (stream) { return stream.codec_type === 'video'; }).length; + var outputNumber = 1; // Default to one video stream + if (videoStreams === 0) { + throw new Error('No video streams found in file.'); + } + else if (videoStreams === 1) { + outputNumber = 1; // One video stream + } + else if (videoStreams > 1) { + outputNumber = 2; // More than one video stream + } + args.jobLog("Number of video streams: ".concat(videoStreams)); + return { + outputFileObj: args.inputFileObj, + outputNumber: outputNumber, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.js new file mode 100644 index 000000000..7750de4ed --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.js @@ -0,0 +1,140 @@ +"use strict"; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +var cliUtils_1 = require("../../../../FlowHelpers/1.0.0/cliUtils"); +var fileUtils_1 = require("../../../../FlowHelpers/1.0.0/fileUtils"); +/* eslint-disable no-param-reassign */ +var details = function () { return ({ + name: 'Run Health Check', + description: 'Run a quick health check using HandBrake or a thorough health check using FFmpeg', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'type', + type: 'string', + defaultValue: 'quick', + inputUI: { + type: 'dropdown', + options: [ + 'quick', + 'thorough', + ], + }, + tooltip: 'Specify the container to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { return __awaiter(void 0, void 0, void 0, function () { + var lib, type, outputFilePath, cliPath, cliArgs, cli, res; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + type = String(args.inputs.type); + args.jobLog("Running health check of type ".concat(type)); + outputFilePath = "".concat((0, fileUtils_1.getPluginWorkDir)(args), "/").concat((0, fileUtils_1.getFileName)(args.inputFileObj._id)) + + ".".concat((0, fileUtils_1.getContainer)(args.inputFileObj._id)); + cliPath = args.handbrakePath; + cliArgs = [ + '-i', + args.inputFileObj._id, + '-o', + outputFilePath, + '--scan', + ]; + if (type === 'thorough') { + cliPath = args.ffmpegPath; + cliArgs = [ + '-stats', + '-v', + 'error', + '-i', + args.inputFileObj._id, + '-f', + 'null', + '-max_muxing_queue_size', + '9999', + outputFilePath, + ]; + } + cli = new cliUtils_1.CLI({ + cli: cliPath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + return [4 /*yield*/, cli.runCli()]; + case 1: + res = _a.sent(); + if (res.cliExitCode !== 0) { + args.jobLog('Running CLI failed'); + throw new Error('Running CLI failed'); + } + return [2 /*return*/, { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }]; + } + }); +}); }; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.js b/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.js new file mode 100644 index 000000000..f8eca12b9 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.js @@ -0,0 +1,61 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.plugin = exports.details = void 0; +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +var details = function () { return ({ + name: 'Transcode Video File', + description: 'Transcode a video file using ffmpeg. GPU transcoding will be used if possible.', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'target_codec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + // 'vp9', + 'h264', + // 'vp8', + ], + }, + tooltip: 'Specify the codec to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); }; +exports.details = details; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +var plugin = function (args) { + var lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + var fs = require('fs'); + var oldFile = args.inputFileObj._id; + var newFile = "".concat(args.inputFileObj._id, ".tmp"); + if (fs.existsSync(newFile)) { + fs.unlinkSync(newFile); + } + fs.copyFileSync(oldFile, newFile); + return { + outputFileObj: { _id: newFile }, + outputNumber: 1, + variables: args.variables, + }; +}; +exports.plugin = plugin; diff --git a/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/test.js b/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/test.js new file mode 100644 index 000000000..3918c74e4 --- /dev/null +++ b/FlowPlugins/CommunityFlowPlugins/video/transcodeVideo/1.0.0/test.js @@ -0,0 +1 @@ +"use strict"; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter1.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter1.js new file mode 100644 index 000000000..6f83c5ff9 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter1.js @@ -0,0 +1,109 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 1: Getting Started", + "description": "Chapter 1: Getting Started", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "_YTuyCZg3", + "position": { + "x": 644.7725474007168, + "y": -59.78556037646227 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "RQzydYbay", + "position": { + "x": 644.8785689715966, + "y": 285.63446752627516 + } + }, + { + "name": "1. Hello and welcome to Tdarr! This is a comment plugin. It doesn't do anything except help explain what's going on in a flow! You can place them anywhere and even link them together.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "df5cejfZP", + "position": { + "x": 774.8672137292031, + "y": -254.93856109034408 + } + }, + { + "name": "2. See! This comment won't do anything. The file from the previous plugin will be passed straight to the next plugin", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "30CajwYP2", + "position": { + "x": 644.6915712919753, + "y": 135.90533672888392 + } + }, + { + "name": "3. This here is an input file plugin and it's where every flow starts. You can only have ONE of these per flow.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "734dA76hg", + "position": { + "x": 444.5704060029551, + "y": -3.4693570957774114 + } + }, + { + "name": "4. That's it for this one, see you in the next chapter!", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "rkYonbPgX", + "position": { + "x": 443.9627448274695, + "y": 332.6480632642012 + } + } + ], + "flowEdges": [ + { + "source": "_YTuyCZg3", + "sourceHandle": "1", + "target": "30CajwYP2", + "targetHandle": null, + "id": "HUBIf10ny" + }, + { + "source": "30CajwYP2", + "sourceHandle": "1", + "target": "RQzydYbay", + "targetHandle": null, + "id": "Gd19X19w1" + }, + { + "source": "df5cejfZP", + "sourceHandle": "1", + "target": "30CajwYP2", + "targetHandle": null, + "id": "0EA92XgvP" + }, + { + "source": "734dA76hg", + "sourceHandle": "1", + "target": "rkYonbPgX", + "targetHandle": null, + "id": "lXbYouTsz" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter2.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter2.js new file mode 100644 index 000000000..fa3df8bfd --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter2.js @@ -0,0 +1,188 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 2: The Basics", + "description": "Chapter 2: The Basics", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "p2KPpRjnB", + "position": { + "x": 414.1115477468154, + "y": -216.87055056329626 + } + }, + { + "name": "1. The flow follows the current 'working file' which we can run checks and take actions on.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "ecLynt2i0", + "position": { + "x": 197.4536903827362, + "y": -265.54506622009336 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "jUig7_cRU", + "position": { + "x": 439.02078192278447, + "y": 122.5624161723565 + } + }, + { + "name": "Rename File to have _BigFile", + "sourceRepo": "Community", + "pluginName": "renameFile", + "version": "1.0.0", + "inputsDB": { + "fileRename": "${fileName}_BigFile.${container}" + }, + "id": "2l0pB_oXW", + "position": { + "x": 257.94626475719076, + "y": -21.078426771503985 + } + }, + { + "name": "Check File Size", + "sourceRepo": "Community", + "pluginName": "checkFileSize", + "version": "1.0.0", + "inputsDB": { + "greaterThan": "1", + "lessThan": "10000" + }, + "id": "oDkceuMNL", + "position": { + "x": 413.7748155871969, + "y": -110.90469509295968 + } + }, + { + "name": "Each plugin can only have one input handle but many plugins can link to it. Plugins which only check something are typically orange coloured and have 2 or more outputs.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "HTvMe6FSV", + "position": { + "x": 34.402701566604065, + "y": -184.71873806260285 + } + }, + { + "name": "Once you make an action on a file, in almost all cases the output is the new file. It will be located in your library cache folder.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "3O3ECJdF-", + "position": { + "x": 33.1114649694174, + "y": 113.19141666640903 + } + }, + { + "name": "Typical usage is to replace the original file. So this plugin will replace the original file with the new file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "qA8hT1mmP", + "position": { + "x": 355.2680532661178, + "y": 199.7482565776084 + } + }, + { + "name": "This flow route doesn't change the file, so the Replace Original File plugin won't do anything and the flow will end succesffully.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "1vBp13H02", + "position": { + "x": 597.7143477707415, + "y": -48.77347490679115 + } + }, + { + "name": "Double click on a plugin to see what each GREEN output does. Ignore the RED outputs for now.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "s-m8qOhJ7", + "position": { + "x": 34.4143205430116, + "y": -5.644569445757767 + } + } + ], + "flowEdges": [ + { + "source": "p2KPpRjnB", + "sourceHandle": "1", + "target": "oDkceuMNL", + "targetHandle": null, + "id": "S8inufSTF" + }, + { + "source": "oDkceuMNL", + "sourceHandle": "1", + "target": "2l0pB_oXW", + "targetHandle": null, + "id": "LFCRv0WUh" + }, + { + "source": "2l0pB_oXW", + "sourceHandle": "1", + "target": "jUig7_cRU", + "targetHandle": null, + "id": "w0K3dKylI" + }, + { + "source": "oDkceuMNL", + "sourceHandle": "2", + "target": "jUig7_cRU", + "targetHandle": null, + "id": "SNdz3urrJ" + }, + { + "source": "ecLynt2i0", + "sourceHandle": "1", + "target": "HTvMe6FSV", + "targetHandle": null, + "id": "7qPHR6V9P" + }, + { + "source": "3O3ECJdF-", + "sourceHandle": "1", + "target": "qA8hT1mmP", + "targetHandle": null, + "id": "GjDmOX_EI" + }, + { + "source": "HTvMe6FSV", + "sourceHandle": "1", + "target": "s-m8qOhJ7", + "targetHandle": null, + "id": "0bPlyyR9Q" + }, + { + "source": "s-m8qOhJ7", + "sourceHandle": "1", + "target": "3O3ECJdF-", + "targetHandle": null, + "id": "Mxxly19vC" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter3.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter3.js new file mode 100644 index 000000000..33241a63d --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter3.js @@ -0,0 +1,224 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 3: FFmpeg Command", + "description": "Chapter 3: FFmpeg Command", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 648.9333795070321, + "y": -12.529435106431094 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 723.9430232247286, + "y": 534.7914903208923 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 400.42838247161643, + "y": 438.58749864385743 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "The FFmpeg Command plugins dynamically create an FFmpeg command depending on the input file", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "hGnpEHnk5", + "position": { + "x": 254.91444207269103, + "y": -44.61887485112061 + } + }, + { + "name": "You must always begin an FFmpeg command using the 'Begin Command' Plugin", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "saj94a135", + "position": { + "x": 201.288800916537, + "y": 100.94856498487928 + } + }, + { + "name": "In this example, if the video file is already in h265/hevc and mkv container, no action will be taken on the file. To force re-encoding, you can use the forceEncoding option on the Video Encoder plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "sb5MvVryc", + "position": { + "x": 201.61485276007585, + "y": 222.09640730256172 + } + }, + { + "name": "Once the FFmpeg command has been created, you need to execute it using this plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "V0QGN5PKA", + "position": { + "x": 202.61485276007582, + "y": 440.0964073025617 + } + }, + { + "name": "Once again, the output contains the new cache file (or the original file if no action was taken on the file). If there's a new cache file, the 'Replace Original File' plugin will replace the original file, else it will do nothing.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "i4eODNlBc", + "position": { + "x": 536.6148527600759, + "y": 568.0964073025617 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "k9JywvYcK" + }, + { + "source": "hGnpEHnk5", + "sourceHandle": "1", + "target": "saj94a135", + "targetHandle": null, + "id": "dX6DiWPJX" + }, + { + "source": "saj94a135", + "sourceHandle": "1", + "target": "sb5MvVryc", + "targetHandle": null, + "id": "0MAqJvu_e" + }, + { + "source": "sb5MvVryc", + "sourceHandle": "1", + "target": "V0QGN5PKA", + "targetHandle": null, + "id": "57NrKKG2n" + }, + { + "source": "V0QGN5PKA", + "sourceHandle": "1", + "target": "i4eODNlBc", + "targetHandle": null, + "id": "BHwljK8rj" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p1.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p1.js new file mode 100644 index 000000000..5bf45db61 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p1.js @@ -0,0 +1,213 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 4: Flow Errors Part 1", + "description": "Chapter 4: Flow Errors Part 1", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 416.1451226612283, + "y": 433.3485852473201 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "If an unhandled error occurs during the flow, the flow will stop and the file will be moved to the Transcode: Error/Cancelled tab. You can then review the job report to see what went wrong.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "mNaOzfS0Y", + "position": { + "x": 604.5850500985517, + "y": 166.18413013606266 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "You can also force a flow to fail which can be useful in certain situation such as here.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "9QkIvxxxx", + "position": { + "x": 678.3646507954192, + "y": 429.7476734555484 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "mNaOzfS0Y", + "sourceHandle": "1", + "target": "9QkIvxxxx", + "targetHandle": null, + "id": "4Yez6rEN2" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p2.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p2.js new file mode 100644 index 000000000..ead074438 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p2.js @@ -0,0 +1,307 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 4: Flow Errors Part 2 - On Flow Error", + "description": "Chapter 4: Flow Errors Part 2 - On Flow Error", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 416.1451226612283, + "y": 433.3485852473201 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 922.4197900595414, + "y": 161.088098623682 + } + }, + { + "name": "1. To handle an error that occurs anywhere in this specifc flow, you can use the 'On Flow Error' plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "jpEn9FVQX", + "position": { + "x": 1018.5785464566798, + "y": 65.98583847747655 + } + }, + { + "name": "All unhandled errors and the 'Fail Flow' plugin IN THIS FLOW will trigger the 'On Flow Error' plugin IN THIS FLOW", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "vRFTPo0p5", + "position": { + "x": 698.3866844766682, + "y": 451.2472106052397 + } + }, + { + "name": "If another error occurs in the 'On Flow Error' flow then the flow will end and the file will be moved to the transcode 'Transcode: Error/Cancelled' tab. The 'On Flow Error' plugin will NOT be run again (to prevent infinite error loops)", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "fAQouEkEY", + "position": { + "x": 1183.421069816697, + "y": 228.19157008625297 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "42P9lb0B3", + "position": { + "x": 897.7260729664589, + "y": 469.1243455181426 + } + }, + { + "name": "Even if all the plugins in the error flow complete successfully, the file will still be moved to the 'Transcode: Error/Cancelled' tab at the end.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "Ke1S57we6", + "position": { + "x": 1006.3188415680227, + "y": 541.3784972055968 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "yj3grm5d8", + "position": { + "x": 1047.7090907308577, + "y": 394.7331214427515 + } + }, + { + "name": "Check File Exists", + "sourceRepo": "Community", + "pluginName": "checkFileExists", + "version": "1.0.0", + "id": "S_rVuKn8S", + "position": { + "x": 926.2665476107438, + "y": 277.7066707997331 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "jpEn9FVQX", + "sourceHandle": "1", + "target": "fAQouEkEY", + "targetHandle": null, + "id": "5mv1ls7Ib" + }, + { + "source": "fAQouEkEY", + "sourceHandle": "1", + "target": "Ke1S57we6", + "targetHandle": null, + "id": "_VEvhMOtk" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "S_rVuKn8S", + "targetHandle": null, + "id": "yweCdlSWM" + }, + { + "source": "S_rVuKn8S", + "sourceHandle": "1", + "target": "yj3grm5d8", + "targetHandle": null, + "id": "xI3eh7wZp" + }, + { + "source": "S_rVuKn8S", + "sourceHandle": "2", + "target": "42P9lb0B3", + "targetHandle": null, + "id": "V-qf6QBC4" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p3.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p3.js new file mode 100644 index 000000000..e04a57ba2 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p3.js @@ -0,0 +1,314 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 4: Flow Errors Part 3 - Plugin-specific Error Handling", + "description": "Chapter 4: Flow Errors Part 3 - Plugin-specific Error Handling", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 541.9238836009351, + "y": 32.863009312154745 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 399.8062875388412, + "y": 417.9708580733087 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 1060.1712089610803, + "y": 145.28055874973487 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "Bc2bZtgBc", + "position": { + "x": 922.4677516191077, + "y": 279.351803016885 + } + }, + { + "name": "Tdarr also offers plugin-specific error handling using the RED connection on each plugin. The flow path will be triggered if an unhandled error occurs within that specific plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "H__edbTLw", + "position": { + "x": 767.7320724785685, + "y": -230.81328720265796 + } + }, + { + "name": "Send Web Request: Ping Melissa to check network storage", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "X9NEJCEgk", + "position": { + "x": 880.7836232681229, + "y": -39.676791653194755 + } + }, + { + "name": "Send Web Request: Ping Romesh to check transcode log", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "BO0c5TlKq", + "position": { + "x": 661.6510110384603, + "y": 300.5554220718082 + } + }, + { + "name": "This allows very specific error flows, for example pinging different team members for different errors.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "zTVwtbuxI", + "position": { + "x": 657.8440331872728, + "y": 166.67570418496692 + } + }, + { + "name": "The plugin-specifc error handling will NOT trigger the 'On Flow Error' plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "N9E-u8l0o", + "position": { + "x": 1170.7519669401188, + "y": 56.50523025141092 + } + }, + { + "name": "But you can still join the plugin-specific error handling flow onto the rest of the 'On Flow Error' Flow", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "AOi3vLobO", + "position": { + "x": 1125.9768689675689, + "y": 284.4868129833375 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "Bc2bZtgBc", + "targetHandle": null, + "id": "7k8P1VYv6" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "err1", + "target": "X9NEJCEgk", + "targetHandle": null, + "id": "9rhuR5eSI" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "err1", + "target": "BO0c5TlKq", + "targetHandle": null, + "id": "ttZgLtKF3" + }, + { + "source": "H__edbTLw", + "sourceHandle": "1", + "target": "zTVwtbuxI", + "targetHandle": null, + "id": "5sjNNMXAK" + }, + { + "source": "X9NEJCEgk", + "sourceHandle": "1", + "target": "Bc2bZtgBc", + "targetHandle": null, + "id": "OW-yqRQH5" + }, + { + "source": "N9E-u8l0o", + "sourceHandle": "1", + "target": "AOi3vLobO", + "targetHandle": null, + "id": "K440_LQm_" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p4.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p4.js new file mode 100644 index 000000000..b8dfd42d3 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter4p4.js @@ -0,0 +1,181 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 4: Flow Errors Part 4 - Resetting Flow Errors", + "description": "Chapter 4: Flow Errors Part 4 - Resetting Flow Errors", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "kc2oWbzdg", + "position": { + "x": 792.9115444209641, + "y": 72.86840721125132 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "MttLdH9JH", + "position": { + "x": 656.7168106666427, + "y": 259.68513570736866 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "SUV-PcTXK", + "position": { + "x": 656.3361499796371, + "y": 466.43606780911165 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "OZTQfLxQU", + "position": { + "x": 657.071898477293, + "y": 356.04954686544056 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "alp4lZYtu", + "position": { + "x": 939.7604719407238, + "y": 380.4448835288041 + } + }, + { + "name": "Once a flow error occurs, even if subsequent plugins run successfully, the item will still be moved to the 'Transcode: Error/Cancelled' tab after the final plugin has finished, as would happen if the flow ended here.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "uyAA1bj79", + "position": { + "x": 1114.7604719407238, + "y": 318.4448835288041 + } + }, + { + "name": "Reset Flow Error", + "sourceRepo": "Community", + "pluginName": "resetFlowError", + "version": "1.0.0", + "id": "_DZqwYTaP", + "position": { + "x": 1005.7604719407236, + "y": 547.4448835288041 + } + }, + { + "name": "HandBrake Custom Arguments", + "sourceRepo": "Community", + "pluginName": "handbrakeCustomArguments", + "version": "1.0.0", + "id": "pX08BTxw7", + "position": { + "x": 1126.7604719407238, + "y": 697.4448835288041 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "0QGXt-4Zi", + "position": { + "x": 855.7604719407237, + "y": 853.4448835288041 + } + }, + { + "name": "Use this plugin to reset the flow error status. You can then attempt a new way of processing the file", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "aWlDgeStq", + "position": { + "x": 1176.7604719407238, + "y": 531.4448835288041 + } + } + ], + "flowEdges": [ + { + "source": "kc2oWbzdg", + "sourceHandle": "1", + "target": "MttLdH9JH", + "targetHandle": null, + "id": "T43cHyOAu" + }, + { + "source": "MttLdH9JH", + "sourceHandle": "1", + "target": "OZTQfLxQU", + "targetHandle": null, + "id": "jqX_8_fKY" + }, + { + "source": "OZTQfLxQU", + "sourceHandle": "1", + "target": "SUV-PcTXK", + "targetHandle": null, + "id": "-1tj4OiNV" + }, + { + "source": "OZTQfLxQU", + "sourceHandle": "err1", + "target": "alp4lZYtu", + "targetHandle": null, + "id": "LOQaBxuai" + }, + { + "source": "pX08BTxw7", + "sourceHandle": "1", + "target": "0QGXt-4Zi", + "targetHandle": null, + "id": "VMBnPqvzU" + }, + { + "source": "SUV-PcTXK", + "sourceHandle": "1", + "target": "0QGXt-4Zi", + "targetHandle": null, + "id": "wsDY8MYki" + }, + { + "source": "alp4lZYtu", + "sourceHandle": "1", + "target": "_DZqwYTaP", + "targetHandle": null, + "id": "g9tDFsL5p" + }, + { + "source": "_DZqwYTaP", + "sourceHandle": "1", + "target": "pX08BTxw7", + "targetHandle": null, + "id": "YwFpa-_YC" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter5.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter5.js new file mode 100644 index 000000000..27fc615d9 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter5.js @@ -0,0 +1,156 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 5: Go To Flow", + "description": "Chapter 5: Go To Flow", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 1122.33024332169, + "y": 226.4434391132305 + } + }, + { + "name": "You can use the Go To Flow to go to a different flow. The working file will be passed to that flow and will continue as normal. Double click on the plugin to select the flow you'd like to go to.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "rHV28Kbkv", + "position": { + "x": 462.0014512264263, + "y": 65.78412788449464 + } + }, + { + "name": "Go To Flow", + "sourceRepo": "Community", + "pluginName": "goToFlow", + "version": "1.0.0", + "id": "gOrbropah", + "position": { + "x": 572.7308895655424, + "y": 234.58707695358294 + } + }, + { + "name": "By design, if an error happens in a different flow, this 'On Flow Error' will not be called. Across all flows, the 'On Flow Error' plugin will only be called ONCE in the flow that the FIRST error occurred in. 'On Flow Error' plugins in flows before or after the current flow will not be called, even if an error occurs in them at a later time.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "7azuiVML9", + "position": { + "x": 1310.2464269220861, + "y": 134.70796523124582 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "ELn0kcc-1", + "position": { + "x": 778.4079905179452, + "y": 428.7308825254772 + } + }, + { + "name": "Go To Flow", + "sourceRepo": "Community", + "pluginName": "goToFlow", + "version": "1.0.0", + "id": "j5dOGi9zz", + "position": { + "x": 1122.484636036451, + "y": 397.97542817745443 + } + }, + { + "name": "After an error has occured you can even go to a different flow! So you can create a dedicated Error flow and go to it each time an error occurs within any of your flows! Useful for notifications etc.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "EpMxr2UuE", + "position": { + "x": 1205.7688307851763, + "y": 479.4625484620842 + } + } + ], + "flowEdges": [ + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "gOrbropah", + "targetHandle": null, + "id": "qVWE7SWt2" + }, + { + "source": "rHV28Kbkv", + "sourceHandle": "1", + "target": "7azuiVML9", + "targetHandle": null, + "id": "i0OUf3hAM" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "ELn0kcc-1", + "targetHandle": null, + "id": "hTaDcPw24" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "j5dOGi9zz", + "targetHandle": null, + "id": "6Hrh7vbfW" + }, + { + "source": "7azuiVML9", + "sourceHandle": "1", + "target": "EpMxr2UuE", + "targetHandle": null, + "id": "S_36mCXnL" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter6.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter6.js new file mode 100644 index 000000000..aedc2a24d --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter6.js @@ -0,0 +1,267 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 6: The Review System", + "description": "Chapter 6: The Review System", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "R0gX9B20d", + "position": { + "x": 879.7236115475249, + "y": 934.782797377857 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "U6N3AQubH", + "position": { + "x": 546.8854528742303, + "y": 174.54090453410515 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "Lv-zb-iTw", + "position": { + "x": 543.172691292081, + "y": 368.6158072160807 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "1pOFUCuQR", + "position": { + "x": 545.0642491154337, + "y": 274.70711126791645 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "oHpu2fZOi", + "position": { + "x": 631.7959812272709, + "y": 459.542392214296 + } + }, + { + "name": "Run Classic Transcode Plugin: Add Audio Stream", + "sourceRepo": "Community", + "pluginName": "runClassicTranscodePlugin", + "version": "1.0.0", + "inputsDB": { + "pluginSourceId": "Community:Tdarr_Plugin_00td_action_add_audio_stream_codec" + }, + "id": "RZX5jIP5I", + "position": { + "x": 632.2402074212371, + "y": 545.8356807635909 + } + }, + { + "name": "Run Classic Transcode Plugin", + "sourceRepo": "Community", + "pluginName": "runClassicTranscodePlugin", + "version": "1.0.0", + "inputsDB": { + "pluginSourceId": "Community:Tdarr_Plugin_00td_action_remove_audio_by_channel_count", + "channelCounts": "8" + }, + "id": "3zj5puRQ1", + "position": { + "x": 696.5390735282473, + "y": 758.7339551203235 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "q8Pz_3HGh", + "position": { + "x": 634.7388490591334, + "y": 648.8668893974542 + } + }, + { + "name": "You can pause a flow by using the 'Require Review' plugin. This will cause the file to stay in the staging section on the Tdarr tab until the 'Reviewed' button is pressed. This allows you to check the last completed cache file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "YPyMAbZ76", + "position": { + "x": 856.2716462414401, + "y": 343.06736953610425 + } + }, + { + "name": "Once the file has been reviewed, the flow will continue from the next plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "REt4UEEGD", + "position": { + "x": 856.7260936183322, + "y": 531.8099095812979 + } + }, + { + "name": "You can 'Require Review' as much as you like!", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "UhuSLjA8g", + "position": { + "x": 857.7070495622867, + "y": 709.362935437006 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "8IU0bhEJs", + "position": { + "x": 780.2115299899054, + "y": 844.7348557026837 + } + } + ], + "flowEdges": [ + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "U6N3AQubH", + "sourceHandle": "1", + "target": "1pOFUCuQR", + "targetHandle": null, + "id": "RdnvWmv0o" + }, + { + "source": "1pOFUCuQR", + "sourceHandle": "1", + "target": "Lv-zb-iTw", + "targetHandle": null, + "id": "p-VkIS6DK" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "U6N3AQubH", + "targetHandle": null, + "id": "x_vWzShYB" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "R0gX9B20d", + "targetHandle": null, + "id": "CtIsUppTB" + }, + { + "source": "Lv-zb-iTw", + "sourceHandle": "1", + "target": "oHpu2fZOi", + "targetHandle": null, + "id": "d9tDIjd1L" + }, + { + "source": "oHpu2fZOi", + "sourceHandle": "1", + "target": "RZX5jIP5I", + "targetHandle": null, + "id": "kFP4WRftx" + }, + { + "source": "RZX5jIP5I", + "sourceHandle": "1", + "target": "q8Pz_3HGh", + "targetHandle": null, + "id": "nqbQJ9wUz" + }, + { + "source": "q8Pz_3HGh", + "sourceHandle": "1", + "target": "3zj5puRQ1", + "targetHandle": null, + "id": "Vx60urLP7" + }, + { + "source": "YPyMAbZ76", + "sourceHandle": "1", + "target": "REt4UEEGD", + "targetHandle": null, + "id": "B85VWeRRu" + }, + { + "source": "REt4UEEGD", + "sourceHandle": "1", + "target": "UhuSLjA8g", + "targetHandle": null, + "id": "wk44u1THD" + }, + { + "source": "3zj5puRQ1", + "sourceHandle": "1", + "target": "8IU0bhEJs", + "targetHandle": null, + "id": "9Q4vfDFmI" + }, + { + "source": "8IU0bhEJs", + "sourceHandle": "1", + "target": "R0gX9B20d", + "targetHandle": null, + "id": "8wXVHwiDC" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/tutorials/chapter7.js b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter7.js new file mode 100644 index 000000000..d08f8956c --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/tutorials/chapter7.js @@ -0,0 +1,166 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + "name": "Chapter 7: Using an Output Folder", + "description": "Chapter 7: Using an Output Folder", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "y4lqcdrho", + "position": { + "x": 657.6098846576496, + "y": 83.73457282843094 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "x4UqtuCK9", + "position": { + "x": 546.5579603766718, + "y": 179.82684381124744 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "6a0HyfVUa", + "position": { + "x": 394.21161179985006, + "y": 264.2702484509714 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "OBbLCZ8SO", + "position": { + "x": 395.9527129264425, + "y": 467.9790802622644 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "nvhiecc42", + "position": { + "x": 395.95271292644225, + "y": 360.03081041354505 + } + }, + { + "name": "Move To Directory", + "sourceRepo": "Community", + "pluginName": "moveToDirectory", + "version": "2.0.0", + "id": "8ffv7PWIl", + "position": { + "x": 586.6032862882935, + "y": 587.2445074338334 + }, + "inputsDB": { + "outputDirectory": "/example" + } + }, + { + "name": "By default, the final working file in the flow is what will be kept in the Tdarr database and will appear in the 'Transcode: Success/Not required' table. If the flow were to end here, the new file in the output folder would be kept in the Tdarr database.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "XJEBW_sGy", + "position": { + "x": 792.5819966148043, + "y": 513.4182522751619 + } + }, + { + "name": "Set Original File", + "sourceRepo": "Community", + "pluginName": "setOriginalFile", + "version": "1.0.0", + "id": "X4K2UoPuU", + "position": { + "x": 587.1019377136648, + "y": 787.596766284271 + } + }, + { + "name": "To have the original file be kept in the Tdarr database, use the 'Set Original File' plugin which will set the working file to the original file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "yXP0EZrMk", + "position": { + "x": 777.1019377136646, + "y": 787.596766284271 + } + } + ], + "flowEdges": [ + { + "source": "y4lqcdrho", + "sourceHandle": "1", + "target": "x4UqtuCK9", + "targetHandle": null, + "id": "c6kYE6bDM" + }, + { + "source": "x4UqtuCK9", + "sourceHandle": "1", + "target": "8ffv7PWIl", + "targetHandle": null, + "id": "odwp-30JQ" + }, + { + "source": "x4UqtuCK9", + "sourceHandle": "2", + "target": "6a0HyfVUa", + "targetHandle": null, + "id": "87bCwwur4" + }, + { + "source": "6a0HyfVUa", + "sourceHandle": "1", + "target": "nvhiecc42", + "targetHandle": null, + "id": "kYgiFcdHk" + }, + { + "source": "nvhiecc42", + "sourceHandle": "1", + "target": "OBbLCZ8SO", + "targetHandle": null, + "id": "guA5aTucK" + }, + { + "source": "OBbLCZ8SO", + "sourceHandle": "1", + "target": "8ffv7PWIl", + "targetHandle": null, + "id": "PgL3EQtsQ" + }, + { + "source": "8ffv7PWIl", + "sourceHandle": "1", + "target": "X4K2UoPuU", + "targetHandle": null, + "id": "JX7tzmso3" + } + ] +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/video/basicVideo.js b/FlowPlugins/CommunityFlowTemplates/video/basicVideo.js new file mode 100644 index 000000000..3a5f94c14 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/video/basicVideo.js @@ -0,0 +1,141 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + name: 'Basic HEVC Video Flow', + description: 'Basic HEVC Video Flow', + tags: '', + flowPlugins: [ + { + name: 'Input File', + sourceRepo: 'Community', + pluginName: 'inputFile', + version: '1.0.0', + id: 'pE6rU7gkW', + position: { + x: 758.5809635618224, + y: 117.19206188888086, + }, + }, + { + name: 'Check if hevc', + sourceRepo: 'Community', + pluginName: 'checkVideoCodec', + version: '1.0.0', + id: '91b7IrsEc', + position: { + x: 672.4549563302081, + y: 253.11148102973914, + }, + }, + { + name: 'Start', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: '4Swd6qzvc', + position: { + x: 489.25252076795084, + y: 370.51229288382495, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: '450g167D8', + position: { + x: 488.72295602997406, + y: 699.5034828311435, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + id: '8B_6pRd_U', + position: { + x: 488.5270135748424, + y: 477.83202026423606, + }, + }, + { + name: 'Replace Original File', + sourceRepo: 'Community', + pluginName: 'replaceOriginalFile', + version: '1.0.0', + id: '4fkfOyR3l', + position: { + x: 820.4549563302082, + y: 742.2114810297393, + }, + }, + { + name: 'Set Container', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetContainer', + version: '1.0.0', + id: 'TtKXi3Q7h', + position: { + x: 488.21110165973323, + y: 570.3064821931456, + }, + }, + ], + flowEdges: [ + { + source: 'pE6rU7gkW', + sourceHandle: '1', + target: '91b7IrsEc', + targetHandle: null, + id: 'HhF4rw2DZ', + }, + { + source: '91b7IrsEc', + sourceHandle: '2', + target: '4Swd6qzvc', + targetHandle: null, + id: 'jJizyFUcr', + }, + { + source: '4Swd6qzvc', + sourceHandle: '1', + target: '8B_6pRd_U', + targetHandle: null, + id: '3Df7Xoy93', + }, + { + source: '450g167D8', + sourceHandle: '1', + target: '4fkfOyR3l', + targetHandle: null, + id: 'rE5Dsh9KM', + }, + { + source: '91b7IrsEc', + sourceHandle: '1', + target: '4fkfOyR3l', + targetHandle: null, + id: 'W2nVG7ts5', + }, + { + source: '8B_6pRd_U', + sourceHandle: '1', + target: 'TtKXi3Q7h', + targetHandle: null, + id: 'epqtLsPuG', + }, + { + source: 'TtKXi3Q7h', + sourceHandle: '1', + target: '450g167D8', + targetHandle: null, + id: 'ljOeP0cAZ', + }, + ], +}); }; +exports.details = details; diff --git a/FlowPlugins/CommunityFlowTemplates/video/lowResolutionCopies.js b/FlowPlugins/CommunityFlowTemplates/video/lowResolutionCopies.js new file mode 100644 index 000000000..a1fef3cc3 --- /dev/null +++ b/FlowPlugins/CommunityFlowTemplates/video/lowResolutionCopies.js @@ -0,0 +1,362 @@ +"use strict"; +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.details = void 0; +var details = function () { return ({ + name: 'Create Low Resolution Video Copies', + description: 'Create Low Resolution Video Copies', + tags: '', + flowPlugins: [ + { + name: 'Input File', + sourceRepo: 'Community', + pluginName: 'inputFile', + version: '1.0.0', + id: 'pE6rU7gkW', + position: { + x: 764.3859715446088, + y: 54.59674430707997, + }, + }, + { + name: 'Check File Exists _480p', + sourceRepo: 'Community', + pluginName: 'checkFileExists', + version: '1.0.0', + inputsDB: { + fileToCheck: '${fileName}_480p.${container}', + }, + id: 'VyNRD3YjM', + position: { + x: 1127.8807371830678, + y: -1.4370146635981769, + }, + }, + { + name: 'Rename File _480p', + sourceRepo: 'Community', + pluginName: 'renameFile', + version: '1.0.0', + inputsDB: { + fileRename: '${fileName}_480p.${container}', + }, + id: 'VpCD-7LZJ', + position: { + x: 1398.163993949301, + y: 562.3533349776774, + }, + }, + { + name: 'Replace Original File', + sourceRepo: 'Community', + pluginName: 'replaceOriginalFile', + version: '1.0.0', + id: '1pj9oSg5G', + position: { + x: 736.3406162570204, + y: 598.8673432638388, + }, + }, + { + name: 'Check File Exists _720p', + sourceRepo: 'Community', + pluginName: 'checkFileExists', + version: '1.0.0', + inputsDB: { + fileToCheck: '${fileName}_720p.${container}', + }, + id: 'uDC6XT1Jy', + position: { + x: 1060.0100333142968, + y: 110.8981370311281, + }, + }, + { + name: 'Check File Name Includes', + sourceRepo: 'Community', + pluginName: 'checkFileNameIncludes', + version: '1.0.0', + inputsDB: { + terms: '_720p,_480p', + }, + id: 'wRipuaq4G', + position: { + x: 763.9976994431687, + y: 198.97576654117708, + }, + }, + { + name: 'Rename File _720p', + sourceRepo: 'Community', + pluginName: 'renameFile', + version: '1.0.0', + inputsDB: { + fileRename: '${fileName}_720p.${container}', + }, + id: 'cTKbaB8nT', + position: { + x: 1087.883110780845, + y: 509.6274273776863, + }, + }, + { + name: 'Move To Original Directory', + sourceRepo: 'Community', + pluginName: 'moveToOriginalDirectory', + version: '1.0.0', + id: 'mFRK-Z9WC', + position: { + x: 1162.0438576735944, + y: 608.0524996697887, + }, + }, + { + name: 'Set Original File', + sourceRepo: 'Community', + pluginName: 'setOriginalFile', + version: '1.0.0', + id: 'oD4u5PY9T', + position: { + x: 1020.6746394849897, + y: 738.9349550904227, + }, + }, + { + name: 'Begin Command', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: 'FSG9AOX5c', + position: { + x: 1171.2902386661297, + y: 178.0193821518036, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + inputsDB: { + forceEncoding: 'true', + }, + id: 'wcmBN2N02', + position: { + x: 1171.0819612214827, + y: 257.19366435734827, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: 'tmUd79-Fb', + position: { + x: 1167.5698309351776, + y: 406.2043896501846, + }, + }, + { + name: 'Begin Command', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: 'Jn6dcKd3i', + position: { + x: 1395.4614497255334, + y: 111.74717420966138, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: 'gbY0xIJnB', + position: { + x: 1398.0103706416776, + y: 417.6787803779547, + }, + }, + { + name: 'Set Video Resolution 480p', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVdeoResolution', + version: '1.0.0', + inputsDB: { + targetResolution: '480p', + }, + id: 'dzFEwECXB', + position: { + x: 1396.1961096759603, + y: 309.9727302535869, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + inputsDB: { + forceEncoding: 'true', + }, + id: '_EynbvgSl', + position: { + x: 1396.1961096759603, + y: 214.35898180146438, + }, + }, + { + name: 'Set Video Resolution 720p', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVdeoResolution', + version: '1.0.0', + inputsDB: { + targetResolution: '720p', + }, + id: 'CMm7MlE7g', + position: { + x: 1169.6624226114702, + y: 336.82482287402803, + }, + }, + ], + flowEdges: [ + { + source: 'pE6rU7gkW', + sourceHandle: '1', + target: 'wRipuaq4G', + targetHandle: null, + id: 'IE_oGhETB', + }, + { + source: 'wRipuaq4G', + sourceHandle: '1', + target: '1pj9oSg5G', + targetHandle: null, + id: 'QR6uGNUhE', + }, + { + source: 'wRipuaq4G', + sourceHandle: '2', + target: 'VyNRD3YjM', + targetHandle: null, + id: 'sh_kstv0D', + }, + { + source: 'uDC6XT1Jy', + sourceHandle: '1', + target: '1pj9oSg5G', + targetHandle: null, + id: 'G5jl85ijr', + }, + { + source: 'VyNRD3YjM', + sourceHandle: '1', + target: 'uDC6XT1Jy', + targetHandle: null, + id: 'DmUL9DS8q', + }, + { + source: 'VpCD-7LZJ', + sourceHandle: '1', + target: 'mFRK-Z9WC', + targetHandle: null, + id: 'ap4YXAxy3', + }, + { + source: 'cTKbaB8nT', + sourceHandle: '1', + target: 'mFRK-Z9WC', + targetHandle: null, + id: 'i9fr5J5pL', + }, + { + source: 'mFRK-Z9WC', + sourceHandle: '1', + target: 'oD4u5PY9T', + targetHandle: null, + id: 'KUw59S_Zl', + }, + { + source: 'oD4u5PY9T', + sourceHandle: '1', + target: 'wRipuaq4G', + targetHandle: null, + id: 'HlM4E6eV8', + }, + { + source: 'tmUd79-Fb', + sourceHandle: '1', + target: 'cTKbaB8nT', + targetHandle: null, + id: 'iJLmmoDLp', + }, + { + source: 'uDC6XT1Jy', + sourceHandle: '2', + target: 'FSG9AOX5c', + targetHandle: null, + id: 'iRTrU8utq', + }, + { + source: 'dzFEwECXB', + sourceHandle: '1', + target: 'gbY0xIJnB', + targetHandle: null, + id: 'A5cyCu_kx', + }, + { + source: 'Jn6dcKd3i', + sourceHandle: '1', + target: '_EynbvgSl', + targetHandle: null, + id: '1HajidLz-', + }, + { + source: '_EynbvgSl', + sourceHandle: '1', + target: 'dzFEwECXB', + targetHandle: null, + id: 'vEESYeSsL', + }, + { + source: 'VyNRD3YjM', + sourceHandle: '2', + target: 'Jn6dcKd3i', + targetHandle: null, + id: 'q8zd_qCSU', + }, + { + source: 'gbY0xIJnB', + sourceHandle: '1', + target: 'VpCD-7LZJ', + targetHandle: null, + id: 'leYMQdxHw', + }, + { + source: 'FSG9AOX5c', + sourceHandle: '1', + target: 'wcmBN2N02', + targetHandle: null, + id: 'Dl5MCSqQM', + }, + { + source: 'wcmBN2N02', + sourceHandle: '1', + target: 'CMm7MlE7g', + targetHandle: null, + id: 'GIpbjomC8', + }, + { + source: 'CMm7MlE7g', + sourceHandle: '1', + target: 'tmUd79-Fb', + targetHandle: null, + id: 'AxR9R10MY', + }, + ], +}); }; +exports.details = details; diff --git a/FlowPlugins/FlowHelpers/1.0.0/classicPlugins.js b/FlowPlugins/FlowHelpers/1.0.0/classicPlugins.js new file mode 100644 index 000000000..f37b54528 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/classicPlugins.js @@ -0,0 +1,155 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runClassicPlugin = void 0; +var fs_1 = require("fs"); +var fileUtils_1 = require("./fileUtils"); +var runClassicPlugin = function (args, type) { return __awaiter(void 0, void 0, void 0, function () { + var path, pluginSourceId, parts, pluginSource, pluginId, relativePluginPath, absolutePath, classicPlugin, pluginSrcStr, res, container, cacheFilePath, scanTypes, pluginInputFileObj, originalLibraryFile, otherArguments, result; + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + path = require('path'); + pluginSourceId = String(args.inputs.pluginSourceId); + parts = pluginSourceId.split(':'); + pluginSource = parts[0]; + pluginId = parts[1]; + relativePluginPath = "../../../".concat(pluginSource, "/").concat(pluginId, ".js"); + absolutePath = path.resolve(__dirname, relativePluginPath); + pluginSrcStr = ''; + if (!(pluginSource === 'Community')) return [3 /*break*/, 2]; + classicPlugin = args.deps.importFresh(relativePluginPath); + return [4 /*yield*/, fs_1.promises.readFile(absolutePath, 'utf8')]; + case 1: + pluginSrcStr = _b.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, args.deps.axiosMiddleware('api/v2/read-plugin', { + plugin: { + id: pluginId, + source: pluginSource, + }, + })]; + case 3: + res = _b.sent(); + classicPlugin = args.deps.requireFromString(res.pluginRaw, absolutePath); + pluginSrcStr = res.pluginRaw; + _b.label = 4; + case 4: + if (type === 'filter' && classicPlugin.details().Operation !== 'Filter') { + throw new Error("".concat('This plugin is meant for classic plugins that have ' + + 'Operation: Filter. This classic plugin has Operation: ').concat(classicPlugin.details().Operation) + + '. Please use the Run Classic Transcode Flow Plugin plugin instead.'); + } + if (type !== 'filter' && classicPlugin.details().Operation === 'Filter') { + throw new Error("".concat('This plugin is meant for classic plugins that have ' + + 'Operation: Transcode. This classic plugin has Operation: ').concat(classicPlugin.details().Operation) + + 'Please use the Run Classic Filter Flow Plugin plugin instead.'); + } + if (!Array.isArray(classicPlugin.dependencies)) return [3 /*break*/, 8]; + if (!args.installClassicPluginDeps) return [3 /*break*/, 6]; + args.jobLog("Installing dependencies for ".concat(pluginSourceId)); + return [4 /*yield*/, args.installClassicPluginDeps(classicPlugin.dependencies)]; + case 5: + _b.sent(); + return [3 /*break*/, 7]; + case 6: + args.jobLog("Not installing dependencies for ".concat(pluginSourceId, ", please update Tdarr")); + _b.label = 7; + case 7: return [3 /*break*/, 9]; + case 8: + args.jobLog("No depedencies to install for ".concat(pluginSourceId)); + _b.label = 9; + case 9: + container = (0, fileUtils_1.getContainer)(args.inputFileObj._id); + cacheFilePath = "".concat((0, fileUtils_1.getPluginWorkDir)(args), "/").concat((0, fileUtils_1.getFileName)(args.inputFileObj._id), ".").concat(container); + scanTypes = (0, fileUtils_1.getScanTypes)([pluginSrcStr]); + return [4 /*yield*/, args.deps.axiosMiddleware('api/v2/scan-individual-file', { + file: { + _id: args.inputFileObj._id, + file: args.inputFileObj.file, + DB: args.inputFileObj.DB, + footprintId: args.inputFileObj.footprintId, + }, + scanTypes: scanTypes, + })]; + case 10: + pluginInputFileObj = _b.sent(); + return [4 /*yield*/, args.deps.axiosMiddleware('api/v2/scan-individual-file', { + file: { + _id: args.originalLibraryFile._id, + file: args.originalLibraryFile.file, + DB: args.originalLibraryFile.DB, + footprintId: args.originalLibraryFile.footprintId, + }, + scanTypes: scanTypes, + })]; + case 11: + originalLibraryFile = _b.sent(); + otherArguments = { + handbrakePath: args.handbrakePath, + ffmpegPath: args.ffmpegPath, + mkvpropeditPath: args.mkvpropeditPath, + originalLibraryFile: originalLibraryFile, + nodeHardwareType: args.nodeHardwareType, + pluginCycle: 0, + workerType: args.workerType, + version: args.config.version, + platform_arch_isdocker: args.platform_arch_isdocker, + cacheFilePath: cacheFilePath, + job: args.job, + }; + return [4 /*yield*/, classicPlugin.plugin(pluginInputFileObj, args.librarySettings, args.inputs, otherArguments)]; + case 12: + result = _b.sent(); + if (((_a = result === null || result === void 0 ? void 0 : result.file) === null || _a === void 0 ? void 0 : _a._id) && args.inputFileObj._id !== result.file._id) { + // eslint-disable-next-line no-param-reassign + args.inputFileObj._id = result.file._id; + // eslint-disable-next-line no-param-reassign + args.inputFileObj.file = result.file.file; + args.jobLog("File ID changed from ".concat(args.inputFileObj._id, " to ").concat(result.file._id)); + } + return [2 /*return*/, { + result: result, + cacheFilePath: cacheFilePath, + absolutePath: absolutePath, + }]; + } + }); +}); }; +exports.runClassicPlugin = runClassicPlugin; diff --git a/FlowPlugins/FlowHelpers/1.0.0/cliParsers.js b/FlowPlugins/FlowHelpers/1.0.0/cliParsers.js new file mode 100644 index 000000000..27d860114 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/cliParsers.js @@ -0,0 +1,162 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.editreadyParser = exports.getFFmpegVar = exports.getFFmpegPercentage = exports.ffmpegParser = exports.handbrakeParser = void 0; +var handbrakeParser = function (_a) { + var str = _a.str; + if (typeof str !== 'string') { + return 0; + } + var percentage = 0; + var numbers = '0123456789'; + var n = str.indexOf('%'); + if (str.length >= 6 + && str.indexOf('%') >= 6 + && numbers.includes(str.charAt(n - 5))) { + var output = str.substring(n - 6, n + 1); + var outputArr = output.split(''); + outputArr.splice(outputArr.length - 1, 1); + output = outputArr.join(''); + var outputNum = Number(output); + if (outputNum > 0) { + percentage = outputNum; + } + } + return percentage; +}; +exports.handbrakeParser = handbrakeParser; +// frame= 889 fps=106 q=26.0 Lsize= 25526kB time=00:00:35.69 bitrate=5858.3kbits/s speed=4.25x +var getFFmpegVar = function (_a) { + var str = _a.str, variable = _a.variable; + if (typeof str !== 'string') { + return ''; + } + var idx = str.indexOf(variable); + var out = ''; + var initSpacesEnded = false; + if (idx >= 0) { + var startIdx = idx + variable.length + 1; + for (var i = startIdx; i < str.length; i += 1) { + if (initSpacesEnded === true && str[i] === ' ') { + break; + } + else if (initSpacesEnded === false && str[i] !== ' ') { + initSpacesEnded = true; + } + if (initSpacesEnded === true && str[i] !== ' ') { + out += str[i]; + } + } + } + return out; +}; +exports.getFFmpegVar = getFFmpegVar; +var getFFmpegPercentage = function (_a) { + var time = _a.time, f = _a.f, fc = _a.fc, vf = _a.vf, d = _a.d; + var frameCount01 = fc; + var VideoFrameRate = vf; + var Duration = d; + var perc = 0; + var frame = parseInt(f, 10); + frameCount01 = Math.ceil(frameCount01); + VideoFrameRate = Math.ceil(VideoFrameRate); + Duration = Math.ceil(Duration); + if (frame > 0) { + if (frameCount01 > 0) { + perc = ((frame / frameCount01) * 100); + } + else if (VideoFrameRate > 0 && Duration > 0) { + perc = ((frame / (VideoFrameRate * Duration)) * 100); + } + else { + perc = (frame); + } + } + else if (time > 0 && Duration > 0) { + perc = ((time / Duration) * 100); + } + var percString = perc.toFixed(2); + // eslint-disable-next-line no-restricted-globals + if (isNaN(perc)) { + return 0.00; + } + return parseFloat(percString); +}; +exports.getFFmpegPercentage = getFFmpegPercentage; +var ffmpegParser = function (_a) { + var str = _a.str, frameCount = _a.frameCount, videoFrameRate = _a.videoFrameRate, ffprobeDuration = _a.ffprobeDuration, metaDuration = _a.metaDuration; + if (typeof str !== 'string') { + return 0; + } + var percentage = 0; + if (str.length >= 6) { + var frame = getFFmpegVar({ + str: str, + variable: 'frame', + }); + var time = 0; + // get time + var timeStr = getFFmpegVar({ + str: str, + variable: 'time', + }); + if (timeStr) { + var timeArr = timeStr.split(':'); + if (timeArr.length === 3) { + var hours = parseInt(timeArr[0], 10); + var minutes = parseInt(timeArr[1], 10); + var seconds = parseInt(timeArr[2], 10); + time = (hours * 3600) + (minutes * 60) + seconds; + } + } + var frameRate = videoFrameRate || 0; + var duration = 0; + if (ffprobeDuration + && parseFloat(ffprobeDuration) > 0) { + duration = parseFloat(ffprobeDuration); + } + else if (metaDuration) { + duration = metaDuration; + } + var per = getFFmpegPercentage({ + time: time, + f: frame, + fc: frameCount, + vf: frameRate, + d: duration, + }); + var outputNum = Number(per); + if (outputNum > 0) { + percentage = outputNum; + } + } + return percentage; +}; +exports.ffmpegParser = ffmpegParser; +var editreadyParser = function (_a) { + var str = _a.str; + if (typeof str !== 'string') { + return 0; + } + var percentage = 0; + // const ex = 'STATUS: {"progress": "0.0000000"}'; + if (str.includes('STATUS:')) { + var parts = str.split('STATUS:'); + if (parts[1]) { + try { + var json = JSON.parse(parts[1]); + var progress = parseFloat(json.progress); + var percStr = (progress * 100).toFixed(2); + percentage = parseFloat(percStr); + } + catch (err) { + // err + } + } + } + // eslint-disable-next-line no-restricted-globals + if (isNaN(percentage)) { + return 0.00; + } + return percentage; +}; +exports.editreadyParser = editreadyParser; diff --git a/FlowPlugins/FlowHelpers/1.0.0/cliUtils.js b/FlowPlugins/FlowHelpers/1.0.0/cliUtils.js new file mode 100644 index 000000000..5451c39da --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/cliUtils.js @@ -0,0 +1,285 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CLI = exports.getFFmpegVar = void 0; +var cliParsers_1 = require("./cliParsers"); +var fs = require('fs'); +var fancyTimeFormat = function (time) { + // Hours, minutes and seconds + // eslint-disable-next-line no-bitwise + var hrs = ~~(time / 3600); + // eslint-disable-next-line no-bitwise + var mins = ~~((time % 3600) / 60); + // eslint-disable-next-line no-bitwise + var secs = ~~time % 60; + // Output like "1:01" or "4:03:59" or "123:03:59" + var ret = ''; + // if (hrs > 0) { + ret += "".concat(hrs, ":").concat(mins < 10 ? '0' : ''); + // } + ret += "".concat(mins, ":").concat(secs < 10 ? '0' : ''); + ret += "".concat(secs); + return ret; +}; +// frame= 889 fps=106 q=26.0 Lsize= 25526kB time=00:00:35.69 bitrate=5858.3kbits/s speed=4.25x +var getFFmpegVar = function (_a) { + var str = _a.str, variable = _a.variable; + if (typeof str !== 'string') { + return ''; + } + var idx = str.indexOf(variable); + var out = ''; + var initSpacesEnded = false; + if (idx >= 0) { + var startIdx = idx + variable.length + 1; + for (var i = startIdx; i < str.length; i += 1) { + if (initSpacesEnded === true && str[i] === ' ') { + break; + } + else if (initSpacesEnded === false && str[i] !== ' ') { + initSpacesEnded = true; + } + if (initSpacesEnded === true && str[i] !== ' ') { + out += str[i]; + } + } + } + return out; +}; +exports.getFFmpegVar = getFFmpegVar; +var CLI = /** @class */ (function () { + function CLI(config) { + var _this = this; + // @ts-expect-error init + this.config = {}; + this.progAVG = []; + this.oldOutSize = 0; + this.oldEstSize = 0; + this.oldProgress = 0; + this.lastProgCheck = 0; + this.updateETA = function (perc) { + if (perc > 0) { + if (_this.lastProgCheck === 0) { + _this.lastProgCheck = new Date().getTime(); + _this.oldProgress = perc; + } + else if (perc !== _this.oldProgress) { + var n = new Date().getTime(); + var secsSinceLastCheck = (n - _this.lastProgCheck) / 1000; + if (secsSinceLastCheck > 1) { + // eta total + var eta = Math.round((100 / (perc - _this.oldProgress)) * secsSinceLastCheck); + // eta remaining + eta *= ((100 - perc) / 100); + _this.progAVG.push(eta); + // let values = [2, 56, 3, 41, 0, 4, 100, 23]; + var sum = _this.progAVG.reduce( + // eslint-disable-next-line + function (previous, current) { return (current += previous); }); + var avg = sum / _this.progAVG.length; + // est size + var estSize = 0; + var outputFileSizeInGbytes = void 0; + try { + if (fs.existsSync(_this.config.outputFilePath)) { + var singleFileSize = fs.statSync(_this.config.outputFilePath); + singleFileSize = singleFileSize.size; + outputFileSizeInGbytes = singleFileSize / (1024 * 1024 * 1024); + if (outputFileSizeInGbytes !== _this.oldOutSize) { + _this.oldOutSize = outputFileSizeInGbytes; + estSize = outputFileSizeInGbytes + + ((100 - perc) / perc) * outputFileSizeInGbytes; + _this.oldEstSize = estSize; + } + } + } + catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + _this.config.updateWorker({ + ETA: fancyTimeFormat(avg), + outputFileSizeInGbytes: outputFileSizeInGbytes === undefined ? 0 : outputFileSizeInGbytes, + estSize: _this.oldEstSize === undefined ? 0 : _this.oldEstSize, + }); + if (_this.progAVG.length > 30) { + _this.progAVG.splice(0, 1); + } + _this.lastProgCheck = n; + _this.oldProgress = perc; + } + } + } + }; + this.parseOutput = function (data) { + var _a, _b, _c, _d, _e, _f, _g; + var str = "".concat(data); + // + if (_this.config.logFullCliOutput === true) { + _this.config.jobLog(str); + } + if (_this.config.cli.toLowerCase().includes('handbrake')) { + var percentage = (0, cliParsers_1.handbrakeParser)({ + str: str, + }); + if (percentage > 0) { + _this.updateETA(percentage); + _this.config.updateWorker({ + percentage: percentage, + }); + } + } + else if (_this.config.cli.toLowerCase().includes('ffmpeg')) { + var n = str.indexOf('fps'); + var shouldUpdate = str.length >= 6 && n >= 6; + var fps = parseInt((0, exports.getFFmpegVar)({ + str: str, + variable: 'fps', + }), 10); + var frameCount = 0; + try { + // @ts-expect-error type + var frameCountTmp = (_a = _this.config.inputFileObj.ffProbeData) === null || _a === void 0 ? void 0 : _a.streams.filter(function (row) { return row.codec_type === 'video'; })[0].nb_frames; + if (frameCountTmp + // @ts-expect-error type + && !isNaN(frameCountTmp)) { // eslint-disable-line no-restricted-globals + // @ts-expect-error type + frameCount = frameCountTmp; + } + } + catch (err) { + // err + } + var percentage = (0, cliParsers_1.ffmpegParser)({ + str: str, + frameCount: frameCount, + videoFrameRate: (_c = (_b = _this.config.inputFileObj) === null || _b === void 0 ? void 0 : _b.meta) === null || _c === void 0 ? void 0 : _c.VideoFrameRate, + ffprobeDuration: (_e = (_d = _this.config.inputFileObj.ffProbeData) === null || _d === void 0 ? void 0 : _d.format) === null || _e === void 0 ? void 0 : _e.duration, + metaDuration: (_g = (_f = _this.config.inputFileObj) === null || _f === void 0 ? void 0 : _f.meta) === null || _g === void 0 ? void 0 : _g.Duration, + }); + if (shouldUpdate === true && fps > 0) { + _this.config.updateWorker({ + fps: fps, + }); + } + if (percentage > 0) { + _this.updateETA(percentage); + _this.config.updateWorker({ + percentage: percentage, + }); + } + } + else if (_this.config.cli.toLowerCase().includes('editready')) { + var percentage = (0, cliParsers_1.editreadyParser)({ + str: str, + }); + if (percentage > 0) { + _this.updateETA(percentage); + _this.config.updateWorker({ + percentage: percentage, + }); + } + } + }; + this.runCli = function () { return __awaiter(_this, void 0, void 0, function () { + var childProcess, errorLogFull, cliExitCode; + var _this = this; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + childProcess = require('child_process'); + errorLogFull = []; + // eslint-disable-next-line no-console + this.config.jobLog("Running ".concat(this.config.cli, " ").concat(this.config.spawnArgs.join(' '))); + return [4 /*yield*/, new Promise(function (resolve) { + try { + var opts = _this.config.spawnOpts || {}; + var spawnArgs = _this.config.spawnArgs.map(function (row) { return row.trim(); }).filter(function (row) { return row !== ''; }); + var thread = childProcess.spawn(_this.config.cli, spawnArgs, opts); + thread.stdout.on('data', function (data) { + // eslint-disable-next-line no-console + // console.log(data.toString()); + errorLogFull.push(data.toString()); + _this.parseOutput(data); + }); + thread.stderr.on('data', function (data) { + // eslint-disable-next-line no-console + // console.log(data.toString()); + errorLogFull.push(data.toString()); + _this.parseOutput(data); + }); + thread.on('error', function () { + // catches execution error (bad file) + // eslint-disable-next-line no-console + console.log(1, "Error executing binary: ".concat(_this.config.cli)); + resolve(1); + }); + // thread.stdout.pipe(process.stdout); + // thread.stderr.pipe(process.stderr); + thread.on('close', function (code) { + if (code !== 0) { + // eslint-disable-next-line no-console + console.log(code, 'CLI error'); + } + resolve(code); + }); + } + catch (err) { + // catches execution error (no file) + // eslint-disable-next-line no-console + console.log(1, "Error executing binary: ".concat(_this.config.cli)); + resolve(1); + } + })]; + case 1: + cliExitCode = _a.sent(); + if (!this.config.logFullCliOutput) { + this.config.jobLog(errorLogFull.slice(-1000).join('')); + } + return [2 /*return*/, { + cliExitCode: cliExitCode, + errorLogFull: errorLogFull, + }]; + } + }); + }); }; + this.config = config; + } + return CLI; +}()); +exports.CLI = CLI; diff --git a/FlowPlugins/FlowHelpers/1.0.0/fileUtils.js b/FlowPlugins/FlowHelpers/1.0.0/fileUtils.js new file mode 100644 index 000000000..5980e9aa6 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/fileUtils.js @@ -0,0 +1,192 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getScanTypes = exports.getPluginWorkDir = exports.moveFileAndValidate = exports.getSubStem = exports.getFfType = exports.getFileAbosluteDir = exports.getFileName = exports.getContainer = void 0; +var fs_1 = require("fs"); +var getContainer = function (filePath) { + var parts = filePath.split('.'); + return parts[parts.length - 1]; +}; +exports.getContainer = getContainer; +var getFileName = function (filePath) { + var parts = filePath.split('/'); + var fileNameAndContainer = parts[parts.length - 1]; + var parts2 = fileNameAndContainer.split('.'); + parts2.pop(); + return parts2.join('.'); +}; +exports.getFileName = getFileName; +var getFileAbosluteDir = function (filePath) { + var parts = filePath.split('/'); + parts.pop(); + return parts.join('/'); +}; +exports.getFileAbosluteDir = getFileAbosluteDir; +var getFfType = function (codecType) { return (codecType === 'video' ? 'v' : 'a'); }; +exports.getFfType = getFfType; +var getSubStem = function (_a) { + var inputPathStem = _a.inputPathStem, inputPath = _a.inputPath; + var subStem = inputPath.substring(inputPathStem.length); + var parts = subStem.split('/'); + parts.pop(); + return parts.join('/'); +}; +exports.getSubStem = getSubStem; +var getFileSize = function (file) { return __awaiter(void 0, void 0, void 0, function () { + var stats, size; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, fs_1.promises.stat(file)]; + case 1: + stats = _a.sent(); + size = stats.size; + return [2 /*return*/, size]; + } + }); +}); }; +var moveFileAndValidate = function (_a) { + var inputPath = _a.inputPath, outputPath = _a.outputPath, args = _a.args; + return __awaiter(void 0, void 0, void 0, function () { + var inputSize, res1, outputSize, err_1, res2, errMessage; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: return [4 /*yield*/, getFileSize(inputPath)]; + case 1: + inputSize = _b.sent(); + args.jobLog("Attempt 1: Moving file from ".concat(inputPath, " to ").concat(outputPath)); + return [4 /*yield*/, new Promise(function (resolve) { + args.deps.gracefulfs.rename(inputPath, outputPath, function (err) { + if (err) { + args.jobLog("Failed to move file from ".concat(inputPath, " to ").concat(outputPath)); + args.jobLog(JSON.stringify(err)); + resolve(false); + } + else { + resolve(true); + } + }); + })]; + case 2: + res1 = _b.sent(); + outputSize = 0; + _b.label = 3; + case 3: + _b.trys.push([3, 5, , 6]); + return [4 /*yield*/, getFileSize(outputPath)]; + case 4: + outputSize = _b.sent(); + return [3 /*break*/, 6]; + case 5: + err_1 = _b.sent(); + args.jobLog(JSON.stringify(err_1)); + return [3 /*break*/, 6]; + case 6: + if (!(!res1 || inputSize !== outputSize)) return [3 /*break*/, 9]; + args.jobLog("Attempt 1 failed: Moving file from ".concat(inputPath, " to ").concat(outputPath)); + args.jobLog("Attempt 2: Moving file from ".concat(inputPath, " to ").concat(outputPath)); + return [4 /*yield*/, new Promise(function (resolve) { + args.deps.mvdir(inputPath, outputPath, { overwrite: true }) + .then(function () { + resolve(true); + }).catch(function (err) { + args.jobLog("Failed to move file from ".concat(inputPath, " to ").concat(outputPath)); + args.jobLog(JSON.stringify(err)); + resolve(false); + }); + })]; + case 7: + res2 = _b.sent(); + return [4 /*yield*/, getFileSize(outputPath)]; + case 8: + outputSize = _b.sent(); + if (!res2 || inputSize !== outputSize) { + errMessage = "Failed to move file from ".concat(inputPath, " to ").concat(outputPath, ", check errors above"); + args.jobLog(errMessage); + throw new Error(errMessage); + } + _b.label = 9; + case 9: return [2 /*return*/]; + } + }); + }); +}; +exports.moveFileAndValidate = moveFileAndValidate; +var getPluginWorkDir = function (args) { + var pluginWorkDir = "".concat(args.workDir, "/").concat(new Date().getTime()); + args.deps.fsextra.ensureDirSync(pluginWorkDir); + return pluginWorkDir; +}; +exports.getPluginWorkDir = getPluginWorkDir; +var getScanTypes = function (pluginsTextRaw) { + var scanTypes = { + exifToolScan: true, + mediaInfoScan: false, + closedCaptionScan: false, + }; + var scannerTypes = [ + // needed for frame and duration data for ffmpeg + // { + // type: 'exifToolScan', + // terms: [ + // 'meta', + // ], + // }, + { + type: 'mediaInfoScan', + terms: [ + 'mediaInfo', + ], + }, + { + type: 'closedCaptionScan', + terms: [ + 'hasClosedCaptions', + ], + }, + ]; + var text = pluginsTextRaw.join(''); + scannerTypes.forEach(function (scanner) { + scanner.terms.forEach(function (term) { + if (text.includes(term)) { + scanTypes[scanner.type] = true; + } + }); + }); + return scanTypes; +}; +exports.getScanTypes = getScanTypes; diff --git a/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.js b/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.js new file mode 100644 index 000000000..48e3d9126 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.js @@ -0,0 +1,430 @@ +"use strict"; +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getEncoder = exports.getBestNvencDevice = exports.hasEncoder = void 0; +var os_1 = __importDefault(require("os")); +var hasEncoder = function (_a) { + var ffmpegPath = _a.ffmpegPath, encoder = _a.encoder, inputArgs = _a.inputArgs, outputArgs = _a.outputArgs, filter = _a.filter, args = _a.args; + return __awaiter(void 0, void 0, void 0, function () { + var spawn, isEnabled, commandArr_1, err_1; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + spawn = require('child_process').spawn; + isEnabled = false; + _b.label = 1; + case 1: + _b.trys.push([1, 3, , 4]); + commandArr_1 = __spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray(__spreadArray([], inputArgs, true), [ + '-f', + 'lavfi', + '-i', + 'color=c=black:s=256x256:d=1:r=30' + ], false), (filter ? filter.split(' ') : []), true), [ + '-c:v', + encoder + ], false), outputArgs, true), [ + '-f', + 'null', + '/dev/null', + ], false); + args.jobLog("Checking for encoder ".concat(encoder, " with command:")); + args.jobLog("".concat(ffmpegPath, " ").concat(commandArr_1.join(' '))); + return [4 /*yield*/, new Promise(function (resolve) { + var error = function () { + resolve(false); + }; + var stderr = ''; + try { + var thread = spawn(ffmpegPath, commandArr_1); + thread.on('error', function () { + // catches execution error (bad file) + error(); + }); + thread.stdout.on('data', function (data) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + stderr += data; + }); + thread.stderr.on('data', function (data) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + stderr += data; + }); + thread.on('close', function (code) { + if (code !== 0) { + error(); + } + else { + resolve(true); + } + }); + } + catch (err) { + // catches execution error (no file) + error(); + } + })]; + case 2: + isEnabled = _b.sent(); + args.jobLog("Encoder ".concat(encoder, " is ").concat(isEnabled ? 'enabled' : 'disabled')); + return [3 /*break*/, 4]; + case 3: + err_1 = _b.sent(); + // eslint-disable-next-line no-console + console.log(err_1); + return [3 /*break*/, 4]; + case 4: return [2 /*return*/, isEnabled]; + } + }); + }); +}; +exports.hasEncoder = hasEncoder; +// credit to UNCode101 for this +var getBestNvencDevice = function (_a) { + var args = _a.args, nvencDevice = _a.nvencDevice; + var execSync = require('child_process').execSync; + var gpu_num = -1; + var lowest_gpu_util = 100000; + var result_util = 0; + var gpu_count = -1; + var gpu_names = ''; + var 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(); + var gpu_namesArr = 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_namesArr[0].includes('nvidia-smi')) { + gpu_count = gpu_namesArr.length; + } + } + catch (error) { + args.jobLog('Error in reading nvidia-smi output! \n'); + } + if (gpu_count > 0) { + for (var gpui = 0; gpui < gpu_count; gpui += 1) { + // Check if GPU # is in GPUs to exclude + if (gpus_to_exclude.includes(String(gpui))) { + args.jobLog("GPU ".concat(gpui, ": ").concat(gpu_names[gpui], " is in exclusion list, will not be used!\n")); + } + else { + try { + var cmd_gpu = "nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits -i ".concat(gpui); + result_util = parseInt(execSync(cmd_gpu), 10); + if (!Number.isNaN(result_util)) { // != "No devices were found") { + args.jobLog("GPU ".concat(gpui, " : Utilization ").concat(result_util, "%\n")); + if (result_util < lowest_gpu_util) { + gpu_num = gpui; + lowest_gpu_util = result_util; + } + } + } + catch (error) { + args.jobLog("Error in reading GPU ".concat(gpui, " Utilization\nError: ").concat(error, "\n")); + } + } + } + } + if (gpu_num >= 0) { + // eslint-disable-next-line no-param-reassign + nvencDevice.inputArgs.push('-hwaccel_device', "".concat(gpu_num)); + // eslint-disable-next-line no-param-reassign + nvencDevice.outputArgs.push('-gpu', "".concat(gpu_num)); + } + return nvencDevice; +}; +exports.getBestNvencDevice = getBestNvencDevice; +var encoderFilter = function (encoder, targetCodec) { + if (targetCodec === 'hevc' && (encoder.includes('hevc') || encoder.includes('h265'))) { + return true; + } + if (targetCodec === 'h264' && encoder.includes('h264')) { + return true; + } + if (targetCodec === 'av1' && encoder.includes('av1')) { + return true; + } + return false; +}; +var getEncoder = function (_a) { + var targetCodec = _a.targetCodec, hardwareEncoding = _a.hardwareEncoding, hardwareType = _a.hardwareType, args = _a.args; + return __awaiter(void 0, void 0, void 0, function () { + var gpuEncoders, filteredGpuEncoders, idx, _i, filteredGpuEncoders_1, gpuEncoder, _b, enabledDevices, res; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: + if (!(args.workerType + && args.workerType.includes('gpu') + && hardwareEncoding && (['hevc', 'h264', 'av1'].includes(targetCodec)))) return [3 /*break*/, 5]; + gpuEncoders = [ + { + encoder: 'hevc_nvenc', + enabled: false, + inputArgs: [ + '-hwaccel', + 'cuda', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'hevc_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'hevc_qsv', + enabled: false, + inputArgs: [ + '-hwaccel', + 'qsv', + ], + outputArgs: __spreadArray([], (os_1.default.platform() === 'win32' ? ['-load_plugin', 'hevc_hw'] : []), true), + filter: '', + }, + { + encoder: 'hevc_vaapi', + inputArgs: [ + '-hwaccel', + 'vaapi', + '-hwaccel_device', + '/dev/dri/renderD128', + '-hwaccel_output_format', + 'vaapi', + ], + outputArgs: [], + enabled: false, + filter: '-vf format=nv12,hwupload', + }, + { + encoder: 'hevc_videotoolbox', + enabled: false, + inputArgs: [ + '-hwaccel', + 'videotoolbox', + ], + outputArgs: [], + filter: '', + }, + // h264 + { + encoder: 'h264_nvenc', + enabled: false, + inputArgs: [ + '-hwaccel', + 'cuda', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_qsv', + enabled: false, + inputArgs: [ + '-hwaccel', + 'qsv', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_videotoolbox', + enabled: false, + inputArgs: [ + '-hwaccel', + 'videotoolbox', + ], + outputArgs: [], + filter: '', + }, + // av1 + { + encoder: 'av1_nvenc', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_qsv', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_vaapi', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + ]; + filteredGpuEncoders = gpuEncoders.filter(function (device) { return encoderFilter(device.encoder, targetCodec); }); + if (hardwareEncoding && hardwareType !== 'auto') { + idx = filteredGpuEncoders.findIndex(function (device) { return device.encoder.includes(hardwareType); }); + if (idx === -1) { + throw new Error("Could not find encoder ".concat(targetCodec, " for hardware ").concat(hardwareType)); + } + return [2 /*return*/, __assign(__assign({}, filteredGpuEncoders[idx]), { isGpu: true, enabledDevices: [] })]; + } + args.jobLog(JSON.stringify({ filteredGpuEncoders: filteredGpuEncoders })); + _i = 0, filteredGpuEncoders_1 = filteredGpuEncoders; + _c.label = 1; + case 1: + if (!(_i < filteredGpuEncoders_1.length)) return [3 /*break*/, 4]; + gpuEncoder = filteredGpuEncoders_1[_i]; + // eslint-disable-next-line no-await-in-loop + _b = gpuEncoder; + return [4 /*yield*/, (0, exports.hasEncoder)({ + ffmpegPath: args.ffmpegPath, + encoder: gpuEncoder.encoder, + inputArgs: gpuEncoder.inputArgs, + outputArgs: gpuEncoder.outputArgs, + filter: gpuEncoder.filter, + args: args, + })]; + case 2: + // eslint-disable-next-line no-await-in-loop + _b.enabled = _c.sent(); + _c.label = 3; + case 3: + _i++; + return [3 /*break*/, 1]; + case 4: + enabledDevices = filteredGpuEncoders.filter(function (device) { return device.enabled === true; }); + args.jobLog(JSON.stringify({ enabledDevices: enabledDevices })); + if (enabledDevices.length > 0) { + if (enabledDevices[0].encoder.includes('nvenc')) { + res = (0, exports.getBestNvencDevice)({ + args: args, + nvencDevice: enabledDevices[0], + }); + return [2 /*return*/, __assign(__assign({}, res), { isGpu: true, enabledDevices: enabledDevices })]; + } + return [2 /*return*/, { + encoder: enabledDevices[0].encoder, + inputArgs: enabledDevices[0].inputArgs, + outputArgs: enabledDevices[0].outputArgs, + isGpu: true, + enabledDevices: enabledDevices, + }]; + } + _c.label = 5; + case 5: + if (targetCodec === 'hevc') { + return [2 /*return*/, { + encoder: 'libx265', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }]; + } + if (targetCodec === 'h264') { + return [2 /*return*/, { + encoder: 'libx264', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }]; + } + if (targetCodec === 'av1') { + return [2 /*return*/, { + encoder: 'libsvtav1', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }]; + } + return [2 /*return*/, { + encoder: targetCodec, + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }]; + } + }); + }); +}; +exports.getEncoder = getEncoder; diff --git a/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.test.js b/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.test.js new file mode 100644 index 000000000..88def6d4d --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/hardwareUtils.test.js @@ -0,0 +1,68 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var hardwareUtils_1 = require("./hardwareUtils"); +var run = function () { return __awaiter(void 0, void 0, void 0, function () { + var encoderProperties; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, (0, hardwareUtils_1.getEncoder)({ + targetCodec: 'h264', + hardwareEncoding: true, + hardwareType: 'auto', + // @ts-expect-error type + args: { + workerType: 'transcodegpu', + ffmpegPath: 'ffmpeg', + jobLog: function (t) { + // eslint-disable-next-line no-console + console.log(t); + }, + }, + })]; + case 1: + encoderProperties = _a.sent(); + // eslint-disable-next-line no-console + console.log({ + encoderProperties: encoderProperties, + }); + return [2 /*return*/]; + } + }); +}); }; +void run(); diff --git a/FlowPlugins/FlowHelpers/1.0.0/interfaces/interfaces.js b/FlowPlugins/FlowHelpers/1.0.0/interfaces/interfaces.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/interfaces/interfaces.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/IFileObject.js b/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/IFileObject.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/IFileObject.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/jobInterface.js b/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/jobInterface.js new file mode 100644 index 000000000..c8ad2e549 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/interfaces/synced/jobInterface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/FlowPlugins/FlowHelpers/1.0.0/normJoinPath.js b/FlowPlugins/FlowHelpers/1.0.0/normJoinPath.js new file mode 100644 index 000000000..8d2db7bb6 --- /dev/null +++ b/FlowPlugins/FlowHelpers/1.0.0/normJoinPath.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var formatWindowsRootFolder = function (path) { + // Remove '.' from end of Windows root folder mapping e.g. 'E:.' + if (path.length === 3 + && path.charAt(1) === ':' + && path.charAt(2) === '.') { + // eslint-disable-next-line no-param-reassign + path = path.slice(0, -1); + } + return path; +}; +var normJoinPath = function (_a) { + var upath = _a.upath, paths = _a.paths; + var path = upath.joinSafe.apply(upath, paths); + path = formatWindowsRootFolder(path); + return path; +}; +exports.default = normJoinPath; diff --git a/FlowPluginsTs/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.ts new file mode 100644 index 000000000..4540cf131 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/audio/checkChannelCount/1.0.0/index.ts @@ -0,0 +1,87 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Channel Count', + description: 'Check streams for specified channel count', + style: { + borderColor: 'orange', + }, + tags: 'audio', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'channelCount', + type: 'number', + defaultValue: '2', + inputUI: { + type: 'dropdown', + options: [ + '1', + '2', + '6', + '8', + ], + }, + tooltip: 'Specify channel count to check for', + }, + + ], + outputs: [ + { + number: 1, + tooltip: 'File has stream with specified channel count', + }, + { + number: 2, + tooltip: 'File does not have stream with specified channel count', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const channelCount = Number(args.inputs.channelCount); + + let hasSpecifiedChannelCount = false; + + args.jobLog(`Checking for ${channelCount} channels`); + + if (Array.isArray(args?.inputFileObj?.ffProbeData?.streams)) { + for (let i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + const stream = args.inputFileObj.ffProbeData.streams[i]; + + args.jobLog(`Stream ${i} has ${stream.channels} channels`); + + if ( + stream.channels === channelCount + ) { + hasSpecifiedChannelCount = true; + } + } + } else { + throw new Error('File has no stream data'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: hasSpecifiedChannelCount ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.ts new file mode 100644 index 000000000..eabfbddd5 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/audio/normalizeAudio/1.0.0/index.ts @@ -0,0 +1,180 @@ +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { getContainer, getFileName, getPluginWorkDir } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Normalize Audio', + description: 'Normalize Audio', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'i', + type: 'string', + defaultValue: '-23.0', + inputUI: { + type: 'text', + }, + tooltip: `"i" value used in loudnorm pass \\n + defaults to -23.0`, + }, + { + name: 'lra', + type: 'string', + defaultValue: '7.0', + inputUI: { + type: 'text', + }, + tooltip: `Desired lra value. \\n Defaults to 7.0 + `, + }, + { + name: 'tp', + type: 'string', + defaultValue: '-2.0', + inputUI: { + type: 'text', + }, + tooltip: `Desired "tp" value. \\n Defaults to -2.0 + `, + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + // setup required varibles + const loudNorm_i = args.inputs.i; + const { lra } = args.inputs; + const { tp } = args.inputs; + + const container = getContainer(args.inputFileObj._id); + const outputFilePath = `${getPluginWorkDir(args)}/${getFileName(args.inputFileObj._id)}.${container}`; + + const normArgs1: string[] = [ + '-i', + args.inputFileObj._id, + '-af', + `loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json`, + '-f', + 'null', + 'NUL', + '-map', + '0', + '-c', + 'copy', + ]; + + const cli = new CLI({ + cli: args.ffmpegPath, + spawnArgs: normArgs1, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: '', + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + const lines = res.errorLogFull; + + let idx = -1; + + // get last index of Parsed_loudnorm + lines.forEach((line, i) => { + if (line.includes('Parsed_loudnorm')) { + idx = i; + } + }); + + if (idx === -1) { + throw new Error('Failed to find loudnorm in report, please rerun'); + } + + const parts = lines[idx].split(']'); + parts.shift(); + let infoLine = parts.join(']'); + infoLine = infoLine.split('\r\n').join('').split('\t').join(''); + + const loudNormValues = JSON.parse(infoLine); + + args.jobLog(`Loudnorm first pass values returned: \n${JSON.stringify(loudNormValues)}`); + + const normArgs2 = [ + '-i', + args.inputFileObj._id, + '-map', + '0', + '-c', + 'copy', + '-c:a', + 'aac', + '-b:a', + '192k', + '-af', + `loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:` + + `measured_i=${loudNormValues.input_i}:` + + `measured_lra=${loudNormValues.input_lra}:` + + `measured_tp=${loudNormValues.input_tp}:` + + `measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} `, + outputFilePath, + ]; + + const cli2 = new CLI({ + cli: args.ffmpegPath, + spawnArgs: normArgs2, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res2 = await cli2.runCli(); + + if (res2.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + + return { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.ts new file mode 100644 index 000000000..8eb33f8d1 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicFilterPlugin/1.0.0/index.ts @@ -0,0 +1,68 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { runClassicPlugin } from '../../../../FlowHelpers/1.0.0/classicPlugins'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Run Classic Filter Plugin', + description: 'Run one of Tdarr\'s classic plugins that has Operation: Filter', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'pluginSourceId', + type: 'string', + defaultValue: 'Community:Tdarr_Plugin_00td_filter_by_codec', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify the classic plugin ID', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File met conditions, would traditionally continue to next plugin in plugin stack', + }, + { + number: 2, + tooltip: 'File did not meet conditions, would traditionally break out of plugin stack', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const outcome = await runClassicPlugin(args, 'filter'); + const { result } = outcome; + + args.jobLog(JSON.stringify(result, null, 2)); + + const outputNumber = result?.processFile ? 1 : 2; + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; + +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.ts new file mode 100644 index 000000000..654050581 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/classic/runClassicTranscodePlugin/1.0.0/index.ts @@ -0,0 +1,206 @@ +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { runClassicPlugin } from '../../../../FlowHelpers/1.0.0/classicPlugins'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Run Classic Transcode Plugin', + description: 'Run one of Tdarr\'s classic plugins that has Operation: Transcode', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'pluginSourceId', + type: 'string', + defaultValue: 'Community:Tdarr_Plugin_MC93_Migz1FFMPEG', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify the classic plugin ID', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +const replaceContainer = (filePath:string, container:string): string => { + const parts = filePath.split('.'); + parts[parts.length - 1] = container.split('.').join(''); + return parts.join('.'); +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const outcome = await runClassicPlugin(args, 'transcode'); + const { result, absolutePath } = outcome; + + let { cacheFilePath } = outcome; + + args.jobLog(JSON.stringify(result, null, 2)); + + if (!result) { + args.jobLog('No result from classic plugin. Continuing to next flow plugin.'); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; + } + + // --- Backwards compatibility------------ + if (result.handBrakeMode) { + result.handbrakeMode = result.handBrakeMode; + } + + if (result.FFmpegMode) { + result.ffmpegMode = result.FFmpegMode; + } + //---------------------------------------- + + if (result.ffmpegMode) { + result.cliToUse = 'ffmpeg'; + } else if (result.handbrakeMode) { + result.cliToUse = 'handbrake'; + } else if (typeof result?.custom?.cliPath === 'string') { + const { cliPath } = result.custom; + if (cliPath.toLowerCase().includes('ffmpeg')) { + result.cliToUse = 'ffmpeg'; + } else if (cliPath.toLowerCase().includes('handbrake')) { + result.cliToUse = 'handbrake'; + } else if (cliPath.toLowerCase().includes('editready')) { + result.cliToUse = 'editready'; + } else if (cliPath.toLowerCase().includes('av1an')) { + result.cliToUse = 'av1an'; + } + } + + result.workerLog = result.transcodeSettingsLog; + args.jobLog(JSON.stringify(result, null, 2)); + + if (result.error) { + throw new Error(`Plugin ${absolutePath} failed: ${result.error}`); + } if (result.processFile !== true) { + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; + } + + const customArgs = result?.custom?.args; + const isCustomConfig = (Array.isArray(customArgs) && customArgs.length > 0) + || (typeof customArgs === 'string' + // @ts-expect-error length + && customArgs.length + > 0); + + if (!isCustomConfig) { + cacheFilePath = replaceContainer(cacheFilePath, result.container); + } else { + // @ts-expect-error type + cacheFilePath = result.custom.outputPath; + } + + let presetSplit; + if (result.preset.includes('')) { + presetSplit = result.preset.split(''); + } else { + presetSplit = result.preset.split(','); + } + + let workerCommand: string[] = []; + let cliPath = ''; + + if (isCustomConfig) { + // @ts-expect-error cliPath + cliPath = result?.custom?.cliPath; + + if (Array.isArray(customArgs)) { + workerCommand = customArgs; + } else { + workerCommand = [ + ...args.deps.parseArgsStringToArgv(customArgs, '', ''), + ]; + } + } else { + // working on windows with '` and spaces + // working on unix with ' + switch (true) { + case result.cliToUse === 'handbrake': + workerCommand = [ + '-i', + `${args.inputFileObj._id}`, + '-o', + `${cacheFilePath}`, + ...args.deps.parseArgsStringToArgv(result.preset, '', ''), + ]; + + cliPath = `${args.handbrakePath}`; + break; + + case result.cliToUse === 'ffmpeg': + workerCommand = [ + ...args.deps.parseArgsStringToArgv(presetSplit[0], '', ''), + '-i', + `${args.inputFileObj._id}`, + ...args.deps.parseArgsStringToArgv(presetSplit[1], '', ''), + `${cacheFilePath}`, + ]; + cliPath = `${args.ffmpegPath}`; + break; + default: + } + } + + const cli = new CLI({ + cli: cliPath, + spawnArgs: workerCommand, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: cacheFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog(`Running ${cliPath} failed`); + throw new Error(`Running ${cliPath} failed`); + } + + args.logOutcome('tSuc'); + + return { + outputFileObj: { + _id: cacheFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.ts new file mode 100644 index 000000000..b5da85c6c --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommand10BitVideo/1.0.0/index.ts @@ -0,0 +1,57 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: '10 Bit Video', + description: 'Set 10 Bit Video', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + for (let i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + const stream = args.variables.ffmpegCommand.streams[i]; + if (stream.codec_type === 'video') { + stream.outputArgs.push('-profile:v:{outputTypeIndex}', 'main10'); + + if (stream.outputArgs.includes('qsv')) { + stream.outputArgs.push('-vf', 'scale_qsv=format=p010le'); + } else { + stream.outputArgs.push('-pix_fmt:v:{outputTypeIndex}', 'p010le'); + } + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.ts new file mode 100644 index 000000000..9f05381fb --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCropBlackBars/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Crop Black Bars', + description: 'Crop Black Bars', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.ts new file mode 100644 index 000000000..e5c8c998d --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandCustomArguments/1.0.0/index.ts @@ -0,0 +1,75 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Custom Arguments', + description: 'Set FFmpeg custome input and output arguments', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'inputArguments', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Specify input arguments', + }, + + { + name: 'outputArguments', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Specify output arguments', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const inputArguments = String(args.inputs.inputArguments); + const outputArguments = String(args.inputs.outputArguments); + + if (inputArguments) { + args.variables.ffmpegCommand.overallInputArguments.push(...inputArguments.split(' ')); + } + + if (outputArguments) { + args.variables.ffmpegCommand.overallOuputArguments.push(...outputArguments.split(' ')); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.ts new file mode 100644 index 000000000..c937e0ab4 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandEnsureAudioStream/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Ensure Audio Stream', + description: 'Ensure that the file has an audio stream with set codec and channel count', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.ts new file mode 100644 index 000000000..d2bf22ff3 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandExecute/1.0.0/index.ts @@ -0,0 +1,188 @@ +import { + IffmpegCommandStream, + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { getFileName, getPluginWorkDir } from '../../../../FlowHelpers/1.0.0/fileUtils'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Execute', + description: 'Execute the created FFmpeg command', + style: { + borderColor: 'green', + }, + tags: 'video', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: 2, + icon: 'faPlay', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +const getOuputStreamIndex = (streams: IffmpegCommandStream[], stream: IffmpegCommandStream): number => { + let index = -1; + + for (let idx = 0; idx < streams.length; idx += 1) { + if (!stream.removed) { + index += 1; + } + + if (streams[idx].index === stream.index) { + break; + } + } + + return index; +}; + +const getOuputStreamTypeIndex = (streams: IffmpegCommandStream[], stream: IffmpegCommandStream): number => { + let index = -1; + + for (let idx = 0; idx < streams.length; idx += 1) { + if (!stream.removed && streams[idx].codec_type === stream.codec_type) { + index += 1; + } + + if (streams[idx].index === stream.index) { + break; + } + } + + return index; +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const cliArgs: string[] = []; + + cliArgs.push('-y'); + cliArgs.push('-i'); + cliArgs.push(args.inputFileObj._id); + + let { shouldProcess, streams } = args.variables.ffmpegCommand; + + if (args.variables.ffmpegCommand.overallInputArguments.length > 0) { + shouldProcess = true; + } + + const inputArgs: string[] = [ + ...args.variables.ffmpegCommand.overallInputArguments, + ]; + + streams = streams.filter((stream) => { + if (stream.removed) { + shouldProcess = true; + } + return !stream.removed; + }); + + for (let i = 0; i < streams.length; i += 1) { + const stream = streams[i]; + + stream.outputArgs = stream.outputArgs.map((arg) => { + if (arg.includes('{outputIndex}')) { + // eslint-disable-next-line no-param-reassign + arg = arg.replace('{outputIndex}', String(getOuputStreamIndex(streams, stream))); + } + + if (arg.includes('{outputTypeIndex}')) { + // eslint-disable-next-line no-param-reassign + arg = arg.replace('{outputTypeIndex}', String(getOuputStreamTypeIndex(streams, stream))); + } + + return arg; + }); + + cliArgs.push(...stream.mapArgs); + + if (stream.outputArgs.length === 0) { + cliArgs.push(`-c:${getOuputStreamIndex(streams, stream)}`, 'copy'); + } else { + cliArgs.push(...stream.outputArgs); + } + + inputArgs.push(...stream.inputArgs); + } + + const idx = cliArgs.indexOf('-i'); + cliArgs.splice(idx, 0, ...inputArgs); + + if (args.variables.ffmpegCommand.overallOuputArguments.length > 0) { + cliArgs.push(...args.variables.ffmpegCommand.overallOuputArguments); + shouldProcess = true; + } + + if (!shouldProcess) { + args.jobLog('No need to process file, already as required'); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; + } + + const outputFilePath = `${getPluginWorkDir(args)}/${getFileName(args.inputFileObj._id)}` + + `.${args.variables.ffmpegCommand.container}`; + + cliArgs.push(outputFilePath); + + const spawnArgs = cliArgs.map((row) => row.trim()).filter((row) => row !== ''); + + args.jobLog('Processing file'); + args.jobLog(JSON.stringify({ + spawnArgs, + outputFilePath, + })); + + args.updateWorker({ + CLIType: args.ffmpegPath, + preset: spawnArgs.join(' '), + }); + + const cli = new CLI({ + cli: args.ffmpegPath, + spawnArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog('Running FFmpeg failed'); + throw new Error('FFmpeg failed'); + } + + args.logOutcome('tSuc'); + + return { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.ts new file mode 100644 index 000000000..16022748d --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandHdrToSdr/1.0.0/index.ts @@ -0,0 +1,50 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'HDR to SDR', + description: 'Convert HDR to SDR', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + if (stream.codec_type === 'video') { + stream.outputArgs.push('-vf', 'zscale=t=linear:npl=100,format=yuv420p'); + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.ts new file mode 100644 index 000000000..fd925f3d3 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandNormalizeAudio/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Normalize Audio', + description: 'Normalize Audio', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.ts new file mode 100644 index 000000000..041da601d --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveDataStreams/1.0.0/index.ts @@ -0,0 +1,53 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint-disable no-param-reassign */ +const details = ():IpluginDetails => ({ + name: 'Remove Data Streams', + description: 'Remove Data Streams ', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + if (stream.codec_type === 'data') { + stream.removed = true; + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.ts new file mode 100644 index 000000000..c5f4fa789 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveStreamByProperty/1.0.0/index.ts @@ -0,0 +1,125 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Remove Stream By Property', + description: 'Remove Stream By Property', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'propertyToCheck', + type: 'string', + defaultValue: 'codec_name', + inputUI: { + type: 'text', + }, + tooltip: + ` + Enter one stream property to check. + + \\nExample:\\n + codec_name + + \\nExample:\\n + tags.language + `, + }, + { + name: 'valuesToRemove', + type: 'string', + defaultValue: 'aac', + inputUI: { + type: 'text', + }, + tooltip: + ` + Enter values of the property above to remove. For example, if removing by codec_name, could enter ac3,aac: + + \\nExample:\\n + ac3,aac + `, + }, + { + name: 'condition', + type: 'string', + defaultValue: 'includes', + inputUI: { + type: 'dropdown', + options: [ + 'includes', + 'not_includes', + ], + }, + tooltip: ` + Specify whether to remove streams that include or do not include the values above. + `, + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const propertyToCheck = String(args.inputs.propertyToCheck).trim(); + const valuesToRemove = String(args.inputs.valuesToRemove).trim().split(','); + const condition = String(args.inputs.condition); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + let target = ''; + if (propertyToCheck.includes('.')) { + const parts = propertyToCheck.split('.'); + target = stream[parts[0]]?.[parts[1]]; + } else { + target = stream[propertyToCheck]; + } + + if (target) { + const prop = String(target).toLowerCase(); + for (let i = 0; i < valuesToRemove.length; i += 1) { + const val = valuesToRemove[i].toLowerCase(); + + const prefix = `Removing stream index ${stream.index} because ${propertyToCheck} of ${prop}`; + if (condition === 'includes' && prop.includes(val)) { + args.jobLog(`${prefix} includes ${val}\n`); + // eslint-disable-next-line no-param-reassign + stream.removed = true; + } else if (condition === 'not_includes' && !prop.includes(val)) { + args.jobLog(`${prefix} not_includes ${val}\n`); + // eslint-disable-next-line no-param-reassign + stream.removed = true; + } + } + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.ts new file mode 100644 index 000000000..f25661563 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRemoveSubtitles/1.0.0/index.ts @@ -0,0 +1,53 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint-disable no-param-reassign */ +const details = ():IpluginDetails => ({ + name: 'Remove Subtitles', + description: 'Remove subtitles from the file', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + if (stream.codec_type === 'subtitle') { + stream.removed = true; + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; + +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.ts new file mode 100644 index 000000000..02e0283bc --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandRorderStreams/1.0.0/index.ts @@ -0,0 +1,230 @@ +import { + IffmpegCommandStream, + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Reorder Streams', + description: 'Reorder Streams', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'processOrder', + type: 'string', + defaultValue: 'codecs,channels,languages,streamTypes', + inputUI: { + type: 'text', + }, + tooltip: + `Specify the process order. +For example, if 'languages' is first, the streams will be ordered based on that first. +So put the most important properties last. +The default order is suitable for most people. + + \\nExample:\\n + codecs,channels,languages,streamTypes + `, + }, + { + name: 'languages', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + `Specify the language tags order, separated by commas. Leave blank to disable. + \\nExample:\\n + eng,fre + `, + }, + { + name: 'channels', + type: 'string', + defaultValue: '7.1,5.1,2,1', + inputUI: { + type: 'text', + }, + tooltip: + `Specify the channels order, separated by commas. Leave blank to disable. + + \\nExample:\\n + 7.1,5.1,2,1`, + }, + { + name: 'codecs', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: + `Specify the codec order, separated by commas. Leave blank to disable. + + \\nExample:\\n + aac,ac3`, + }, + { + name: 'streamTypes', + type: 'string', + defaultValue: 'video,audio,subtitle', + inputUI: { + type: 'text', + }, + tooltip: + `Specify the streamTypes order, separated by commas. Leave blank to disable. + \\nExample:\\n + video,audio,subtitle + `, + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let streams: IffmpegCommandStream[] = JSON.parse(JSON.stringify(args.variables.ffmpegCommand.streams)); + + const originalStreams = JSON.stringify(streams); + + streams.forEach((stream, index) => { + // eslint-disable-next-line no-param-reassign + stream.typeIndex = index; + }); + + const sortStreams = (sortType: { + inputs: string, + getValue: (stream: Istreams) => string, + }) => { + const items = sortType.inputs.split(','); + items.reverse(); + for (let i = 0; i < items.length; i += 1) { + const matchedStreams = []; + for (let j = 0; j < streams.length; j += 1) { + if (String(sortType.getValue(streams[j])) === String(items[i])) { + if ( + streams[j].codec_long_name + && ( + streams[j].codec_long_name.includes('image') + || streams[j].codec_name.includes('png') + ) + ) { + // do nothing, ffmpeg bug, doesn't move image streams + } else { + matchedStreams.push(streams[j]); + streams.splice(j, 1); + j -= 1; + } + } + } + streams = matchedStreams.concat(streams); + } + }; + + const processOrder = String(args.inputs.processOrder); + + const { + languages, codecs, channels, streamTypes, + } = args.inputs; + + const sortTypes:{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any, + } = { + languages: { + getValue: (stream: Istreams) => { + if (stream?.tags?.language) { + return stream.tags.language; + } + + return ''; + }, + inputs: languages, + }, + codecs: { + getValue: (stream: Istreams) => { + try { + return stream.codec_name; + } catch (err) { + // err + } + return ''; + }, + inputs: codecs, + }, + channels: { + getValue: (stream: Istreams) => { + const chanMap:{ + [key: number]: string + } = { + 8: '7.1', + 6: '5.1', + 2: '2', + 1: '1', + }; + + if (stream?.channels && chanMap[stream.channels]) { + return chanMap[stream.channels]; + } + + return ''; + }, + inputs: channels, + }, + streamTypes: { + getValue: (stream:Istreams) => { + if (stream.codec_type) { + return stream.codec_type; + } + return ''; + }, + inputs: streamTypes, + }, + }; + + const processOrderArr = processOrder.split(','); + + for (let k = 0; k < processOrderArr.length; k += 1) { + if (sortTypes[processOrderArr[k]] && sortTypes[processOrderArr[k]].inputs) { + sortStreams(sortTypes[processOrderArr[k]]); + } + } + + if (JSON.stringify(streams) !== originalStreams) { + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.shouldProcess = true; + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.streams = streams; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.ts new file mode 100644 index 000000000..e5beb15c2 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetContainer/1.0.0/index.ts @@ -0,0 +1,124 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { getContainer } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint-disable no-param-reassign */ +const details = (): IpluginDetails => ({ + name: 'Set Container', + description: 'Set the container of the output file', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'container', + type: 'string', + defaultValue: 'mkv', + inputUI: { + type: 'dropdown', + options: [ + 'mkv', + 'mp4', + ], + }, + tooltip: 'Specify the container to use', + }, + { + name: 'forceConform', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: ` +Specify if you want to force conform the file to the new container, +This is useful if not all streams are supported by the new container. +For example mkv does not support data streams. + `, + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const newContainer = String(args.inputs.container); + const { forceConform } = args.inputs; + + if (getContainer(args.inputFileObj._id) !== newContainer) { + args.variables.ffmpegCommand.container = newContainer; + args.variables.ffmpegCommand.shouldProcess = true; + + if (forceConform === true) { + for (let i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + const stream = args.variables.ffmpegCommand.streams[i]; + + try { + const codecType = stream.codec_type.toLowerCase(); + const codecName = stream.codec_name.toLowerCase(); + if (newContainer === 'mkv') { + if ( + codecType === 'data' + || [ + 'mov_text', + 'eia_608', + 'timed_id3', + ].includes(codecName) + ) { + stream.removed = true; + } + } + + if (newContainer === 'mp4') { + if ( + [ + 'hdmv_pgs_subtitle', + 'eia_608', + 'timed_id3', + 'subrip', + ].includes(codecName) + ) { + stream.removed = true; + } + } + } catch (err) { + // Error + } + } + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.ts new file mode 100644 index 000000000..fdd8be4e0 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoFramerate/1.0.0/index.ts @@ -0,0 +1,93 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Set Video Framerate', + description: 'Set Video Framerate. If the original framerate is lower than the' + + ' specified framerate, the original framerate will be used.', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'framerate', + type: 'number', + defaultValue: '30', + inputUI: { + type: 'text', + }, + tooltip: 'Specify framerate value', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const desiredFrameRate = Number(args.inputs.framerate); + + args.jobLog(`Desired framerate: ${desiredFrameRate}`); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + if (stream.codec_type === 'video') { + let fileFramerateUsed = false; + + if (stream.avg_frame_rate) { + const parts = stream.avg_frame_rate.split('/'); + + if (parts.length === 2) { + const numerator = parseInt(parts[0], 10); + const denominator = parseInt(parts[1], 10); + + if (numerator > 0 && denominator > 0) { + const fileFramerate = numerator / denominator; + + args.jobLog(`File framerate: ${fileFramerate}`); + + if (fileFramerate < desiredFrameRate) { + args.jobLog('File framerate is lower than desired framerate. Using file framerate.'); + stream.outputArgs.push('-r', `${String(fileFramerate)}`); + fileFramerateUsed = true; + } else { + args.jobLog('File framerate is greater than desired framerate. Using desired framerate.'); + } + } + } + } + + if (!fileFramerateUsed) { + args.jobLog('Using desired framerate.'); + stream.outputArgs.push('-r', `${String(desiredFrameRate)}`); + } + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.ts new file mode 100644 index 000000000..a73302849 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVdeoResolution/1.0.0/index.ts @@ -0,0 +1,105 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Set Video Resolution', + description: 'Change video resolution', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'targetResolution', + type: 'string', + defaultValue: '1080p', + inputUI: { + type: 'dropdown', + options: [ + '480p', + '720p', + '1080p', + '1440p', + '4KUHD', + ], + }, + tooltip: 'Specify the codec to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +const getVfScale = ( + targetResolution: string, +):string[] => { + switch (targetResolution) { + case '480p': + return ['-vf', 'scale=720:-2']; + + case '576p': + return ['-vf', 'scale=720:-2']; + + case '720p': + return ['-vf', 'scale=1280:-2']; + + case '1080p': + return ['-vf', 'scale=1920:-2']; + + case '1440p': + return ['-vf', 'scale=2560:-2']; + + case '4KUHD': + return ['-vf', 'scale=3840:-2']; + + default: + return ['-vf', 'scale=1920:-2']; + } +}; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + for (let i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + const stream = args.variables.ffmpegCommand.streams[i]; + + if (stream.codec_type === 'video') { + const targetResolution = String(args.inputs.targetResolution); + + if ( + targetResolution !== args.inputFileObj.video_resolution + ) { + // eslint-disable-next-line no-param-reassign + args.variables.ffmpegCommand.shouldProcess = true; + const scaleArgs = getVfScale(targetResolution); + stream.outputArgs.push(...scaleArgs); + } + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.ts new file mode 100644 index 000000000..4e21bedec --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoBitrate/1.0.0/index.ts @@ -0,0 +1,62 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { getFfType } from '../../../../FlowHelpers/1.0.0/fileUtils'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = () :IpluginDetails => ({ + name: 'Set Video Bitrate', + description: 'Set Video Bitrate', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'bitrate', + type: 'string', + defaultValue: '5000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify bitrate in kbps', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + args.variables.ffmpegCommand.streams.forEach((stream) => { + if (stream.codec_type === 'video') { + const ffType = getFfType(stream.codec_type); + stream.outputArgs.push(`-b:${ffType}:{outputTypeIndex}`, `${String(args.inputs.bitrate)}k`); + } + }); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.ts new file mode 100644 index 000000000..094f233e1 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSetVideoEncoder/1.0.0/index.ts @@ -0,0 +1,199 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { getEncoder } from '../../../../FlowHelpers/1.0.0/hardwareUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint-disable no-param-reassign */ +const details = (): IpluginDetails => ({ + name: 'Set Video Encoder', + description: 'Set the video encoder for all streams', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'outputCodec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + // 'vp9', + 'h264', + // 'vp8', + 'av1', + ], + }, + tooltip: 'Specify codec of the output file', + }, + { + name: 'ffmpegPreset', + type: 'string', + defaultValue: 'fast', + inputUI: { + type: 'dropdown', + options: [ + 'veryslow', + 'slower', + 'slow', + 'medium', + 'fast', + 'faster', + 'veryfast', + 'superfast', + 'ultrafast', + ], + }, + tooltip: 'Specify ffmpeg preset', + }, + { + name: 'ffmpegQuality', + type: 'number', + defaultValue: '25', + inputUI: { + type: 'text', + }, + tooltip: 'Specify ffmpeg quality', + }, + { + name: 'hardwareEncoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to use hardware encoding if available', + }, + { + name: 'hardwareType', + type: 'string', + defaultValue: 'auto', + inputUI: { + type: 'dropdown', + options: [ + 'auto', + 'nvenc', + 'qsv', + 'vaapi', + 'videotoolbox', + ], + }, + tooltip: 'Specify codec of the output file', + }, + { + name: 'hardwareDecoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to use hardware decoding if available', + }, + { + name: 'forceEncoding', + type: 'boolean', + defaultValue: 'true', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to force encoding if stream already has the target codec', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const hardwareDecoding = args.inputs.hardwareDecoding === true; + const hardwareType = String(args.inputs.hardwareType); + args.variables.ffmpegCommand.hardwareDecoding = hardwareDecoding; + + for (let i = 0; i < args.variables.ffmpegCommand.streams.length; i += 1) { + const stream = args.variables.ffmpegCommand.streams[i]; + + if (stream.codec_type === 'video') { + const targetCodec = String(args.inputs.outputCodec); + const ffmpegPreset = String(args.inputs.ffmpegPreset); + const ffmpegQuality = String(args.inputs.ffmpegQuality); + const forceEncoding = args.inputs.forceEncoding === true; + const hardwarEncoding = args.inputs.hardwareEncoding === true; + + if ( + forceEncoding + || stream.codec_name !== targetCodec + ) { + args.variables.ffmpegCommand.shouldProcess = true; + + // eslint-disable-next-line no-await-in-loop + const encoderProperties = await getEncoder({ + targetCodec, + hardwareEncoding: hardwarEncoding, + hardwareType, + args, + }); + + stream.outputArgs.push('-c:{outputIndex}', encoderProperties.encoder); + + if (encoderProperties.isGpu) { + stream.outputArgs.push('-qp', ffmpegQuality); + } else { + stream.outputArgs.push('-crf', ffmpegQuality); + } + + if (targetCodec !== 'av1' && ffmpegPreset) { + stream.outputArgs.push('-preset', ffmpegPreset); + } + + if (hardwareDecoding) { + stream.inputArgs.push(...encoderProperties.inputArgs); + } + + if (encoderProperties.outputArgs) { + stream.outputArgs.push(...encoderProperties.outputArgs); + } + } + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.ts new file mode 100644 index 000000000..a2824072a --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStart/1.0.0/index.ts @@ -0,0 +1,72 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { getContainer } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject'; + +/* eslint-disable no-param-reassign */ +const details = () :IpluginDetails => ({ + name: 'Begin Command', + description: 'Begin creating the FFmpeg command for the current working file.' + + ' Should be used before any other FFmpeg command plugins.', + style: { + borderColor: 'green', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: 1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const container = getContainer(args.inputFileObj._id); + + const ffmpegCommand = { + inputFiles: [], + streams: JSON.parse(JSON.stringify(args.inputFileObj.ffProbeData.streams)).map((stream:Istreams) => ({ + ...stream, + removed: false, + mapArgs: [ + '-map', + `0:${stream.index}`, + ], + inputArgs: [], + outputArgs: [], + })), + container, + hardwareDecoding: false, + shouldProcess: false, + overallInputArguments: [], + overallOuputArguments: [], + }; + + args.variables.ffmpegCommand = ffmpegCommand; + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.ts new file mode 100644 index 000000000..2b5558155 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExists/1.0.0/index.ts @@ -0,0 +1,89 @@ +import fs from 'fs'; +import { getContainer, getFileAbosluteDir, getFileName } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check File Exists', + description: 'Check file Exists', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'fileToCheck', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '${fileName}_720p.${container}', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify file to check using templating e.g. ${fileName}_720p.${container}', + }, + { + name: 'directory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify directory to check. Leave blank to use working directory.' + + ' Put below Input File plugin to check original file directory.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File exists', + }, + { + number: 2, + tooltip: 'File does not exist', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const directory = String(args.inputs.directory).trim() || getFileAbosluteDir(args.inputFileObj._id); + + const fileName = getFileName(args.inputFileObj._id); + + let fileToCheck = String(args.inputs.fileToCheck).trim(); + fileToCheck = fileToCheck.replace(/\${fileName}/g, fileName); + fileToCheck = fileToCheck.replace(/\${container}/g, getContainer(args.inputFileObj._id)); + fileToCheck = `${directory}/${fileToCheck}`; + + let fileExists = false; + if (fs.existsSync(fileToCheck)) { + fileExists = true; + args.jobLog(`File exists: ${fileToCheck}`); + } else { + args.jobLog(`File does not exist: ${fileToCheck}`); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: fileExists ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.ts new file mode 100644 index 000000000..1149b1cf1 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileExtension/1.0.0/index.ts @@ -0,0 +1,70 @@ +import { getContainer } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check File Extension', + description: 'Check file extension', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'extensions', + type: 'string', + defaultValue: 'mkv,mp4', + inputUI: { + type: 'text', + }, + tooltip: 'A comma separated list of extensions to check', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File is one of extensions', + }, + { + number: 2, + tooltip: 'File is not one of extensions', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const extensions = String(args.inputs.extensions); + const extensionArray = extensions.trim().split(','); + + const extension = getContainer(args.inputFileObj._id); + + let extensionMatch = false; + + if (extensionArray.includes(extension)) { + extensionMatch = true; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: extensionMatch ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.ts new file mode 100644 index 000000000..3fc1ebf62 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileMedium/1.0.0/index.ts @@ -0,0 +1,69 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check File Medium', + description: 'Check if file is video, audio or other type of file', + style: { + borderColor: 'orange', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File medium is a Video', + + }, + { + number: 2, + tooltip: 'File medium is an Audio', + }, + { + number: 3, + tooltip: 'File medium is Other', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let outputNumber = 1; + switch (args.inputFileObj.fileMedium) { + case 'video': + outputNumber = 1; + break; + case 'audio': + outputNumber = 2; + break; + case 'other': + outputNumber = 3; + break; + default: + throw new Error('File has no fileMedium!'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.ts new file mode 100644 index 000000000..21680e8c8 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileNameIncludes/1.0.0/index.ts @@ -0,0 +1,72 @@ +import { getContainer, getFileName } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check File Name Includes', + description: 'Check if a file name includes specific terms. Only needs to match one term', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'terms', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '_720p,_1080p', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify terms to check for in file name using comma seperated list e.g. _720p,_1080p', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File name contains terms', + }, + { + number: 2, + tooltip: 'File name does not contains terms', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const fileName = `${getFileName(args.inputFileObj._id)}.${getContainer(args.inputFileObj._id)}`; + const terms = String(args.inputs.terms).trim().split(','); + let containsTerms = false; + + for (let i = 0; i < terms.length; i++) { + if (fileName.includes(terms[i])) { + containsTerms = true; + break; + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: containsTerms ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.ts new file mode 100644 index 000000000..b43505362 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/checkFileSize/1.0.0/index.ts @@ -0,0 +1,102 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check File Size', + description: 'Check size of working file', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'GB', + inputUI: { + type: 'dropdown', + options: [ + 'B', + 'KB', + 'MB', + 'GB', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isWithinRange = false; + let greaterThanBytes = Number(args.inputs.greaterThan); + let lessThanBytes = Number(args.inputs.lessThan); + const fileSizeBytes = args.inputFileObj.file_size * 1000 * 1000; + + if (args.inputs.unit === 'KB') { + greaterThanBytes *= 1000; + lessThanBytes *= 1000; + } else if (args.inputs.unit === 'MB') { + greaterThanBytes *= 1000000; + lessThanBytes *= 1000000; + } else if (args.inputs.unit === 'GB') { + greaterThanBytes *= 1000000000; + lessThanBytes *= 1000000000; + } + + if (fileSizeBytes >= greaterThanBytes && fileSizeBytes <= lessThanBytes) { + isWithinRange = true; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.ts new file mode 100644 index 000000000..d9a92dfa3 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileDurationRatio/1.0.0/index.ts @@ -0,0 +1,121 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { IFileObject } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Compare File Duration Ratio', + description: 'Compare file duration ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '99.5', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound.' + + 'Default value is 99.5% so new file duration must be at least 40% of original file duration.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '100.5', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 100.5% so new file duration must be at most 100.5% of original file duration.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file duration % is within range', + }, + { + number: 2, + tooltip: 'Working file duration % is smaller than lower bound', + }, + { + number: 3, + tooltip: 'Working file duration % is larger than upper bound', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const getData = (obj:IFileObject) => { + try { + if (obj?.ffProbeData?.format?.duration) { + const dur = Number(obj.ffProbeData.format.duration); + + if (dur > 0) { + return dur; + } + } + } catch (err) { + // err + } + return 0; + }; + + const newFileDuration = getData(args.inputFileObj); + const origFileDuration = getData(args.originalLibraryFile); + + args.jobLog(`newFileDuration: ${newFileDuration}`); + args.jobLog(`origFileDuration: ${origFileDuration}`); + + const greaterThanPerc = Number(args.inputs.greaterThan); + const lessThanPerc = Number(args.inputs.lessThan); + + const ratio = (newFileDuration / origFileDuration) * 100; + + const durationText = `New file has duration ${newFileDuration.toFixed(3)} which is ${ratio}% ` + + `of original file duration: ${origFileDuration.toFixed(3)}`; + + const getBound = (bound:number) => (bound / 100) * origFileDuration; + + let outputNumber = 1; + + const errText = 'New file duration not within limits.'; + if (newFileDuration > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog(`${errText} ${durationText}. upperBound is ${lessThanPerc}%`); + outputNumber = 3; + } else if (newFileDuration < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog(`${errText} ${durationText}. lowerBound is ${greaterThanPerc}%`); + outputNumber = 2; + } else { + args.jobLog(durationText); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.ts new file mode 100644 index 000000000..c1a041e14 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSize/1.0.0/index.ts @@ -0,0 +1,64 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Compare File Size', + description: 'Compare file size of working file compared to original file', + style: { + borderColor: 'orange', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Working file is smaller than original file', + }, + { + number: 2, + tooltip: 'Working file is same size as original file', + }, + + { + number: 3, + tooltip: 'Working file is larger than original file', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let outputNumber = 1; + + if (args.inputFileObj.file_size < args.originalLibraryFile.file_size) { + outputNumber = 1; + } else if (args.inputFileObj.file_size === args.originalLibraryFile.file_size) { + outputNumber = 2; + } else if (args.inputFileObj.file_size > args.originalLibraryFile.file_size) { + outputNumber = 3; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.ts new file mode 100644 index 000000000..dfcd7148f --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/1.0.0/index.ts @@ -0,0 +1,96 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Compare File Size Ratio', + description: 'Compare file size ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '40', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound. ' + + 'Default value is 40% so new file size must be at least 40% of original file size.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '110', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 110% so new file size must be at most 110% of original file size.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file size % is within range', + }, + { + number: 2, + tooltip: 'Working file size % is not within range', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isWithinRange = false; + const newFileSizeBytes = args.inputFileObj.file_size; + const origFileSizeBytes = args.originalLibraryFile.file_size; + + const greaterThanPerc = Number(args.inputs.greaterThan); + const lessThanPerc = Number(args.inputs.lessThan); + + const ratio = (newFileSizeBytes / origFileSizeBytes) * 100; + + const sizeText = `New file has size ${newFileSizeBytes.toFixed(3)} MB which is ${ratio}% ` + + `of original file size: ${origFileSizeBytes.toFixed(3)} MB`; + + const getBound = (bound:number) => (bound / 100) * origFileSizeBytes; + + const errText = 'New file size not within limits.'; + if (newFileSizeBytes > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog(`${errText} ${sizeText}. upperBound is ${lessThanPerc}%`); + } else if (newFileSizeBytes < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog(`${errText} ${sizeText}. lowerBound is ${greaterThanPerc}%`); + } else { + args.jobLog(sizeText); + isWithinRange = true; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.ts new file mode 100644 index 000000000..c3dd7f057 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/compareFileSizeRatio/2.0.0/index.ts @@ -0,0 +1,102 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Compare File Size Ratio', + description: 'Compare file size ratio of working file compared to original file using percentage.', + style: { + borderColor: 'orange', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '40', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound.' + + 'Default value is 40% so new file size must be at least 40% of original file size.', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '110', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound.' + + ' Default value is 110% so new file size must be at most 110% of original file size.', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Working file size % is within range', + }, + { + number: 2, + tooltip: 'Working file size % is smaller than lower bound', + }, + { + number: 3, + tooltip: 'Working file size % is larger than upper bound', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const newFileSizeBytes = args.inputFileObj.file_size; + const origFileSizeBytes = args.originalLibraryFile.file_size; + + const greaterThanPerc = Number(args.inputs.greaterThan); + const lessThanPerc = Number(args.inputs.lessThan); + + const ratio = (newFileSizeBytes / origFileSizeBytes) * 100; + + const sizeText = `New file has size ${newFileSizeBytes.toFixed(3)} MB which is ${ratio}% ` + + `of original file size: ${origFileSizeBytes.toFixed(3)} MB`; + + const getBound = (bound:number) => (bound / 100) * origFileSizeBytes; + + let outputNumber = 1; + + const errText = 'New file size not within limits.'; + if (newFileSizeBytes > getBound(lessThanPerc)) { + // Item will be errored in UI + args.jobLog(`${errText} ${sizeText}. upperBound is ${lessThanPerc}%`); + outputNumber = 3; + } else if (newFileSizeBytes < getBound(greaterThanPerc)) { + // // Item will be errored in UI + args.jobLog(`${errText} ${sizeText}. lowerBound is ${greaterThanPerc}%`); + outputNumber = 2; + } else { + args.jobLog(sizeText); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.ts new file mode 100644 index 000000000..92aec6551 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/copyToDirectory/1.0.0/index.ts @@ -0,0 +1,147 @@ +import { promises as fs } from 'fs'; +import { getContainer, getFileName, getSubStem } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import normJoinPath from '../../../../FlowHelpers/1.0.0/normJoinPath'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Copy to Directory', + description: 'Copy the working file to a directory', + style: { + borderColor: 'green', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'outputDirectory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify ouput directory', + }, + { + name: 'keepRelativePath', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to keep the relative path', + }, + { + name: 'makeWorkingFile', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Make the copied file the working file', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { + keepRelativePath, makeWorkingFile, + } = args.inputs; + + const outputDirectory = String(args.inputs.outputDirectory); + + const originalFileName = getFileName(args.originalLibraryFile._id); + const newContainer = getContainer(args.inputFileObj._id); + + let outputPath = ''; + + if (keepRelativePath) { + const subStem = getSubStem({ + inputPathStem: args.librarySettings.folder, + inputPath: args.originalLibraryFile._id, + }); + + outputPath = normJoinPath({ + upath: args.deps.upath, + paths: [ + outputDirectory, + subStem, + ], + }); + } else { + outputPath = outputDirectory; + } + + const ouputFilePath = normJoinPath({ + upath: args.deps.upath, + paths: [ + outputPath, + `${originalFileName}.${newContainer}`, + ], + }); + + let workingFile = args.inputFileObj._id; + + if (makeWorkingFile) { + workingFile = ouputFilePath; + } + + args.jobLog(`Input path: ${args.inputFileObj._id}`); + args.jobLog(`Output path: ${outputPath}`); + + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping copy.'); + + return { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }; + } + + args.deps.fsextra.ensureDirSync(outputPath); + + await fs.copyFile(args.inputFileObj._id, ouputFilePath); + + return { + outputFileObj: { + _id: workingFile, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.ts new file mode 100644 index 000000000..04458d1f8 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/copyToWorkDirectory/1.0.0/index.ts @@ -0,0 +1,83 @@ +import { promises as fs } from 'fs'; +import { getContainer, getFileName } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import normJoinPath from '../../../../FlowHelpers/1.0.0/normJoinPath'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Copy to Working Directory', + description: 'Copy the working file to the working directory of the Tdarr worker. ' + + 'Useful if you want to copy the file to the library cache before transcoding begins', + style: { + borderColor: 'green', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const originalFileName = getFileName(args.originalLibraryFile._id); + const newContainer = getContainer(args.inputFileObj._id); + + const outputPath = args.workDir; + + const ouputFilePath = normJoinPath({ + upath: args.deps.upath, + paths: [ + outputPath, + `${originalFileName}.${newContainer}`, + ], + }); + + args.jobLog(`Input path: ${args.inputFileObj._id}`); + args.jobLog(`Output path: ${outputPath}`); + + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping copy.'); + + return { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }; + } + + args.deps.fsextra.ensureDirSync(outputPath); + + await fs.copyFile(args.inputFileObj._id, ouputFilePath); + + return { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.ts new file mode 100644 index 000000000..1184e920c --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Move To Directory', + description: 'Move working file to directory.', + style: { + borderColor: 'green', + opacity: 0.5, + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.ts new file mode 100644 index 000000000..cc3465888 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/moveToDirectory/2.0.0/index.ts @@ -0,0 +1,134 @@ +import { + getContainer, getFileName, getSubStem, moveFileAndValidate, +} from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import normJoinPath from '../../../../FlowHelpers/1.0.0/normJoinPath'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Move To Directory', + description: 'Move working file to directory.', + style: { + borderColor: 'green', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'outputDirectory', + type: 'string', + defaultValue: '', + inputUI: { + type: 'directory', + }, + tooltip: 'Specify ouput directory', + }, + { + name: 'keepRelativePath', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Specify whether to keep the relative path', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args:IpluginInputArgs):Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { + keepRelativePath, + } = args.inputs; + + const outputDirectory = String(args.inputs.outputDirectory); + + const originalFileName = getFileName(args.originalLibraryFile._id); + const newContainer = getContainer(args.inputFileObj._id); + + let outputPath = ''; + + if (keepRelativePath) { + const subStem = getSubStem({ + inputPathStem: args.librarySettings.folder, + inputPath: args.originalLibraryFile._id, + }); + + outputPath = normJoinPath({ + upath: args.deps.upath, + paths: [ + outputDirectory, + subStem, + ], + }); + } else { + outputPath = outputDirectory; + } + + const ouputFilePath = normJoinPath({ + upath: args.deps.upath, + paths: [ + outputPath, + `${originalFileName}.${newContainer}`, + ], + }); + + args.jobLog(`Input path: ${args.inputFileObj._id}`); + args.jobLog(`Output path: ${ouputFilePath}`); + + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping move.'); + + return { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }; + } + + args.deps.fsextra.ensureDirSync(outputPath); + + await moveFileAndValidate({ + inputPath: args.inputFileObj._id, + outputPath: ouputFilePath, + args, + + }); + + return { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.ts new file mode 100644 index 000000000..693027a9a --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/moveToOriginalDirectory/1.0.0/index.ts @@ -0,0 +1,74 @@ +import { + getContainer, getFileAbosluteDir, getFileName, moveFileAndValidate, +} from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Move To Original Directory', + description: 'Move working file original directory.', + style: { + borderColor: 'green', + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args:IpluginInputArgs):Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const fileName = getFileName(args.inputFileObj._id); + const container = getContainer(args.inputFileObj._id); + const outputDir = getFileAbosluteDir(args.originalLibraryFile._id); + + const ouputFilePath = `${outputDir}/${fileName}.${container}`; + + if (args.inputFileObj._id === ouputFilePath) { + args.jobLog('Input and output path are the same, skipping move.'); + + return { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }; + } + + await moveFileAndValidate({ + inputPath: args.inputFileObj._id, + outputPath: ouputFilePath, + args, + }); + + return { + outputFileObj: { + _id: ouputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/renameFile/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/renameFile/1.0.0/index.ts new file mode 100644 index 000000000..81b21a3b7 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/renameFile/1.0.0/index.ts @@ -0,0 +1,88 @@ +import { + getContainer, getFileAbosluteDir, getFileName, moveFileAndValidate, +} from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Rename File', + description: 'Rename a file', + style: { + borderColor: 'green', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'fileRename', + type: 'string', + // eslint-disable-next-line no-template-curly-in-string + defaultValue: '${fileName}_720p.${container}', + inputUI: { + type: 'text', + }, + // eslint-disable-next-line no-template-curly-in-string + tooltip: 'Specify file to check using templating e.g. ${fileName}_720p.${container}', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const fileName = getFileName(args.inputFileObj._id); + + let newName = String(args.inputs.fileRename).trim(); + newName = newName.replace(/\${fileName}/g, fileName); + newName = newName.replace(/\${container}/g, getContainer(args.inputFileObj._id)); + + const fileDir = getFileAbosluteDir(args.inputFileObj._id); + const newPath = `${fileDir}/${newName}`; + + if (args.inputFileObj._id === newPath) { + args.jobLog('Input and output path are the same, skipping rename.'); + + return { + outputFileObj: { + _id: args.inputFileObj._id, + }, + outputNumber: 1, + variables: args.variables, + }; + } + + await moveFileAndValidate({ + inputPath: args.inputFileObj._id, + outputPath: newPath, + args, + }); + + return { + outputFileObj: { + _id: newPath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.ts new file mode 100644 index 000000000..8f4f68c2e --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/replaceOriginalFile/1.0.0/index.ts @@ -0,0 +1,103 @@ +import { + getContainer, getFileAbosluteDir, getFileName, moveFileAndValidate, +} from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Replace Original File', + description: 'Replace the original file. If the file hasn\'t changed then no action is taken.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const fs = require('fs'); + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + if ( + args.inputFileObj._id === args.originalLibraryFile._id + && args.inputFileObj.file_size === args.originalLibraryFile.file_size + ) { + args.jobLog('File has not changed, no need to replace file'); + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; + } + + args.jobLog('File has changed, replacing original file'); + + const currentPath = args.inputFileObj._id; + const orignalFolder = getFileAbosluteDir(args.originalLibraryFile._id); + const fileName = getFileName(args.inputFileObj._id); + const container = getContainer(args.inputFileObj._id); + + const newPath = `${orignalFolder}/${fileName}.${container}`; + const newPathTmp = `${newPath}.tmp`; + + args.jobLog(JSON.stringify({ + currentPath, + newPath, + newPathTmp, + })); + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + await moveFileAndValidate({ + inputPath: currentPath, + outputPath: newPathTmp, + args, + }); + + // delete original file + if ( + fs.existsSync(args.originalLibraryFile._id) + && args.originalLibraryFile._id !== currentPath + ) { + args.jobLog(`Deleting original file:${args.originalLibraryFile._id}`); + fs.unlinkSync(args.originalLibraryFile._id); + } + + await new Promise((resolve) => setTimeout(resolve, 2000)); + + await moveFileAndValidate({ + inputPath: newPathTmp, + outputPath: newPath, + args, + }); + + return { + outputFileObj: { + _id: newPath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.ts new file mode 100644 index 000000000..3c0d4ff2b --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/setOriginalFile/1.0.0/index.ts @@ -0,0 +1,46 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Set Original File', + description: 'Set the working file to the original file', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: { + _id: args.originalLibraryFile._id, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/file/unpack/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/file/unpack/1.0.0/index.ts new file mode 100644 index 000000000..bac4c3881 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/file/unpack/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Unpack File', + description: 'Unpack a file', + style: { + borderColor: 'green', + opacity: 0.5, + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.ts new file mode 100644 index 000000000..7ed495b5f --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/handbrake/handbrakeCustomArguments/1.0.0/index.ts @@ -0,0 +1,144 @@ +import { promises as fs } from 'fs'; +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { getContainer, getFileName, getPluginWorkDir } from '../../../../FlowHelpers/1.0.0/fileUtils'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'HandBrake Custom Arguments', + description: 'HandBrake Custom Arguments', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'customArguments', + type: 'string', + defaultValue: '-Z "Fast 1080p30" --all-subtitles', + inputUI: { + type: 'text', + }, + tooltip: 'Specify HandBrake arguments', + }, + { + name: 'jsonPreset', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Paste a HandBrake JSON preset here. Leave blank to disable.', + }, + { + name: 'container', + type: 'string', + defaultValue: 'mkv', + inputUI: { + type: 'dropdown', + options: [ + 'original', + 'mkv', + 'mp4', + 'm4v', + 'avi', + 'mov', + 'mpg', + 'mpeg', + ], + }, + tooltip: 'Specify HandBrake arguments', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args:IpluginInputArgs):Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const customArguments = String(args.inputs.customArguments); + + let container = String(args.inputs.container); + + if (container === 'original') { + container = getContainer(args.inputFileObj._id); + } + + const outputFilePath = `${getPluginWorkDir(args)}/${getFileName(args.inputFileObj._id)}.${container}`; + + const presetString = String(args.inputs.jsonPreset); + + const cliArgs = [ + '-i', + `${args.inputFileObj._id}`, + '-o', + `${outputFilePath}`, + ]; + + const presetPath = `${args.workDir}/preset.json`; + + if (presetString.trim() !== '') { + const preset = JSON.parse(presetString); + await fs.writeFile(presetPath, JSON.stringify(preset, null, 2)); + cliArgs.push('--preset-import-file'); + cliArgs.push(presetPath); + cliArgs.push('-Z'); + cliArgs.push(preset.PresetList[0].PresetName); + } else { + cliArgs.push(...args.deps.parseArgsStringToArgv(customArguments, '', '')); + } + + args.updateWorker({ + CLIType: args.handbrakePath, + preset: cliArgs.join(' '), + }); + + const cli = new CLI({ + cli: args.handbrakePath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog('Running HandBrake failed'); + throw new Error('Running HandBrake failed'); + } + + args.logOutcome('tSuc'); + + return { + outputFileObj: { + _id: outputFilePath, + }, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/input/inputFile/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/input/inputFile/1.0.0/index.ts new file mode 100644 index 000000000..187bbe002 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/input/inputFile/1.0.0/index.ts @@ -0,0 +1,133 @@ +import { promises as fs } from 'fs'; + +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; +import { getFileAbosluteDir } from '../../../../FlowHelpers/1.0.0/fileUtils'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Input File', + description: 'Start the flow with an input file', + style: { + borderColor: 'pink', + }, + tags: '', + isStartPlugin: true, + pType: 'start', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'fileAccessChecks', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'Will check if input file and cache are readable and writable', + }, + { + name: 'pauseNodeIfAccessChecksFail', + type: 'boolean', + defaultValue: 'false', + inputUI: { + type: 'dropdown', + options: [ + 'false', + 'true', + ], + }, + tooltip: 'This will pause the node if the file access checks fail', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const orignalFolder = getFileAbosluteDir(args.originalLibraryFile._id); + const { fileAccessChecks, pauseNodeIfAccessChecksFail } = args.inputs; + + const nodeID = process.argv[8]; + const { serverIP, serverPort } = args.deps.configVars.config; + + const url = `http://${serverIP}:${serverPort}/api/v2/update-node`; + + const pauseNode = async () => { + args.jobLog('Pausing node'); + const requestConfig = { + method: 'post', + url, + headers: {}, + data: { + data: { + nodeID, + nodeUpdates: { + nodePaused: true, + }, + }, + }, + }; + + await args.deps.axios(requestConfig); + args.jobLog('Node paused'); + }; + + const checkReadWrite = async (location: string) => { + try { + await fs.access(location, fs.constants.R_OK); + } catch (err) { + args.jobLog(JSON.stringify(err)); + if (pauseNodeIfAccessChecksFail) { + await pauseNode(); + } + + throw new Error(`Location not readable:${location}`); + } + + try { + await fs.access(location, fs.constants.W_OK); + } catch (err) { + args.jobLog(JSON.stringify(err)); + if (pauseNodeIfAccessChecksFail) { + await pauseNode(); + } + throw new Error(`Location not writeable:${location}`); + } + }; + + if (fileAccessChecks) { + args.jobLog('Checking file access'); + await checkReadWrite(orignalFolder); + await checkReadWrite(args.librarySettings.cache); + } else { + args.jobLog('Skipping file access checks'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.ts new file mode 100644 index 000000000..11a8b7593 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/checkFlowVariable/1.0.0/index.ts @@ -0,0 +1,136 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Flow Variable', + description: 'Check Flow Variable', + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'variable', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Variable to check. For example args.librarySettings._id', + }, + { + name: 'condition', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [ + '==', + '!=', + ], + }, + tooltip: 'Check condition', + }, + + { + name: 'value', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Value of variable to check', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'The variable matches the condition', + }, + { + number: 2, + tooltip: 'The variable does not match the condition', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const variable = String(args.inputs.variable); + const condition = String(args.inputs.condition); + const value = String(args.inputs.value); + + // variable could be e.g. args.librarySettings._id or args.inputFileObj._id + // condition could be e.g. '==' or '!=' + + const variableParts = variable.split('.'); + + let targetValue; + switch (variableParts.length) { + case 1: + targetValue = args; + break; + case 2: + // @ts-expect-error index + targetValue = args[variableParts[1]]; + break; + case 3: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]]; + break; + case 4: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]][variableParts[3]]; + break; + case 5: + // @ts-expect-error index + targetValue = args[variableParts[1]][variableParts[2]][variableParts[3]][variableParts[4]]; + break; + default: + throw new Error(`Invalid variable: ${variable}`); + } + + targetValue = String(targetValue); + let outputNumber = 1; + + if (condition === '==') { + if (targetValue === value) { + args.jobLog(`Variable ${variable} of value ${targetValue} matches condition ${condition} ${value}`); + outputNumber = 1; + } else { + args.jobLog(`Variable ${variable} of value ${targetValue} does not match condition ${condition} ${value}`); + outputNumber = 2; + } + } else if (condition === '!=') { + if (targetValue !== value) { + args.jobLog(`Variable ${variable} of value ${targetValue} matches condition ${condition} ${value}`); + outputNumber = 1; + } else { + args.jobLog(`Variable ${variable} of value ${targetValue} does not match condition ${condition} ${value}`); + outputNumber = 2; + } + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.ts new file mode 100644 index 000000000..e56206498 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/checkNodeHardwareEncoder/1.0.0/index.ts @@ -0,0 +1,89 @@ +import { getEncoder } from '../../../../FlowHelpers/1.0.0/hardwareUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Node Hardware Encoder', + description: ` + Check if node hardware encoder is available. Can also be used to check for specific hardware. + For example: + + hevc_nvenc = Nvidia + hevc_amf = AMD + hevc_vaapi = Intel + hevc_qsv = Intel + hevc_videotoolbox = Apple + `, + style: { + borderColor: 'orange', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'hardwareEncoder', + type: 'string', + defaultValue: 'hevc_nvenc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc_nvenc', + 'hevc_amf', + 'hevc_vaapi', + 'hevc_qsv', + 'hevc_videotoolbox', + ], + }, + tooltip: 'Specify hardware (based on encoder) to check for', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Node has hardware', + }, + { + number: 2, + tooltip: 'Node does not have hardware', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { hardwareEncoder } = args.inputs; + + // eslint-disable-next-line no-await-in-loop + const encoderProperties = await getEncoder({ + targetCodec: 'hevc', + hardwareEncoding: true, + hardwareType: 'auto', + args, + }); + + const nodeHasHardware = encoderProperties.enabledDevices.some((row) => row.encoder === hardwareEncoder); + + args.jobLog(`Node has hardwareEncoder ${hardwareEncoder}: ${nodeHasHardware}`); + + return { + outputFileObj: args.inputFileObj, + outputNumber: nodeHasHardware ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/comment/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/comment/1.0.0/index.ts new file mode 100644 index 000000000..60d851276 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/comment/1.0.0/index.ts @@ -0,0 +1,53 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Comment', + nameUI: { + type: 'textarea', + style: { + height: '250px', + }, + }, + description: `Add a comment to your flow. Can place anywhere and link together. + Any file input into the comment will be passed straight through.`, + style: { + borderColor: 'white', + borderRadius: '10px', + backgroundColor: '#043775', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faComment', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/failFlow/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/failFlow/1.0.0/index.ts new file mode 100644 index 000000000..a39cb0ccf --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/failFlow/1.0.0/index.ts @@ -0,0 +1,41 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Fail Flow', + description: 'Force the flow to fail and be move to the error table', + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faExclamationTriangle', + inputs: [], + outputs: [], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + throw new Error('Forcing flow to fail!'); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.ts new file mode 100644 index 000000000..51eb7f6f7 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/1.0.0/index.ts @@ -0,0 +1,50 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Go To Flow', + description: 'Go to a different flow', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'flowId', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify flow ID to go to', + }, + ], + outputs: [], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.ts new file mode 100644 index 000000000..c04baecdd --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/goToFlow/2.0.0/index.ts @@ -0,0 +1,59 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Go To Flow', + description: 'Go to a different flow', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.14.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'flowId', + type: 'string', + defaultValue: '', + inputUI: { + type: 'dropdown', + options: [], + }, + tooltip: 'Specify flow ID to go to', + }, + { + name: 'pluginId', + type: 'string', + defaultValue: 'start', + inputUI: { + type: 'text', + }, + tooltip: 'Specify plugin ID to go to', + }, + ], + outputs: [], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.ts new file mode 100644 index 000000000..2dd235b26 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/notifyRadarrOrSonarr/1.0.0/index.ts @@ -0,0 +1,145 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = (): IpluginDetails => ({ + name: 'Notify Radarr or Sonarr', + description: 'Notify Radarr or Sonarr to refresh after file change', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faBell', + inputs: [ + { + name: 'arr', + type: 'string', + defaultValue: 'radarr', + inputUI: { + type: 'dropdown', + options: ['radarr', 'sonarr'], + }, + tooltip: 'Specify which arr to use', + }, + { + name: 'arr_api_key', + type: 'string', + defaultValue: '', + inputUI: { + type: 'text', + }, + tooltip: 'Input your arr api key here', + }, + { + name: 'arr_host', + type: 'string', + defaultValue: 'http://192.168.1.1:7878', + inputUI: { + type: 'text', + }, + tooltip: 'Input your arr host here.' + + '\\nExample:\\n' + + 'http://192.168.1.1:7878\\n' + + 'http://192.168.1.1:8989\\n' + + 'https://radarr.domain.com\\n' + + 'https://sonarr.domain.com\\n', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { arr, arr_api_key } = args.inputs; + const arr_host = String(args.inputs.arr_host).trim(); + + const { meta } = args.inputFileObj; + const arrHost = arr_host.endsWith('/') ? arr_host.slice(0, -1) : arr_host; + + const headers = { + 'Content-Type': 'application/json', + 'X-Api-Key': arr_api_key, + Accept: 'application/json', + }; + + args.jobLog('Going to force scan'); + + if (arr === 'radarr') { + args.jobLog('Refreshing Radarr...'); + + const requestConfig = { + method: 'get', + url: `${arrHost}/api/v3/parse?title=${encodeURIComponent(meta?.FileName || '')}`, + headers, + }; + + const res = await args.deps.axios(requestConfig); + const { movieId } = res.data.movie.movieFile; + + const requestConfig2 = { + method: 'post', + url: `${arrHost}/api/v3/command`, + headers, + data: JSON.stringify({ + name: 'RefreshMovie', + movieIds: [movieId], + }), + }; + + await args.deps.axios(requestConfig2); + + args.jobLog(`✔ Refreshed movie ${movieId} in Radarr.`); + } else if (arr === 'sonarr') { + args.jobLog('Refreshing Sonarr...'); + + const requestConfig = { + method: 'get', + url: `${arrHost}/api/v3/parse?title=${encodeURIComponent(meta?.FileName || '')}`, + headers, + }; + + const res = await args.deps.axios(requestConfig); + const seriesId = res.data.series.id; + + const requestConfig2 = { + method: 'post', + url: `${arrHost}/api/v3/command`, + headers, + data: JSON.stringify({ + name: 'RefreshSeries', + seriesId, + }), + }; + + await args.deps.axios(requestConfig2); + + args.jobLog(`✔ Refreshed series ${seriesId} in Sonarr.`); + } else { + args.jobLog('No arr specified in plugin inputs.'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; + +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.ts new file mode 100644 index 000000000..1546bc189 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/onFlowError/1.0.0/index.ts @@ -0,0 +1,45 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'On Flow Error', + description: `Runs if an error occurs in this specific flow. + Won't run if error occurs in after going to a different flow (unless that flow comes back to this one).`, + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: 'onFlowError', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/requireReview/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/requireReview/1.0.0/index.ts new file mode 100644 index 000000000..053e0d15b --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/requireReview/1.0.0/index.ts @@ -0,0 +1,46 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Require Review', + description: `Makes the flow pause. + The file will stay in the staging section on the Tdarr tab until the user clicks the "Reviewed" button. + `, + style: { + borderColor: 'yellow', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faHand', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.ts new file mode 100644 index 000000000..a37f4223f --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/resetFlowError/1.0.0/index.ts @@ -0,0 +1,48 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Reset Flow Error', + description: `After a flow error occurs, this plugin will reset the flow +error so that the flow will not go to error status at the end of the flow.`, + style: { + borderColor: 'red', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faUndo', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + // eslint-disable-next-line no-param-reassign + args.variables.flowFailed = false; + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.ts new file mode 100644 index 000000000..34167d972 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/runMkvPropEdit/1.0.0/index.ts @@ -0,0 +1,69 @@ +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Run MKVPropEdit', + description: 'Run MKVPropEdit on a file to update metadata which' + + ' FFmpeg doesn\'t typically update such as stream bitrate.', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args:IpluginInputArgs):Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const cliArgs = [ + '--add-track-statistics-tags', + args.inputFileObj._id, + ]; + + const cli = new CLI({ + cli: args.mkvpropeditPath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath: '', + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog('Running MKVPropEdit failed'); + throw new Error('Running MKVPropEdit failed'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.ts new file mode 100644 index 000000000..f61d15fd4 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/waitTimeout/1.0.0/index.ts @@ -0,0 +1,106 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Wait', + description: 'Wait for a specified amount of time before continuing to the next plugin', + style: { + borderColor: 'yellow', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faClock', + inputs: [ + { + name: 'amount', + type: 'string', + defaultValue: '1', + inputUI: { + type: 'text', + }, + tooltip: 'Specify the amount of time to wait', + }, + { + name: 'unit', + type: 'string', + defaultValue: 'seconds', + inputUI: { + type: 'dropdown', + options: [ + 'seconds', + 'minutes', + 'hours', + ], + }, + tooltip: 'Specify the unit of time to wait', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { + amount, + unit, + } = args.inputs; + + const amountNum = Number(amount); + if (Number.isNaN(amountNum)) { + throw new Error('Amount must be a number'); + } + let multiplier = 1; + if (unit === 'seconds') { + multiplier = 1000; + } else if (unit === 'minutes') { + multiplier = 60000; + } else if (unit === 'hours') { + multiplier = 3600000; + } + + const waitTime = amountNum * multiplier; + + args.jobLog(`Waiting for ${amount} ${unit}`); + args.jobLog(`Waiting for ${waitTime} milliseconds`); + + let finished = false; + + const logWait = () => { + if (!finished) { + args.jobLog('Waiting...'); + setTimeout(logWait, 5000); + } + }; + + logWait(); + + await new Promise((resolve) => setTimeout(resolve, waitTime)); + + finished = true; + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/tools/webRequest/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/tools/webRequest/1.0.0/index.ts new file mode 100644 index 000000000..76c19a26c --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/tools/webRequest/1.0.0/index.ts @@ -0,0 +1,118 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Send Web Request', + description: 'Send Web Request', + style: { + borderColor: 'green', + }, + tags: '', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faArrowRight', + inputs: [ + { + name: 'method', + type: 'string', + defaultValue: 'post', + inputUI: { + type: 'dropdown', + options: [ + 'get', + 'post', + 'put', + 'delete', + ], + }, + tooltip: 'Specify request method', + }, + { + name: 'requestUrl', + type: 'string', + defaultValue: 'http://example.com', + inputUI: { + type: 'text', + }, + tooltip: 'Specify request URL', + }, + { + name: 'requestHeaders', + type: 'string', + defaultValue: `{ + "Content-Type": "application/json" +}`, + inputUI: { + type: 'textarea', + style: { + height: '100px', + }, + }, + tooltip: 'Specify request URL', + }, + { + name: 'requestBody', + type: 'string', + defaultValue: `{ + "test": "test" +}`, + inputUI: { + type: 'textarea', + style: { + height: '100px', + }, + }, + tooltip: 'Specify request body', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args: IpluginInputArgs): Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const method = String(args.inputs.method); + const requestUrl = String(args.inputs.requestUrl); + const requestHeaders = JSON.parse(String(args.inputs.requestHeaders)); + const requestBody = JSON.parse(String(args.inputs.requestBody)); + + const requestConfig = { + method, + url: requestUrl, + headers: requestHeaders, + data: requestBody, + }; + + try { + const res = await args.deps.axios(requestConfig); + args.jobLog(`Web request succeeded: Status Code: ${res.status}`); + } catch (err) { + args.jobLog('Web Request Failed'); + args.jobLog(JSON.stringify(err)); + throw new Error('Web Request Failed'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.ts new file mode 100644 index 000000000..98a280f58 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/CheckVideoFramerate/1.0.0/index.ts @@ -0,0 +1,88 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Video Framerate', + description: 'Check if video framerate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound of fps', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '60', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound fps', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isWithinRange = false; + + const greaterThanFps = Number(args.inputs.greaterThan); + const lessThanFps = Number(args.inputs.lessThan); + + const VideoFrameRate = args.inputFileObj?.meta?.VideoFrameRate; + + if (VideoFrameRate) { + if (VideoFrameRate >= greaterThanFps && VideoFrameRate <= lessThanFps) { + isWithinRange = true; + } + } else { + throw new Error('Video framerate not found'); + } + + if (isWithinRange) { + args.jobLog(`Video framerate of ${VideoFrameRate} is within range of ${greaterThanFps} and ${lessThanFps}`); + } else { + args.jobLog(`Video framerate of ${VideoFrameRate} is not within range of ${greaterThanFps} and ${lessThanFps}`); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/check10Bit/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/check10Bit/1.0.0/index.ts new file mode 100644 index 000000000..04548c2c4 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/check10Bit/1.0.0/index.ts @@ -0,0 +1,67 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check 10 Bit Video', + description: 'Check if a file is 10 bit video', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is 10 bit video', + }, + { + number: 2, + tooltip: 'File is not 10 bit video', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let is10Bit = false; + + if (Array.isArray(args?.inputFileObj?.ffProbeData?.streams)) { + for (let i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + const stream = args.inputFileObj.ffProbeData.streams[i]; + if ( + stream.codec_type === 'video' + && ( + stream.bits_per_raw_sample === 10 + || stream.pix_fmt === 'yuv420p10le' + ) + ) { + is10Bit = true; + } + } + } else { + throw new Error('File has not stream data'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: is10Bit ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkHdr/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkHdr/1.0.0/index.ts new file mode 100644 index 000000000..f28f79a92 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkHdr/1.0.0/index.ts @@ -0,0 +1,66 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check HDR Video', + description: 'Check if video is HDR', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is HDR', + }, + { + number: 2, + tooltip: 'File is not HDR', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isHdr = false; + + if (Array.isArray(args?.inputFileObj?.ffProbeData?.streams)) { + for (let i = 0; i < args.inputFileObj.ffProbeData.streams.length; i += 1) { + const stream = args.inputFileObj.ffProbeData.streams[i]; + if ( + stream.codec_type === 'video' + && stream.color_transfer === 'smpte2084' + && stream.color_primaries === 'bt2020' + && stream.color_range === 'tv' + ) { + isHdr = true; + } + } + } else { + throw new Error('File has not stream data'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isHdr ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.ts new file mode 100644 index 000000000..d9bd7ee26 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkOverallBitrate/1.0.0/index.ts @@ -0,0 +1,104 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Overall Bitrate', + description: 'Check if overall file bitrate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'kbps', + inputUI: { + type: 'dropdown', + options: [ + 'bps', + 'kbps', + 'mbps', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isWithinRange = false; + + let greaterThanBits = Number(args.inputs.greaterThan); + let lessThanBits = Number(args.inputs.lessThan); + + if (args.inputs.unit === 'kbps') { + greaterThanBits *= 1000; + lessThanBits *= 1000; + } else if (args.inputs.unit === 'mbps') { + greaterThanBits *= 1000000; + lessThanBits *= 1000000; + } + + args.jobLog(`File bitrate is ${args.inputFileObj.bit_rate} bps`); + args.jobLog(`Checking if bitrate is within range ${greaterThanBits} bps and ${lessThanBits} bps`); + + if (args.inputFileObj.bit_rate >= greaterThanBits && args.inputFileObj.bit_rate <= lessThanBits) { + isWithinRange = true; + args.jobLog('File bitrate is within range'); + } else { + args.jobLog('File bitrate is not within range'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.ts new file mode 100644 index 000000000..4c6182d45 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoBitrate/1.0.0/index.ts @@ -0,0 +1,114 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Video Bitrate', + description: 'Check if video bitrate is within a specific range', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'unit', + type: 'string', + defaultValue: 'kbps', + inputUI: { + type: 'dropdown', + options: [ + 'bps', + 'kbps', + 'mbps', + ], + }, + tooltip: 'Specify the unit to use', + }, + { + name: 'greaterThan', + type: 'number', + defaultValue: '0', + inputUI: { + type: 'text', + }, + tooltip: 'Specify lower bound', + }, + { + name: 'lessThan', + type: 'number', + defaultValue: '10000', + inputUI: { + type: 'text', + }, + tooltip: 'Specify upper bound', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File within range', + }, + { + number: 2, + tooltip: 'File not within range', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let isWithinRange = false; + + let greaterThanBits = Number(args.inputs.greaterThan); + let lessThanBits = Number(args.inputs.lessThan); + + if (args.inputs.unit === 'kbps') { + greaterThanBits *= 1000; + lessThanBits *= 1000; + } else if (args.inputs.unit === 'mbps') { + greaterThanBits *= 1000000; + lessThanBits *= 1000000; + } + + let hasVideoBitrate = false; + + if (args.inputFileObj?.mediaInfo?.track) { + args.inputFileObj.mediaInfo.track.forEach((stream) => { + if (stream['@type'].toLowerCase() === 'video') { + if (stream.BitRate) { + hasVideoBitrate = true; + args.jobLog(`Found video bitrate: ${stream.BitRate}`); + } + if (stream.BitRate >= greaterThanBits && stream.BitRate <= lessThanBits) { + isWithinRange = true; + } + } + }); + } + + if (!hasVideoBitrate) { + throw new Error('Video bitrate not found'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: isWithinRange ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.ts new file mode 100644 index 000000000..21b8c33fe --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoCodec/1.0.0/index.ts @@ -0,0 +1,74 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Check Video Codec', + description: 'Check if a file has a specific video codec', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [ + { + name: 'codec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + 'vp9', + 'h264', + 'vp8', + ], + }, + tooltip: 'Specify the codec check for', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'File has codec', + }, + { + number: 2, + tooltip: 'File does not have codec', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let hasCodec = false; + + if (args.inputFileObj.ffProbeData.streams) { + args.inputFileObj.ffProbeData.streams.forEach((stream) => { + if (stream.codec_type === 'video' && stream.codec_name === args.inputs.codec) { + hasCodec = true; + } + }); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: hasCodec ? 1 : 2, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.ts new file mode 100644 index 000000000..0e76a04e9 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoResolution/1.0.0/index.ts @@ -0,0 +1,107 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Check Video Resolution', + description: 'Check is video is 480p,576p,720p,1080p,1440p,4KUHD,DCI4K,8KUHD,Other', + style: { + borderColor: 'orange', + }, + tags: 'video', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File is 480p', + }, + { + number: 2, + tooltip: 'File is 576p', + }, + { + number: 3, + tooltip: 'File is 720p', + }, + { + number: 4, + tooltip: 'File is 1080p', + }, + { + number: 5, + tooltip: 'File is 1440p', + }, + { + number: 6, + tooltip: 'File is 4KUHD', + }, + { + number: 7, + tooltip: 'File is DCI4K', + }, + { + number: 8, + tooltip: 'File is 8KUHD', + }, + { + number: 9, + tooltip: 'File is Other', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + let outputNumber = 9; + switch (args.inputFileObj.video_resolution) { + case '480p': + outputNumber = 1; + break; + case '576p': + outputNumber = 2; + break; + case '720p': + outputNumber = 3; + break; + case '1080p': + outputNumber = 4; + break; + case '1440p': + outputNumber = 5; + break; + case '4KUHD': + outputNumber = 6; + break; + case 'DCI4K': + outputNumber = 7; + break; + case '8KUHD': + outputNumber = 8; + break; + default: + outputNumber = 9; + } + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.ts new file mode 100644 index 000000000..d7c530ceb --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/checkVideoStreamsCount/1.0.0/index.ts @@ -0,0 +1,69 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = (): IpluginDetails => ({ + name: 'Check Video Streams Count', + description: 'This plugin checks if the number of video streams is 1 or more.', + style: { + borderColor: 'orange', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: 'faQuestion', + inputs: [], + outputs: [ + { + number: 1, + tooltip: 'File has one video stream', + }, + { + number: 2, + tooltip: 'File has more than one video stream', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args: IpluginInputArgs): IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const { ffProbeData } = args.inputFileObj; + + if (!ffProbeData || !ffProbeData.streams) { + throw new Error('ffProbeData or ffProbeData.streams is not available.'); + } + + const videoStreams = ffProbeData.streams.filter((stream) => stream.codec_type === 'video').length; + + let outputNumber = 1; // Default to one video stream + + if (videoStreams === 0) { + throw new Error('No video streams found in file.'); + } else if (videoStreams === 1) { + outputNumber = 1; // One video stream + } else if (videoStreams > 1) { + outputNumber = 2; // More than one video stream + } + + args.jobLog(`Number of video streams: ${videoStreams}`); + + return { + outputFileObj: args.inputFileObj, + outputNumber, + variables: args.variables, + }; +}; + +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.ts new file mode 100644 index 000000000..7a9d9969c --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/runHealthCheck/1.0.0/index.ts @@ -0,0 +1,113 @@ +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ + +import { CLI } from '../../../../FlowHelpers/1.0.0/cliUtils'; +import { getContainer, getFileName, getPluginWorkDir } from '../../../../FlowHelpers/1.0.0/fileUtils'; +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint-disable no-param-reassign */ +const details = (): IpluginDetails => ({ + name: 'Run Health Check', + description: 'Run a quick health check using HandBrake or a thorough health check using FFmpeg', + style: { + borderColor: '#6efefc', + }, + tags: 'video', + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'type', + type: 'string', + defaultValue: 'quick', + inputUI: { + type: 'dropdown', + options: [ + 'quick', + 'thorough', + ], + }, + tooltip: 'Specify the container to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = async (args:IpluginInputArgs):Promise => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const type = String(args.inputs.type); + + args.jobLog(`Running health check of type ${type}`); + + const outputFilePath = `${getPluginWorkDir(args)}/${getFileName(args.inputFileObj._id)}` + + `.${getContainer(args.inputFileObj._id)}`; + + let cliPath = args.handbrakePath; + let cliArgs = [ + '-i', + args.inputFileObj._id, + '-o', + outputFilePath, + '--scan', + ]; + + if (type === 'thorough') { + cliPath = args.ffmpegPath; + + cliArgs = [ + '-stats', + '-v', + 'error', + '-i', + args.inputFileObj._id, + '-f', + 'null', + '-max_muxing_queue_size', + '9999', + outputFilePath, + ]; + } + + const cli = new CLI({ + cli: cliPath, + spawnArgs: cliArgs, + spawnOpts: {}, + jobLog: args.jobLog, + outputFilePath, + inputFileObj: args.inputFileObj, + logFullCliOutput: args.logFullCliOutput, + updateWorker: args.updateWorker, + }); + + const res = await cli.runCli(); + + if (res.cliExitCode !== 0) { + args.jobLog('Running CLI failed'); + throw new Error('Running CLI failed'); + } + + return { + outputFileObj: args.inputFileObj, + outputNumber: 1, + variables: args.variables, + }; +}; +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.ts new file mode 100644 index 000000000..50f48b334 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowPlugins/video/transcodeVideo/1.0.0/index.ts @@ -0,0 +1,74 @@ +import { + IpluginDetails, + IpluginInputArgs, + IpluginOutputArgs, +} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces'; + +/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ +const details = ():IpluginDetails => ({ + name: 'Transcode Video File', + description: 'Transcode a video file using ffmpeg. GPU transcoding will be used if possible.', + style: { + borderColor: '#6efefc', + opacity: 0.5, + }, + tags: '', + + isStartPlugin: false, + pType: '', + requiresVersion: '2.11.01', + sidebarPosition: -1, + icon: '', + inputs: [ + { + name: 'target_codec', + type: 'string', + defaultValue: 'hevc', + inputUI: { + type: 'dropdown', + options: [ + 'hevc', + // 'vp9', + 'h264', + // 'vp8', + ], + }, + tooltip: 'Specify the codec to use', + }, + ], + outputs: [ + { + number: 1, + tooltip: 'Continue to next plugin', + }, + ], +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const plugin = (args:IpluginInputArgs):IpluginOutputArgs => { + const lib = require('../../../../../methods/lib')(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign + args.inputs = lib.loadDefaultValues(args.inputs, details); + + const fs = require('fs'); + + const oldFile = args.inputFileObj._id; + const newFile = `${args.inputFileObj._id}.tmp`; + + if (fs.existsSync(newFile)) { + fs.unlinkSync(newFile); + } + + fs.copyFileSync(oldFile, newFile); + + return { + outputFileObj: { _id: newFile }, + outputNumber: 1, + variables: args.variables, + }; +}; + +export { + details, + plugin, +}; diff --git a/FlowPluginsTs/CommunityFlowPlugins/video/transcodeVideo/1.0.0/test.ts b/FlowPluginsTs/CommunityFlowPlugins/video/transcodeVideo/1.0.0/test.ts new file mode 100644 index 000000000..e69de29bb diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter1.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter1.tsx new file mode 100644 index 000000000..b6425d811 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter1.tsx @@ -0,0 +1,112 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 1: Getting Started", + "description": "Chapter 1: Getting Started", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "_YTuyCZg3", + "position": { + "x": 644.7725474007168, + "y": -59.78556037646227 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "RQzydYbay", + "position": { + "x": 644.8785689715966, + "y": 285.63446752627516 + } + }, + { + "name": "1. Hello and welcome to Tdarr! This is a comment plugin. It doesn't do anything except help explain what's going on in a flow! You can place them anywhere and even link them together.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "df5cejfZP", + "position": { + "x": 774.8672137292031, + "y": -254.93856109034408 + } + }, + { + "name": "2. See! This comment won't do anything. The file from the previous plugin will be passed straight to the next plugin", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "30CajwYP2", + "position": { + "x": 644.6915712919753, + "y": 135.90533672888392 + } + }, + { + "name": "3. This here is an input file plugin and it's where every flow starts. You can only have ONE of these per flow.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "734dA76hg", + "position": { + "x": 444.5704060029551, + "y": -3.4693570957774114 + } + }, + { + "name": "4. That's it for this one, see you in the next chapter!", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "rkYonbPgX", + "position": { + "x": 443.9627448274695, + "y": 332.6480632642012 + } + } + ], + "flowEdges": [ + { + "source": "_YTuyCZg3", + "sourceHandle": "1", + "target": "30CajwYP2", + "targetHandle": null, + "id": "HUBIf10ny" + }, + { + "source": "30CajwYP2", + "sourceHandle": "1", + "target": "RQzydYbay", + "targetHandle": null, + "id": "Gd19X19w1" + }, + { + "source": "df5cejfZP", + "sourceHandle": "1", + "target": "30CajwYP2", + "targetHandle": null, + "id": "0EA92XgvP" + }, + { + "source": "734dA76hg", + "sourceHandle": "1", + "target": "rkYonbPgX", + "targetHandle": null, + "id": "lXbYouTsz" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter2.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter2.tsx new file mode 100644 index 000000000..a43320df0 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter2.tsx @@ -0,0 +1,191 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 2: The Basics", + "description": "Chapter 2: The Basics", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "p2KPpRjnB", + "position": { + "x": 414.1115477468154, + "y": -216.87055056329626 + } + }, + { + "name": "1. The flow follows the current 'working file' which we can run checks and take actions on.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "ecLynt2i0", + "position": { + "x": 197.4536903827362, + "y": -265.54506622009336 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "jUig7_cRU", + "position": { + "x": 439.02078192278447, + "y": 122.5624161723565 + } + }, + { + "name": "Rename File to have _BigFile", + "sourceRepo": "Community", + "pluginName": "renameFile", + "version": "1.0.0", + "inputsDB": { + "fileRename": "${fileName}_BigFile.${container}" + }, + "id": "2l0pB_oXW", + "position": { + "x": 257.94626475719076, + "y": -21.078426771503985 + } + }, + { + "name": "Check File Size", + "sourceRepo": "Community", + "pluginName": "checkFileSize", + "version": "1.0.0", + "inputsDB": { + "greaterThan": "1", + "lessThan": "10000" + }, + "id": "oDkceuMNL", + "position": { + "x": 413.7748155871969, + "y": -110.90469509295968 + } + }, + { + "name": "Each plugin can only have one input handle but many plugins can link to it. Plugins which only check something are typically orange coloured and have 2 or more outputs.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "HTvMe6FSV", + "position": { + "x": 34.402701566604065, + "y": -184.71873806260285 + } + }, + { + "name": "Once you make an action on a file, in almost all cases the output is the new file. It will be located in your library cache folder.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "3O3ECJdF-", + "position": { + "x": 33.1114649694174, + "y": 113.19141666640903 + } + }, + { + "name": "Typical usage is to replace the original file. So this plugin will replace the original file with the new file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "qA8hT1mmP", + "position": { + "x": 355.2680532661178, + "y": 199.7482565776084 + } + }, + { + "name": "This flow route doesn't change the file, so the Replace Original File plugin won't do anything and the flow will end succesffully.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "1vBp13H02", + "position": { + "x": 597.7143477707415, + "y": -48.77347490679115 + } + }, + { + "name": "Double click on a plugin to see what each GREEN output does. Ignore the RED outputs for now.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "s-m8qOhJ7", + "position": { + "x": 34.4143205430116, + "y": -5.644569445757767 + } + } + ], + "flowEdges": [ + { + "source": "p2KPpRjnB", + "sourceHandle": "1", + "target": "oDkceuMNL", + "targetHandle": null, + "id": "S8inufSTF" + }, + { + "source": "oDkceuMNL", + "sourceHandle": "1", + "target": "2l0pB_oXW", + "targetHandle": null, + "id": "LFCRv0WUh" + }, + { + "source": "2l0pB_oXW", + "sourceHandle": "1", + "target": "jUig7_cRU", + "targetHandle": null, + "id": "w0K3dKylI" + }, + { + "source": "oDkceuMNL", + "sourceHandle": "2", + "target": "jUig7_cRU", + "targetHandle": null, + "id": "SNdz3urrJ" + }, + { + "source": "ecLynt2i0", + "sourceHandle": "1", + "target": "HTvMe6FSV", + "targetHandle": null, + "id": "7qPHR6V9P" + }, + { + "source": "3O3ECJdF-", + "sourceHandle": "1", + "target": "qA8hT1mmP", + "targetHandle": null, + "id": "GjDmOX_EI" + }, + { + "source": "HTvMe6FSV", + "sourceHandle": "1", + "target": "s-m8qOhJ7", + "targetHandle": null, + "id": "0bPlyyR9Q" + }, + { + "source": "s-m8qOhJ7", + "sourceHandle": "1", + "target": "3O3ECJdF-", + "targetHandle": null, + "id": "Mxxly19vC" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter3.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter3.tsx new file mode 100644 index 000000000..39ba7ff1f --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter3.tsx @@ -0,0 +1,227 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 3: FFmpeg Command", + "description": "Chapter 3: FFmpeg Command", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 648.9333795070321, + "y": -12.529435106431094 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 723.9430232247286, + "y": 534.7914903208923 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 400.42838247161643, + "y": 438.58749864385743 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "The FFmpeg Command plugins dynamically create an FFmpeg command depending on the input file", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "hGnpEHnk5", + "position": { + "x": 254.91444207269103, + "y": -44.61887485112061 + } + }, + { + "name": "You must always begin an FFmpeg command using the 'Begin Command' Plugin", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "saj94a135", + "position": { + "x": 201.288800916537, + "y": 100.94856498487928 + } + }, + { + "name": "In this example, if the video file is already in h265/hevc and mkv container, no action will be taken on the file. To force re-encoding, you can use the forceEncoding option on the Video Encoder plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "sb5MvVryc", + "position": { + "x": 201.61485276007585, + "y": 222.09640730256172 + } + }, + { + "name": "Once the FFmpeg command has been created, you need to execute it using this plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "V0QGN5PKA", + "position": { + "x": 202.61485276007582, + "y": 440.0964073025617 + } + }, + { + "name": "Once again, the output contains the new cache file (or the original file if no action was taken on the file). If there's a new cache file, the 'Replace Original File' plugin will replace the original file, else it will do nothing.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "i4eODNlBc", + "position": { + "x": 536.6148527600759, + "y": 568.0964073025617 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "k9JywvYcK" + }, + { + "source": "hGnpEHnk5", + "sourceHandle": "1", + "target": "saj94a135", + "targetHandle": null, + "id": "dX6DiWPJX" + }, + { + "source": "saj94a135", + "sourceHandle": "1", + "target": "sb5MvVryc", + "targetHandle": null, + "id": "0MAqJvu_e" + }, + { + "source": "sb5MvVryc", + "sourceHandle": "1", + "target": "V0QGN5PKA", + "targetHandle": null, + "id": "57NrKKG2n" + }, + { + "source": "V0QGN5PKA", + "sourceHandle": "1", + "target": "i4eODNlBc", + "targetHandle": null, + "id": "BHwljK8rj" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p1.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p1.tsx new file mode 100644 index 000000000..35eb93e27 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p1.tsx @@ -0,0 +1,216 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 4: Flow Errors Part 1", + "description": "Chapter 4: Flow Errors Part 1", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 416.1451226612283, + "y": 433.3485852473201 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "If an unhandled error occurs during the flow, the flow will stop and the file will be moved to the Transcode: Error/Cancelled tab. You can then review the job report to see what went wrong.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "mNaOzfS0Y", + "position": { + "x": 604.5850500985517, + "y": 166.18413013606266 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "You can also force a flow to fail which can be useful in certain situation such as here.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "9QkIvxxxx", + "position": { + "x": 678.3646507954192, + "y": 429.7476734555484 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "mNaOzfS0Y", + "sourceHandle": "1", + "target": "9QkIvxxxx", + "targetHandle": null, + "id": "4Yez6rEN2" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p2.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p2.tsx new file mode 100644 index 000000000..2fbf8ddf0 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p2.tsx @@ -0,0 +1,310 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 4: Flow Errors Part 2 - On Flow Error", + "description": "Chapter 4: Flow Errors Part 2 - On Flow Error", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 416.1451226612283, + "y": 433.3485852473201 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 922.4197900595414, + "y": 161.088098623682 + } + }, + { + "name": "1. To handle an error that occurs anywhere in this specifc flow, you can use the 'On Flow Error' plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "jpEn9FVQX", + "position": { + "x": 1018.5785464566798, + "y": 65.98583847747655 + } + }, + { + "name": "All unhandled errors and the 'Fail Flow' plugin IN THIS FLOW will trigger the 'On Flow Error' plugin IN THIS FLOW", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "vRFTPo0p5", + "position": { + "x": 698.3866844766682, + "y": 451.2472106052397 + } + }, + { + "name": "If another error occurs in the 'On Flow Error' flow then the flow will end and the file will be moved to the transcode 'Transcode: Error/Cancelled' tab. The 'On Flow Error' plugin will NOT be run again (to prevent infinite error loops)", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "fAQouEkEY", + "position": { + "x": 1183.421069816697, + "y": 228.19157008625297 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "42P9lb0B3", + "position": { + "x": 897.7260729664589, + "y": 469.1243455181426 + } + }, + { + "name": "Even if all the plugins in the error flow complete successfully, the file will still be moved to the 'Transcode: Error/Cancelled' tab at the end.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "Ke1S57we6", + "position": { + "x": 1006.3188415680227, + "y": 541.3784972055968 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "yj3grm5d8", + "position": { + "x": 1047.7090907308577, + "y": 394.7331214427515 + } + }, + { + "name": "Check File Exists", + "sourceRepo": "Community", + "pluginName": "checkFileExists", + "version": "1.0.0", + "id": "S_rVuKn8S", + "position": { + "x": 926.2665476107438, + "y": 277.7066707997331 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "jpEn9FVQX", + "sourceHandle": "1", + "target": "fAQouEkEY", + "targetHandle": null, + "id": "5mv1ls7Ib" + }, + { + "source": "fAQouEkEY", + "sourceHandle": "1", + "target": "Ke1S57we6", + "targetHandle": null, + "id": "_VEvhMOtk" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "S_rVuKn8S", + "targetHandle": null, + "id": "yweCdlSWM" + }, + { + "source": "S_rVuKn8S", + "sourceHandle": "1", + "target": "yj3grm5d8", + "targetHandle": null, + "id": "xI3eh7wZp" + }, + { + "source": "S_rVuKn8S", + "sourceHandle": "2", + "target": "42P9lb0B3", + "targetHandle": null, + "id": "V-qf6QBC4" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p3.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p3.tsx new file mode 100644 index 000000000..dbf952929 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p3.tsx @@ -0,0 +1,319 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ( + { + "name": "Chapter 4: Flow Errors Part 3 - Plugin-specific Error Handling", + "description": "Chapter 4: Flow Errors Part 3 - Plugin-specific Error Handling", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 541.9238836009351, + "y": 32.863009312154745 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "QdLvoNjuG", + "position": { + "x": 773.1888091521793, + "y": 727.583503313465 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "-kY9osnGE", + "position": { + "x": 399.6705241883612, + "y": 143.02276817432977 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "pmoPx8W0W", + "position": { + "x": 399.8062875388412, + "y": 417.9708580733087 + } + }, + { + "name": "Set Container", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetContainer", + "version": "1.0.0", + "id": "-DEIJA3Pf", + "position": { + "x": 401.1862407548717, + "y": 335.51877212115033 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "U0fVPXskr", + "position": { + "x": 400.1862407548716, + "y": 249.12292783005773 + } + }, + { + "name": "Fail Flow", + "sourceRepo": "Community", + "pluginName": "failFlow", + "version": "1.0.0", + "id": "mNwoZNlmo", + "position": { + "x": 616.8564543703576, + "y": 578.3209514237449 + } + }, + { + "name": "Compare File Size", + "sourceRepo": "Community", + "pluginName": "compareFileSize", + "version": "1.0.0", + "id": "YGd45fK8d", + "position": { + "x": 518.9335431151374, + "y": 502.8688871164036 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 1060.1712089610803, + "y": 145.28055874973487 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "Bc2bZtgBc", + "position": { + "x": 922.4677516191077, + "y": 279.351803016885 + } + }, + { + "name": "Tdarr also offers plugin-specific error handling using the RED connection on each plugin. The flow path will be triggered if an unhandled error occurs within that specific plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "H__edbTLw", + "position": { + "x": 767.7320724785685, + "y": -230.81328720265796 + } + }, + { + "name": "Send Web Request: Ping Melissa to check network storage", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "X9NEJCEgk", + "position": { + "x": 880.7836232681229, + "y": -39.676791653194755 + } + }, + { + "name": "Send Web Request: Ping Romesh to check transcode log", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "BO0c5TlKq", + "position": { + "x": 661.6510110384603, + "y": 300.5554220718082 + } + }, + { + "name": "This allows very specific error flows, for example pinging different team members for different errors.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "zTVwtbuxI", + "position": { + "x": 657.8440331872728, + "y": 166.67570418496692 + } + }, + { + "name": "The plugin-specifc error handling will NOT trigger the 'On Flow Error' plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "N9E-u8l0o", + "position": { + "x": 1170.7519669401188, + "y": 56.50523025141092 + } + }, + { + "name": "But you can still join the plugin-specific error handling flow onto the rest of the 'On Flow Error' Flow", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "AOi3vLobO", + "position": { + "x": 1125.9768689675689, + "y": 284.4868129833375 + } + } + ], + "flowEdges": [ + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "ldiZljXp2" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "-kY9osnGE", + "sourceHandle": "1", + "target": "U0fVPXskr", + "targetHandle": null, + "id": "wuqNLcC1D" + }, + { + "source": "U0fVPXskr", + "sourceHandle": "1", + "target": "-DEIJA3Pf", + "targetHandle": null, + "id": "Coq5pIs3c" + }, + { + "source": "-DEIJA3Pf", + "sourceHandle": "1", + "target": "pmoPx8W0W", + "targetHandle": null, + "id": "fGjbMXOng" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "-kY9osnGE", + "targetHandle": null, + "id": "E5NHstmdF" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "1", + "target": "YGd45fK8d", + "targetHandle": null, + "id": "bldP67hmm" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "1", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "fw9Le5zqo" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "2", + "target": "QdLvoNjuG", + "targetHandle": null, + "id": "wd7SmimpM" + }, + { + "source": "YGd45fK8d", + "sourceHandle": "3", + "target": "mNwoZNlmo", + "targetHandle": null, + "id": "RmpqCf-Vh" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "Bc2bZtgBc", + "targetHandle": null, + "id": "7k8P1VYv6" + }, + { + "source": "gtZCtmY-l", + "sourceHandle": "err1", + "target": "X9NEJCEgk", + "targetHandle": null, + "id": "9rhuR5eSI" + }, + { + "source": "pmoPx8W0W", + "sourceHandle": "err1", + "target": "BO0c5TlKq", + "targetHandle": null, + "id": "ttZgLtKF3" + }, + { + "source": "H__edbTLw", + "sourceHandle": "1", + "target": "zTVwtbuxI", + "targetHandle": null, + "id": "5sjNNMXAK" + }, + { + "source": "X9NEJCEgk", + "sourceHandle": "1", + "target": "Bc2bZtgBc", + "targetHandle": null, + "id": "OW-yqRQH5" + }, + { + "source": "N9E-u8l0o", + "sourceHandle": "1", + "target": "AOi3vLobO", + "targetHandle": null, + "id": "K440_LQm_" + } + ] + } +); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p4.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p4.tsx new file mode 100644 index 000000000..6434f28ad --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter4p4.tsx @@ -0,0 +1,186 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ( + { + "name": "Chapter 4: Flow Errors Part 4 - Resetting Flow Errors", + "description": "Chapter 4: Flow Errors Part 4 - Resetting Flow Errors", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "kc2oWbzdg", + "position": { + "x": 792.9115444209641, + "y": 72.86840721125132 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "MttLdH9JH", + "position": { + "x": 656.7168106666427, + "y": 259.68513570736866 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "SUV-PcTXK", + "position": { + "x": 656.3361499796371, + "y": 466.43606780911165 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "OZTQfLxQU", + "position": { + "x": 657.071898477293, + "y": 356.04954686544056 + } + }, + { + "name": "Send Web Request", + "sourceRepo": "Community", + "pluginName": "webRequest", + "version": "1.0.0", + "id": "alp4lZYtu", + "position": { + "x": 939.7604719407238, + "y": 380.4448835288041 + } + }, + { + "name": "Once a flow error occurs, even if subsequent plugins run successfully, the item will still be moved to the 'Transcode: Error/Cancelled' tab after the final plugin has finished, as would happen if the flow ended here.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "uyAA1bj79", + "position": { + "x": 1114.7604719407238, + "y": 318.4448835288041 + } + }, + { + "name": "Reset Flow Error", + "sourceRepo": "Community", + "pluginName": "resetFlowError", + "version": "1.0.0", + "id": "_DZqwYTaP", + "position": { + "x": 1005.7604719407236, + "y": 547.4448835288041 + } + }, + { + "name": "HandBrake Custom Arguments", + "sourceRepo": "Community", + "pluginName": "handbrakeCustomArguments", + "version": "1.0.0", + "id": "pX08BTxw7", + "position": { + "x": 1126.7604719407238, + "y": 697.4448835288041 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "0QGXt-4Zi", + "position": { + "x": 855.7604719407237, + "y": 853.4448835288041 + } + }, + { + "name": "Use this plugin to reset the flow error status. You can then attempt a new way of processing the file", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "aWlDgeStq", + "position": { + "x": 1176.7604719407238, + "y": 531.4448835288041 + } + } + ], + "flowEdges": [ + { + "source": "kc2oWbzdg", + "sourceHandle": "1", + "target": "MttLdH9JH", + "targetHandle": null, + "id": "T43cHyOAu" + }, + { + "source": "MttLdH9JH", + "sourceHandle": "1", + "target": "OZTQfLxQU", + "targetHandle": null, + "id": "jqX_8_fKY" + }, + { + "source": "OZTQfLxQU", + "sourceHandle": "1", + "target": "SUV-PcTXK", + "targetHandle": null, + "id": "-1tj4OiNV" + }, + { + "source": "OZTQfLxQU", + "sourceHandle": "err1", + "target": "alp4lZYtu", + "targetHandle": null, + "id": "LOQaBxuai" + }, + { + "source": "pX08BTxw7", + "sourceHandle": "1", + "target": "0QGXt-4Zi", + "targetHandle": null, + "id": "VMBnPqvzU" + }, + { + "source": "SUV-PcTXK", + "sourceHandle": "1", + "target": "0QGXt-4Zi", + "targetHandle": null, + "id": "wsDY8MYki" + }, + { + "source": "alp4lZYtu", + "sourceHandle": "1", + "target": "_DZqwYTaP", + "targetHandle": null, + "id": "g9tDFsL5p" + }, + { + "source": "_DZqwYTaP", + "sourceHandle": "1", + "target": "pX08BTxw7", + "targetHandle": null, + "id": "YwFpa-_YC" + } + ] + } +); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter5.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter5.tsx new file mode 100644 index 000000000..4016acd0f --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter5.tsx @@ -0,0 +1,159 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 5: Go To Flow", + "description": "Chapter 5: Go To Flow", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "On Flow Error", + "sourceRepo": "Community", + "pluginName": "onFlowError", + "version": "1.0.0", + "id": "yMWso-uZa", + "position": { + "x": 1122.33024332169, + "y": 226.4434391132305 + } + }, + { + "name": "You can use the Go To Flow to go to a different flow. The working file will be passed to that flow and will continue as normal. Double click on the plugin to select the flow you'd like to go to.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "rHV28Kbkv", + "position": { + "x": 462.0014512264263, + "y": 65.78412788449464 + } + }, + { + "name": "Go To Flow", + "sourceRepo": "Community", + "pluginName": "goToFlow", + "version": "1.0.0", + "id": "gOrbropah", + "position": { + "x": 572.7308895655424, + "y": 234.58707695358294 + } + }, + { + "name": "By design, if an error happens in a different flow, this 'On Flow Error' will not be called. Across all flows, the 'On Flow Error' plugin will only be called ONCE in the flow that the FIRST error occurred in. 'On Flow Error' plugins in flows before or after the current flow will not be called, even if an error occurs in them at a later time.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "7azuiVML9", + "position": { + "x": 1310.2464269220861, + "y": 134.70796523124582 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "ELn0kcc-1", + "position": { + "x": 778.4079905179452, + "y": 428.7308825254772 + } + }, + { + "name": "Go To Flow", + "sourceRepo": "Community", + "pluginName": "goToFlow", + "version": "1.0.0", + "id": "j5dOGi9zz", + "position": { + "x": 1122.484636036451, + "y": 397.97542817745443 + } + }, + { + "name": "After an error has occured you can even go to a different flow! So you can create a dedicated Error flow and go to it each time an error occurs within any of your flows! Useful for notifications etc.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "EpMxr2UuE", + "position": { + "x": 1205.7688307851763, + "y": 479.4625484620842 + } + } + ], + "flowEdges": [ + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "gOrbropah", + "targetHandle": null, + "id": "qVWE7SWt2" + }, + { + "source": "rHV28Kbkv", + "sourceHandle": "1", + "target": "7azuiVML9", + "targetHandle": null, + "id": "i0OUf3hAM" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "ELn0kcc-1", + "targetHandle": null, + "id": "hTaDcPw24" + }, + { + "source": "yMWso-uZa", + "sourceHandle": "1", + "target": "j5dOGi9zz", + "targetHandle": null, + "id": "6Hrh7vbfW" + }, + { + "source": "7azuiVML9", + "sourceHandle": "1", + "target": "EpMxr2UuE", + "targetHandle": null, + "id": "S_36mCXnL" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter6.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter6.tsx new file mode 100644 index 000000000..591dff578 --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter6.tsx @@ -0,0 +1,270 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 6: The Review System", + "description": "Chapter 6: The Review System", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "gtZCtmY-l", + "position": { + "x": 648.6536861377089, + "y": -82.45578042880155 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "PpLF-5jxp", + "position": { + "x": 752.4065242952165, + "y": 51.12406033129332 + } + }, + { + "name": "Replace Original File", + "sourceRepo": "Community", + "pluginName": "replaceOriginalFile", + "version": "1.0.0", + "id": "R0gX9B20d", + "position": { + "x": 879.7236115475249, + "y": 934.782797377857 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "U6N3AQubH", + "position": { + "x": 546.8854528742303, + "y": 174.54090453410515 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "Lv-zb-iTw", + "position": { + "x": 543.172691292081, + "y": 368.6158072160807 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "1pOFUCuQR", + "position": { + "x": 545.0642491154337, + "y": 274.70711126791645 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "oHpu2fZOi", + "position": { + "x": 631.7959812272709, + "y": 459.542392214296 + } + }, + { + "name": "Run Classic Transcode Plugin: Add Audio Stream", + "sourceRepo": "Community", + "pluginName": "runClassicTranscodePlugin", + "version": "1.0.0", + "inputsDB": { + "pluginSourceId": "Community:Tdarr_Plugin_00td_action_add_audio_stream_codec" + }, + "id": "RZX5jIP5I", + "position": { + "x": 632.2402074212371, + "y": 545.8356807635909 + } + }, + { + "name": "Run Classic Transcode Plugin", + "sourceRepo": "Community", + "pluginName": "runClassicTranscodePlugin", + "version": "1.0.0", + "inputsDB": { + "pluginSourceId": "Community:Tdarr_Plugin_00td_action_remove_audio_by_channel_count", + "channelCounts": "8" + }, + "id": "3zj5puRQ1", + "position": { + "x": 696.5390735282473, + "y": 758.7339551203235 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "q8Pz_3HGh", + "position": { + "x": 634.7388490591334, + "y": 648.8668893974542 + } + }, + { + "name": "You can pause a flow by using the 'Require Review' plugin. This will cause the file to stay in the staging section on the Tdarr tab until the 'Reviewed' button is pressed. This allows you to check the last completed cache file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "YPyMAbZ76", + "position": { + "x": 856.2716462414401, + "y": 343.06736953610425 + } + }, + { + "name": "Once the file has been reviewed, the flow will continue from the next plugin.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "REt4UEEGD", + "position": { + "x": 856.7260936183322, + "y": 531.8099095812979 + } + }, + { + "name": "You can 'Require Review' as much as you like!", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "UhuSLjA8g", + "position": { + "x": 857.7070495622867, + "y": 709.362935437006 + } + }, + { + "name": "Require Review", + "sourceRepo": "Community", + "pluginName": "requireReview", + "version": "1.0.0", + "id": "8IU0bhEJs", + "position": { + "x": 780.2115299899054, + "y": 844.7348557026837 + } + } + ], + "flowEdges": [ + { + "source": "gtZCtmY-l", + "sourceHandle": "1", + "target": "PpLF-5jxp", + "targetHandle": null, + "id": "Cs5aBSUks" + }, + { + "source": "U6N3AQubH", + "sourceHandle": "1", + "target": "1pOFUCuQR", + "targetHandle": null, + "id": "RdnvWmv0o" + }, + { + "source": "1pOFUCuQR", + "sourceHandle": "1", + "target": "Lv-zb-iTw", + "targetHandle": null, + "id": "p-VkIS6DK" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "2", + "target": "U6N3AQubH", + "targetHandle": null, + "id": "x_vWzShYB" + }, + { + "source": "PpLF-5jxp", + "sourceHandle": "1", + "target": "R0gX9B20d", + "targetHandle": null, + "id": "CtIsUppTB" + }, + { + "source": "Lv-zb-iTw", + "sourceHandle": "1", + "target": "oHpu2fZOi", + "targetHandle": null, + "id": "d9tDIjd1L" + }, + { + "source": "oHpu2fZOi", + "sourceHandle": "1", + "target": "RZX5jIP5I", + "targetHandle": null, + "id": "kFP4WRftx" + }, + { + "source": "RZX5jIP5I", + "sourceHandle": "1", + "target": "q8Pz_3HGh", + "targetHandle": null, + "id": "nqbQJ9wUz" + }, + { + "source": "q8Pz_3HGh", + "sourceHandle": "1", + "target": "3zj5puRQ1", + "targetHandle": null, + "id": "Vx60urLP7" + }, + { + "source": "YPyMAbZ76", + "sourceHandle": "1", + "target": "REt4UEEGD", + "targetHandle": null, + "id": "B85VWeRRu" + }, + { + "source": "REt4UEEGD", + "sourceHandle": "1", + "target": "UhuSLjA8g", + "targetHandle": null, + "id": "wk44u1THD" + }, + { + "source": "3zj5puRQ1", + "sourceHandle": "1", + "target": "8IU0bhEJs", + "targetHandle": null, + "id": "9Q4vfDFmI" + }, + { + "source": "8IU0bhEJs", + "sourceHandle": "1", + "target": "R0gX9B20d", + "targetHandle": null, + "id": "8wXVHwiDC" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter7.tsx b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter7.tsx new file mode 100644 index 000000000..20a3660fe --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/tutorials/chapter7.tsx @@ -0,0 +1,169 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + "name": "Chapter 7: Using an Output Folder", + "description": "Chapter 7: Using an Output Folder", + "tags": "", + "flowPlugins": [ + { + "name": "Input File", + "sourceRepo": "Community", + "pluginName": "inputFile", + "version": "1.0.0", + "id": "y4lqcdrho", + "position": { + "x": 657.6098846576496, + "y": 83.73457282843094 + } + }, + { + "name": "Check Video Codec", + "sourceRepo": "Community", + "pluginName": "checkVideoCodec", + "version": "1.0.0", + "id": "x4UqtuCK9", + "position": { + "x": 546.5579603766718, + "y": 179.82684381124744 + } + }, + { + "name": "Begin Command", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandStart", + "version": "1.0.0", + "id": "6a0HyfVUa", + "position": { + "x": 394.21161179985006, + "y": 264.2702484509714 + } + }, + { + "name": "Execute", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandExecute", + "version": "1.0.0", + "id": "OBbLCZ8SO", + "position": { + "x": 395.9527129264425, + "y": 467.9790802622644 + } + }, + { + "name": "Set Video Encoder", + "sourceRepo": "Community", + "pluginName": "ffmpegCommandSetVideoEncoder", + "version": "1.0.0", + "id": "nvhiecc42", + "position": { + "x": 395.95271292644225, + "y": 360.03081041354505 + } + }, + { + "name": "Move To Directory", + "sourceRepo": "Community", + "pluginName": "moveToDirectory", + "version": "2.0.0", + "id": "8ffv7PWIl", + "position": { + "x": 586.6032862882935, + "y": 587.2445074338334 + }, + "inputsDB": { + "outputDirectory": "/example" + } + }, + { + "name": "By default, the final working file in the flow is what will be kept in the Tdarr database and will appear in the 'Transcode: Success/Not required' table. If the flow were to end here, the new file in the output folder would be kept in the Tdarr database.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "XJEBW_sGy", + "position": { + "x": 792.5819966148043, + "y": 513.4182522751619 + } + }, + { + "name": "Set Original File", + "sourceRepo": "Community", + "pluginName": "setOriginalFile", + "version": "1.0.0", + "id": "X4K2UoPuU", + "position": { + "x": 587.1019377136648, + "y": 787.596766284271 + } + }, + { + "name": "To have the original file be kept in the Tdarr database, use the 'Set Original File' plugin which will set the working file to the original file.", + "sourceRepo": "Community", + "pluginName": "comment", + "version": "1.0.0", + "id": "yXP0EZrMk", + "position": { + "x": 777.1019377136646, + "y": 787.596766284271 + } + } + ], + "flowEdges": [ + { + "source": "y4lqcdrho", + "sourceHandle": "1", + "target": "x4UqtuCK9", + "targetHandle": null, + "id": "c6kYE6bDM" + }, + { + "source": "x4UqtuCK9", + "sourceHandle": "1", + "target": "8ffv7PWIl", + "targetHandle": null, + "id": "odwp-30JQ" + }, + { + "source": "x4UqtuCK9", + "sourceHandle": "2", + "target": "6a0HyfVUa", + "targetHandle": null, + "id": "87bCwwur4" + }, + { + "source": "6a0HyfVUa", + "sourceHandle": "1", + "target": "nvhiecc42", + "targetHandle": null, + "id": "kYgiFcdHk" + }, + { + "source": "nvhiecc42", + "sourceHandle": "1", + "target": "OBbLCZ8SO", + "targetHandle": null, + "id": "guA5aTucK" + }, + { + "source": "OBbLCZ8SO", + "sourceHandle": "1", + "target": "8ffv7PWIl", + "targetHandle": null, + "id": "PgL3EQtsQ" + }, + { + "source": "8ffv7PWIl", + "sourceHandle": "1", + "target": "X4K2UoPuU", + "targetHandle": null, + "id": "JX7tzmso3" + } + ] +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/video/basicVideo.ts b/FlowPluginsTs/CommunityFlowTemplates/video/basicVideo.ts new file mode 100644 index 000000000..3a146d04e --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/video/basicVideo.ts @@ -0,0 +1,144 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + name: 'Basic HEVC Video Flow', + description: 'Basic HEVC Video Flow', + tags: '', + flowPlugins: [ + { + name: 'Input File', + sourceRepo: 'Community', + pluginName: 'inputFile', + version: '1.0.0', + id: 'pE6rU7gkW', + position: { + x: 758.5809635618224, + y: 117.19206188888086, + }, + }, + { + name: 'Check if hevc', + sourceRepo: 'Community', + pluginName: 'checkVideoCodec', + version: '1.0.0', + id: '91b7IrsEc', + position: { + x: 672.4549563302081, + y: 253.11148102973914, + }, + }, + { + name: 'Start', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: '4Swd6qzvc', + position: { + x: 489.25252076795084, + y: 370.51229288382495, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: '450g167D8', + position: { + x: 488.72295602997406, + y: 699.5034828311435, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + id: '8B_6pRd_U', + position: { + x: 488.5270135748424, + y: 477.83202026423606, + }, + }, + { + name: 'Replace Original File', + sourceRepo: 'Community', + pluginName: 'replaceOriginalFile', + version: '1.0.0', + id: '4fkfOyR3l', + position: { + x: 820.4549563302082, + y: 742.2114810297393, + }, + }, + { + name: 'Set Container', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetContainer', + version: '1.0.0', + id: 'TtKXi3Q7h', + position: { + x: 488.21110165973323, + y: 570.3064821931456, + }, + }, + ], + flowEdges: [ + { + source: 'pE6rU7gkW', + sourceHandle: '1', + target: '91b7IrsEc', + targetHandle: null, + id: 'HhF4rw2DZ', + }, + { + source: '91b7IrsEc', + sourceHandle: '2', + target: '4Swd6qzvc', + targetHandle: null, + id: 'jJizyFUcr', + }, + { + source: '4Swd6qzvc', + sourceHandle: '1', + target: '8B_6pRd_U', + targetHandle: null, + id: '3Df7Xoy93', + }, + { + source: '450g167D8', + sourceHandle: '1', + target: '4fkfOyR3l', + targetHandle: null, + id: 'rE5Dsh9KM', + }, + { + source: '91b7IrsEc', + sourceHandle: '1', + target: '4fkfOyR3l', + targetHandle: null, + id: 'W2nVG7ts5', + }, + { + source: '8B_6pRd_U', + sourceHandle: '1', + target: 'TtKXi3Q7h', + targetHandle: null, + id: 'epqtLsPuG', + }, + { + source: 'TtKXi3Q7h', + sourceHandle: '1', + target: '450g167D8', + targetHandle: null, + id: 'ljOeP0cAZ', + }, + ], +}); + +export { + details, +}; diff --git a/FlowPluginsTs/CommunityFlowTemplates/video/lowResolutionCopies.ts b/FlowPluginsTs/CommunityFlowTemplates/video/lowResolutionCopies.ts new file mode 100644 index 000000000..d9ddbcf8c --- /dev/null +++ b/FlowPluginsTs/CommunityFlowTemplates/video/lowResolutionCopies.ts @@ -0,0 +1,365 @@ +/* eslint-disable no-template-curly-in-string */ +/* eslint-disable import/prefer-default-export */ + +import { IflowTemplate } from '../../FlowHelpers/1.0.0/interfaces/interfaces'; + +const details = () :IflowTemplate => ({ + name: 'Create Low Resolution Video Copies', + description: 'Create Low Resolution Video Copies', + tags: '', + flowPlugins: [ + { + name: 'Input File', + sourceRepo: 'Community', + pluginName: 'inputFile', + version: '1.0.0', + id: 'pE6rU7gkW', + position: { + x: 764.3859715446088, + y: 54.59674430707997, + }, + }, + { + name: 'Check File Exists _480p', + sourceRepo: 'Community', + pluginName: 'checkFileExists', + version: '1.0.0', + inputsDB: { + fileToCheck: '${fileName}_480p.${container}', + }, + id: 'VyNRD3YjM', + position: { + x: 1127.8807371830678, + y: -1.4370146635981769, + }, + }, + { + name: 'Rename File _480p', + sourceRepo: 'Community', + pluginName: 'renameFile', + version: '1.0.0', + inputsDB: { + fileRename: '${fileName}_480p.${container}', + }, + id: 'VpCD-7LZJ', + position: { + x: 1398.163993949301, + y: 562.3533349776774, + }, + }, + { + name: 'Replace Original File', + sourceRepo: 'Community', + pluginName: 'replaceOriginalFile', + version: '1.0.0', + id: '1pj9oSg5G', + position: { + x: 736.3406162570204, + y: 598.8673432638388, + }, + }, + { + name: 'Check File Exists _720p', + sourceRepo: 'Community', + pluginName: 'checkFileExists', + version: '1.0.0', + inputsDB: { + fileToCheck: '${fileName}_720p.${container}', + }, + id: 'uDC6XT1Jy', + position: { + x: 1060.0100333142968, + y: 110.8981370311281, + }, + }, + { + name: 'Check File Name Includes', + sourceRepo: 'Community', + pluginName: 'checkFileNameIncludes', + version: '1.0.0', + inputsDB: { + terms: '_720p,_480p', + }, + id: 'wRipuaq4G', + position: { + x: 763.9976994431687, + y: 198.97576654117708, + }, + }, + { + name: 'Rename File _720p', + sourceRepo: 'Community', + pluginName: 'renameFile', + version: '1.0.0', + inputsDB: { + fileRename: '${fileName}_720p.${container}', + }, + id: 'cTKbaB8nT', + position: { + x: 1087.883110780845, + y: 509.6274273776863, + }, + }, + { + name: 'Move To Original Directory', + sourceRepo: 'Community', + pluginName: 'moveToOriginalDirectory', + version: '1.0.0', + id: 'mFRK-Z9WC', + position: { + x: 1162.0438576735944, + y: 608.0524996697887, + }, + }, + { + name: 'Set Original File', + sourceRepo: 'Community', + pluginName: 'setOriginalFile', + version: '1.0.0', + id: 'oD4u5PY9T', + position: { + x: 1020.6746394849897, + y: 738.9349550904227, + }, + }, + { + name: 'Begin Command', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: 'FSG9AOX5c', + position: { + x: 1171.2902386661297, + y: 178.0193821518036, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + inputsDB: { + forceEncoding: 'true', + }, + id: 'wcmBN2N02', + position: { + x: 1171.0819612214827, + y: 257.19366435734827, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: 'tmUd79-Fb', + position: { + x: 1167.5698309351776, + y: 406.2043896501846, + }, + }, + { + name: 'Begin Command', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandStart', + version: '1.0.0', + id: 'Jn6dcKd3i', + position: { + x: 1395.4614497255334, + y: 111.74717420966138, + }, + }, + { + name: 'Execute', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandExecute', + version: '1.0.0', + id: 'gbY0xIJnB', + position: { + x: 1398.0103706416776, + y: 417.6787803779547, + }, + }, + { + name: 'Set Video Resolution 480p', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVdeoResolution', + version: '1.0.0', + inputsDB: { + targetResolution: '480p', + }, + id: 'dzFEwECXB', + position: { + x: 1396.1961096759603, + y: 309.9727302535869, + }, + }, + { + name: 'Set Video Encoder', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVideoEncoder', + version: '1.0.0', + inputsDB: { + forceEncoding: 'true', + }, + id: '_EynbvgSl', + position: { + x: 1396.1961096759603, + y: 214.35898180146438, + }, + }, + { + name: 'Set Video Resolution 720p', + sourceRepo: 'Community', + pluginName: 'ffmpegCommandSetVdeoResolution', + version: '1.0.0', + inputsDB: { + targetResolution: '720p', + }, + id: 'CMm7MlE7g', + position: { + x: 1169.6624226114702, + y: 336.82482287402803, + }, + }, + ], + flowEdges: [ + { + source: 'pE6rU7gkW', + sourceHandle: '1', + target: 'wRipuaq4G', + targetHandle: null, + id: 'IE_oGhETB', + }, + { + source: 'wRipuaq4G', + sourceHandle: '1', + target: '1pj9oSg5G', + targetHandle: null, + id: 'QR6uGNUhE', + }, + { + source: 'wRipuaq4G', + sourceHandle: '2', + target: 'VyNRD3YjM', + targetHandle: null, + id: 'sh_kstv0D', + }, + { + source: 'uDC6XT1Jy', + sourceHandle: '1', + target: '1pj9oSg5G', + targetHandle: null, + id: 'G5jl85ijr', + }, + { + source: 'VyNRD3YjM', + sourceHandle: '1', + target: 'uDC6XT1Jy', + targetHandle: null, + id: 'DmUL9DS8q', + }, + { + source: 'VpCD-7LZJ', + sourceHandle: '1', + target: 'mFRK-Z9WC', + targetHandle: null, + id: 'ap4YXAxy3', + }, + { + source: 'cTKbaB8nT', + sourceHandle: '1', + target: 'mFRK-Z9WC', + targetHandle: null, + id: 'i9fr5J5pL', + }, + { + source: 'mFRK-Z9WC', + sourceHandle: '1', + target: 'oD4u5PY9T', + targetHandle: null, + id: 'KUw59S_Zl', + }, + { + source: 'oD4u5PY9T', + sourceHandle: '1', + target: 'wRipuaq4G', + targetHandle: null, + id: 'HlM4E6eV8', + }, + { + source: 'tmUd79-Fb', + sourceHandle: '1', + target: 'cTKbaB8nT', + targetHandle: null, + id: 'iJLmmoDLp', + }, + { + source: 'uDC6XT1Jy', + sourceHandle: '2', + target: 'FSG9AOX5c', + targetHandle: null, + id: 'iRTrU8utq', + }, + { + source: 'dzFEwECXB', + sourceHandle: '1', + target: 'gbY0xIJnB', + targetHandle: null, + id: 'A5cyCu_kx', + }, + { + source: 'Jn6dcKd3i', + sourceHandle: '1', + target: '_EynbvgSl', + targetHandle: null, + id: '1HajidLz-', + }, + { + source: '_EynbvgSl', + sourceHandle: '1', + target: 'dzFEwECXB', + targetHandle: null, + id: 'vEESYeSsL', + }, + { + source: 'VyNRD3YjM', + sourceHandle: '2', + target: 'Jn6dcKd3i', + targetHandle: null, + id: 'q8zd_qCSU', + }, + { + source: 'gbY0xIJnB', + sourceHandle: '1', + target: 'VpCD-7LZJ', + targetHandle: null, + id: 'leYMQdxHw', + }, + { + source: 'FSG9AOX5c', + sourceHandle: '1', + target: 'wcmBN2N02', + targetHandle: null, + id: 'Dl5MCSqQM', + }, + { + source: 'wcmBN2N02', + sourceHandle: '1', + target: 'CMm7MlE7g', + targetHandle: null, + id: 'GIpbjomC8', + }, + { + source: 'CMm7MlE7g', + sourceHandle: '1', + target: 'tmUd79-Fb', + targetHandle: null, + id: 'AxR9R10MY', + }, + ], +}); + +export { + details, +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/classicPlugins.ts b/FlowPluginsTs/FlowHelpers/1.0.0/classicPlugins.ts new file mode 100644 index 000000000..00c89773e --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/classicPlugins.ts @@ -0,0 +1,147 @@ +import { promises as fs } from 'fs'; +import { + getContainer, getFileName, getPluginWorkDir, getScanTypes, +} from './fileUtils'; +import { IpluginInputArgs } from './interfaces/interfaces'; + +export interface IrunClassicPlugin { + result:{ + processFile: boolean, + handBrakeMode?: boolean, + handbrakeMode?: boolean, + FFmpegMode?: boolean, + ffmpegMode?: boolean, + cliToUse?: string, + custom?: { + cliPath?: string, + args: string[], + outputPath: string, + }, + workerLog?: string, + transcodeSettingsLog?: string, + error?: string, + container: string, + preset: string, + }; + cacheFilePath:string; + absolutePath:string; +} + +export const runClassicPlugin = async (args:IpluginInputArgs, type:'filter'|'transcode'):Promise => { + const path = require('path'); + + const pluginSourceId = String(args.inputs.pluginSourceId); + const parts = pluginSourceId.split(':'); + const pluginSource = parts[0]; + const pluginId = parts[1]; + + const relativePluginPath = `../../../${pluginSource}/${pluginId}.js`; + const absolutePath = path.resolve(__dirname, relativePluginPath); + + let classicPlugin; + let pluginSrcStr = ''; + if (pluginSource === 'Community') { + classicPlugin = args.deps.importFresh(relativePluginPath); + pluginSrcStr = await fs.readFile(absolutePath, 'utf8'); + } else { + // eslint-disable-next-line no-await-in-loop + const res = await args.deps.axiosMiddleware('api/v2/read-plugin', { + plugin: { + id: pluginId, + source: pluginSource, + }, + }); + + classicPlugin = args.deps.requireFromString(res.pluginRaw, absolutePath); + pluginSrcStr = res.pluginRaw; + } + + if (type === 'filter' && classicPlugin.details().Operation !== 'Filter') { + throw new Error( + `${'This plugin is meant for classic plugins that have ' + + 'Operation: Filter. This classic plugin has Operation: '}${classicPlugin.details().Operation}` + + '. Please use the Run Classic Transcode Flow Plugin plugin instead.' + , + ); + } + + if (type !== 'filter' && classicPlugin.details().Operation === 'Filter') { + throw new Error( + `${'This plugin is meant for classic plugins that have ' + + 'Operation: Transcode. This classic plugin has Operation: '}${classicPlugin.details().Operation}` + + 'Please use the Run Classic Filter Flow Plugin plugin instead.' + , + ); + } + + if (Array.isArray(classicPlugin.dependencies)) { + if (args.installClassicPluginDeps) { + args.jobLog(`Installing dependencies for ${pluginSourceId}`); + await args.installClassicPluginDeps(classicPlugin.dependencies); + } else { + args.jobLog(`Not installing dependencies for ${pluginSourceId}, please update Tdarr`); + } + } else { + args.jobLog(`No depedencies to install for ${pluginSourceId}`); + } + + const container = getContainer(args.inputFileObj._id); + const cacheFilePath = `${getPluginWorkDir(args)}/${getFileName(args.inputFileObj._id)}.${container}`; + + const scanTypes = getScanTypes([pluginSrcStr]); + + const pluginInputFileObj = await args.deps.axiosMiddleware('api/v2/scan-individual-file', { + file: { + _id: args.inputFileObj._id, + file: args.inputFileObj.file, + DB: args.inputFileObj.DB, + footprintId: args.inputFileObj.footprintId, + }, + scanTypes, + }); + + const originalLibraryFile = await args.deps.axiosMiddleware('api/v2/scan-individual-file', { + file: { + _id: args.originalLibraryFile._id, + file: args.originalLibraryFile.file, + DB: args.originalLibraryFile.DB, + footprintId: args.originalLibraryFile.footprintId, + }, + scanTypes, + }); + + const otherArguments = { + handbrakePath: args.handbrakePath, + ffmpegPath: args.ffmpegPath, + mkvpropeditPath: args.mkvpropeditPath, + originalLibraryFile, + nodeHardwareType: args.nodeHardwareType, + pluginCycle: 0, + workerType: args.workerType, + version: args.config.version, + platform_arch_isdocker: args.platform_arch_isdocker, + cacheFilePath, + job: args.job, + }; + + const result = await classicPlugin.plugin( + pluginInputFileObj, + args.librarySettings, + args.inputs, + otherArguments, + ); + + if (result?.file?._id && args.inputFileObj._id !== result.file._id) { + // eslint-disable-next-line no-param-reassign + args.inputFileObj._id = result.file._id; + // eslint-disable-next-line no-param-reassign + args.inputFileObj.file = result.file.file; + args.jobLog(`File ID changed from ${args.inputFileObj._id} to ${result.file._id}`); + } + + return { + result, + cacheFilePath, + absolutePath, + }; +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/cliParsers.ts b/FlowPluginsTs/FlowHelpers/1.0.0/cliParsers.ts new file mode 100644 index 000000000..8e79cc66a --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/cliParsers.ts @@ -0,0 +1,223 @@ +const handbrakeParser = ({ + str, +}: + { + str: string + }): number => { + if (typeof str !== 'string') { + return 0; + } + + let percentage = 0; + const numbers = '0123456789'; + const n = str.indexOf('%'); + + if ( + str.length >= 6 + && str.indexOf('%') >= 6 + && numbers.includes(str.charAt(n - 5)) + ) { + let output: string = str.substring(n - 6, n + 1); + const outputArr: string[] = output.split(''); + outputArr.splice(outputArr.length - 1, 1); + output = outputArr.join(''); + + const outputNum = Number(output); + if (outputNum > 0) { + percentage = outputNum; + } + } + + return percentage; +}; + +// frame= 889 fps=106 q=26.0 Lsize= 25526kB time=00:00:35.69 bitrate=5858.3kbits/s speed=4.25x +const getFFmpegVar = ({ + str, + variable, +}: { + str: string, variable: string +}): string => { + if (typeof str !== 'string') { + return ''; + } + + const idx = str.indexOf(variable); + + let out = ''; + let initSpacesEnded = false; + + if (idx >= 0) { + const startIdx = idx + variable.length + 1; + for (let i = startIdx; i < str.length; i += 1) { + if (initSpacesEnded === true && str[i] === ' ') { + break; + } else if (initSpacesEnded === false && str[i] !== ' ') { + initSpacesEnded = true; + } + + if (initSpacesEnded === true && str[i] !== ' ') { + out += str[i]; + } + } + } + + return out; +}; + +const getFFmpegPercentage = ({ + time, + f, + fc, + vf, + d, +}: { + time: number, + f: string, fc: number, vf: number, d: number +}): number => { + let frameCount01: number = fc; + let VideoFrameRate: number = vf; + let Duration: number = d; + + let perc = 0; + + const frame: number = parseInt(f, 10); + frameCount01 = Math.ceil(frameCount01); + VideoFrameRate = Math.ceil(VideoFrameRate); + Duration = Math.ceil(Duration); + + if (frame > 0) { + if (frameCount01 > 0) { + perc = ((frame / frameCount01) * 100); + } else if (VideoFrameRate > 0 && Duration > 0) { + perc = ((frame / (VideoFrameRate * Duration)) * 100); + } else { + perc = (frame); + } + } else if (time > 0 && Duration > 0) { + perc = ((time / Duration) * 100); + } + + const percString = perc.toFixed(2); + + // eslint-disable-next-line no-restricted-globals + if (isNaN(perc)) { + return 0.00; + } + + return parseFloat(percString); +}; + +const ffmpegParser = ({ + str, + frameCount, + + videoFrameRate, + ffprobeDuration, + metaDuration, +}: { + str: string, + frameCount: number, + + videoFrameRate: number | undefined, + ffprobeDuration: string | undefined, + metaDuration: number | undefined, +}): number => { + if (typeof str !== 'string') { + return 0; + } + + let percentage = 0; + if (str.length >= 6) { + const frame = getFFmpegVar({ + str, + variable: 'frame', + }); + + let time = 0; + + // get time + const timeStr = getFFmpegVar({ + str, + variable: 'time', + }); + + if (timeStr) { + const timeArr = timeStr.split(':'); + if (timeArr.length === 3) { + const hours = parseInt(timeArr[0], 10); + const minutes = parseInt(timeArr[1], 10); + const seconds = parseInt(timeArr[2], 10); + time = (hours * 3600) + (minutes * 60) + seconds; + } + } + + const frameRate = videoFrameRate || 0; + let duration = 0; + + if ( + ffprobeDuration + && parseFloat(ffprobeDuration) > 0 + ) { + duration = parseFloat(ffprobeDuration); + } else if (metaDuration) { + duration = metaDuration; + } + + const per = getFFmpegPercentage( + { + time, + f: frame, + fc: frameCount, + vf: frameRate, + d: duration, + }, + ); + + const outputNum = Number(per); + if (outputNum > 0) { + percentage = outputNum; + } + } + + return percentage; +}; + +const editreadyParser = ({ str }:{str: string}): number => { + if (typeof str !== 'string') { + return 0; + } + let percentage = 0; + + // const ex = 'STATUS: {"progress": "0.0000000"}'; + + if (str.includes('STATUS:')) { + const parts = str.split('STATUS:'); + + if (parts[1]) { + try { + const json = JSON.parse(parts[1]); + const progress = parseFloat(json.progress); + const percStr = (progress * 100).toFixed(2); + percentage = parseFloat(percStr); + } catch (err) { + // err + } + } + } + + // eslint-disable-next-line no-restricted-globals + if (isNaN(percentage)) { + return 0.00; + } + + return percentage; +}; + +export { + handbrakeParser, + ffmpegParser, + getFFmpegPercentage, + getFFmpegVar, + editreadyParser, +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/cliUtils.ts b/FlowPluginsTs/FlowHelpers/1.0.0/cliUtils.ts new file mode 100644 index 000000000..ef5f30271 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/cliUtils.ts @@ -0,0 +1,301 @@ +import { editreadyParser, ffmpegParser, handbrakeParser } from './cliParsers'; +import { Ilog, IupdateWorker } from './interfaces/interfaces'; +import { IFileObject, Istreams } from './interfaces/synced/IFileObject'; + +const fs = require('fs'); + +const fancyTimeFormat = (time: number) => { + // Hours, minutes and seconds + + // eslint-disable-next-line no-bitwise + const hrs = ~~(time / 3600); + // eslint-disable-next-line no-bitwise + const mins = ~~((time % 3600) / 60); + // eslint-disable-next-line no-bitwise + const secs = ~~time % 60; + + // Output like "1:01" or "4:03:59" or "123:03:59" + let ret = ''; + + // if (hrs > 0) { + ret += `${hrs}:${mins < 10 ? '0' : ''}`; + // } + + ret += `${mins}:${secs < 10 ? '0' : ''}`; + ret += `${secs}`; + return ret; +}; + +// frame= 889 fps=106 q=26.0 Lsize= 25526kB time=00:00:35.69 bitrate=5858.3kbits/s speed=4.25x +export const getFFmpegVar = ({ + str, + variable, +}: { + str: string, variable: string +}): string => { + if (typeof str !== 'string') { + return ''; + } + + const idx = str.indexOf(variable); + + let out = ''; + let initSpacesEnded = false; + + if (idx >= 0) { + const startIdx = idx + variable.length + 1; + for (let i = startIdx; i < str.length; i += 1) { + if (initSpacesEnded === true && str[i] === ' ') { + break; + } else if (initSpacesEnded === false && str[i] !== ' ') { + initSpacesEnded = true; + } + + if (initSpacesEnded === true && str[i] !== ' ') { + out += str[i]; + } + } + } + + return out; +}; + +interface Iconfig { + cli: string, + spawnArgs: string[], + spawnOpts: Record, + jobLog: Ilog, + outputFilePath: string, + updateWorker: IupdateWorker, + logFullCliOutput: boolean, + inputFileObj: IFileObject, +} + +class CLI { + // @ts-expect-error init + config: Iconfig = {}; + + progAVG: number[] = []; + + oldOutSize = 0; + + oldEstSize = 0; + + oldProgress = 0; + + lastProgCheck = 0; + + constructor(config: Iconfig) { + this.config = config; + } + + updateETA = (perc: number): void => { + if (perc > 0) { + if (this.lastProgCheck === 0) { + this.lastProgCheck = new Date().getTime(); + this.oldProgress = perc; + } else if (perc !== this.oldProgress) { + const n = new Date().getTime(); + const secsSinceLastCheck = (n - this.lastProgCheck) / 1000; + + if (secsSinceLastCheck > 1) { + // eta total + let eta = Math.round( + (100 / (perc - this.oldProgress)) * secsSinceLastCheck, + ); + + // eta remaining + eta *= ((100 - perc) / 100); + this.progAVG.push(eta); + + // let values = [2, 56, 3, 41, 0, 4, 100, 23]; + const sum = this.progAVG.reduce( + // eslint-disable-next-line + (previous, current) => (current += previous), + ); + const avg = sum / this.progAVG.length; + + // est size + let estSize = 0; + + let outputFileSizeInGbytes; + + try { + if (fs.existsSync(this.config.outputFilePath)) { + let singleFileSize = fs.statSync(this.config.outputFilePath); + singleFileSize = singleFileSize.size; + outputFileSizeInGbytes = singleFileSize / (1024 * 1024 * 1024); + + if (outputFileSizeInGbytes !== this.oldOutSize) { + this.oldOutSize = outputFileSizeInGbytes; + estSize = outputFileSizeInGbytes + + ((100 - perc) / perc) * outputFileSizeInGbytes; + this.oldEstSize = estSize; + } + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + + this.config.updateWorker({ + ETA: fancyTimeFormat(avg), + outputFileSizeInGbytes: outputFileSizeInGbytes === undefined ? 0 : outputFileSizeInGbytes, + estSize: this.oldEstSize === undefined ? 0 : this.oldEstSize, + }); + + if (this.progAVG.length > 30) { + this.progAVG.splice(0, 1); + } + + this.lastProgCheck = n; + this.oldProgress = perc; + } + } + } + }; + + parseOutput = (data: string): void => { + const str = `${data}`; + // + if (this.config.logFullCliOutput === true) { + this.config.jobLog(str); + } + + if (this.config.cli.toLowerCase().includes('handbrake')) { + const percentage = handbrakeParser({ + str, + }); + if (percentage > 0) { + this.updateETA(percentage); + this.config.updateWorker({ + percentage, + }); + } + } else if (this.config.cli.toLowerCase().includes('ffmpeg')) { + const n = str.indexOf('fps'); + const shouldUpdate = str.length >= 6 && n >= 6; + + const fps = parseInt(getFFmpegVar({ + str, + variable: 'fps', + }), 10); + + let frameCount = 0; + + try { + // @ts-expect-error type + const frameCountTmp = this.config.inputFileObj.ffProbeData?.streams + .filter((row: Istreams) => row.codec_type === 'video')[0].nb_frames; + + if (frameCountTmp + // @ts-expect-error type + && !isNaN(frameCountTmp)) { // eslint-disable-line no-restricted-globals + // @ts-expect-error type + frameCount = frameCountTmp; + } + } catch (err) { + // err + } + + const percentage = ffmpegParser({ + str, + frameCount, + videoFrameRate: this.config.inputFileObj?.meta?.VideoFrameRate, + ffprobeDuration: this.config.inputFileObj.ffProbeData?.format?.duration, + metaDuration: this.config.inputFileObj?.meta?.Duration, + }); + + if (shouldUpdate === true && fps > 0) { + this.config.updateWorker({ + fps, + }); + } + + if (percentage > 0) { + this.updateETA(percentage); + this.config.updateWorker({ + percentage, + }); + } + } else if (this.config.cli.toLowerCase().includes('editready')) { + const percentage = editreadyParser({ + str, + }); + if (percentage > 0) { + this.updateETA(percentage); + this.config.updateWorker({ + percentage, + }); + } + } + }; + + runCli = async (): Promise<{ + cliExitCode: number, + errorLogFull: string[], + }> => { + const childProcess = require('child_process'); + + const errorLogFull: string[] = []; + + // eslint-disable-next-line no-console + this.config.jobLog(`Running ${this.config.cli} ${this.config.spawnArgs.join(' ')}`); + const cliExitCode: number = await new Promise((resolve) => { + try { + const opts = this.config.spawnOpts || {}; + const spawnArgs = this.config.spawnArgs.map((row) => row.trim()).filter((row) => row !== ''); + const thread = childProcess.spawn(this.config.cli, spawnArgs, opts); + + thread.stdout.on('data', (data: string) => { + // eslint-disable-next-line no-console + // console.log(data.toString()); + errorLogFull.push(data.toString()); + this.parseOutput(data); + }); + + thread.stderr.on('data', (data: string) => { + // eslint-disable-next-line no-console + // console.log(data.toString()); + errorLogFull.push(data.toString()); + this.parseOutput(data); + }); + + thread.on('error', () => { + // catches execution error (bad file) + // eslint-disable-next-line no-console + console.log(1, `Error executing binary: ${this.config.cli}`); + resolve(1); + }); + + // thread.stdout.pipe(process.stdout); + // thread.stderr.pipe(process.stderr); + thread.on('close', (code: number) => { + if (code !== 0) { + // eslint-disable-next-line no-console + console.log(code, 'CLI error'); + } + resolve(code); + }); + } catch (err) { + // catches execution error (no file) + // eslint-disable-next-line no-console + console.log(1, `Error executing binary: ${this.config.cli}`); + resolve(1); + } + }); + + if (!this.config.logFullCliOutput) { + this.config.jobLog(errorLogFull.slice(-1000).join('')); + } + + return { + cliExitCode, + errorLogFull, + }; + }; +} + +export { + CLI, +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/fileUtils.ts b/FlowPluginsTs/FlowHelpers/1.0.0/fileUtils.ts new file mode 100644 index 000000000..018f9a768 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/fileUtils.ts @@ -0,0 +1,153 @@ +import { promises as fs } from 'fs'; +import { IpluginInputArgs } from './interfaces/interfaces'; + +export const getContainer = (filePath: string): string => { + const parts = filePath.split('.'); + return parts[parts.length - 1]; +}; + +export const getFileName = (filePath: string): string => { + const parts = filePath.split('/'); + const fileNameAndContainer = parts[parts.length - 1]; + const parts2 = fileNameAndContainer.split('.'); + parts2.pop(); + return parts2.join('.'); +}; + +export const getFileAbosluteDir = (filePath: string):string => { + const parts = filePath.split('/'); + parts.pop(); + return parts.join('/'); +}; + +export const getFfType = (codecType: string): string => (codecType === 'video' ? 'v' : 'a'); + +export const getSubStem = ({ + inputPathStem, + inputPath, +}: { + inputPathStem: string, + inputPath: string, +}): string => { + const subStem = inputPath.substring(inputPathStem.length); + const parts = subStem.split('/'); + parts.pop(); + + return parts.join('/'); +}; + +const getFileSize = async (file:string):Promise => { + const stats = await fs.stat(file); + const { size } = stats; + return size; +}; + +export const moveFileAndValidate = async ({ + inputPath, + outputPath, + args, +}: { + inputPath: string, + outputPath: string, + args: IpluginInputArgs +}):Promise => { + const inputSize = await getFileSize(inputPath); + + args.jobLog(`Attempt 1: Moving file from ${inputPath} to ${outputPath}`); + + const res1 = await new Promise((resolve) => { + args.deps.gracefulfs.rename(inputPath, outputPath, (err: Error) => { + if (err) { + args.jobLog(`Failed to move file from ${inputPath} to ${outputPath}`); + args.jobLog(JSON.stringify(err)); + resolve(false); + } else { + resolve(true); + } + }); + }); + + let outputSize = 0; + try { + outputSize = await getFileSize(outputPath); + } catch (err) { + args.jobLog(JSON.stringify(err)); + } + + if (!res1 || inputSize !== outputSize) { + args.jobLog(`Attempt 1 failed: Moving file from ${inputPath} to ${outputPath}`); + args.jobLog(`Attempt 2: Moving file from ${inputPath} to ${outputPath}`); + + const res2 = await new Promise((resolve) => { + args.deps.mvdir(inputPath, outputPath, { overwrite: true }) + .then(() => { + resolve(true); + }).catch((err: Error) => { + args.jobLog(`Failed to move file from ${inputPath} to ${outputPath}`); + args.jobLog(JSON.stringify(err)); + resolve(false); + }); + }); + + outputSize = await getFileSize(outputPath); + + if (!res2 || inputSize !== outputSize) { + const errMessage = `Failed to move file from ${inputPath} to ${outputPath}, check errors above`; + args.jobLog(errMessage); + throw new Error(errMessage); + } + } +}; + +export const getPluginWorkDir = (args: IpluginInputArgs):string => { + const pluginWorkDir = `${args.workDir}/${new Date().getTime()}`; + args.deps.fsextra.ensureDirSync(pluginWorkDir); + return pluginWorkDir; +}; + +export interface IscanTypes { + mediaInfoScan: boolean, + exifToolScan: boolean, + closedCaptionScan: boolean, + [index: string]: boolean, +} + +export const getScanTypes = (pluginsTextRaw: string[]): IscanTypes => { + const scanTypes: IscanTypes = { + exifToolScan: true, + mediaInfoScan: false, + closedCaptionScan: false, + }; + const scannerTypes = [ + // needed for frame and duration data for ffmpeg + // { + // type: 'exifToolScan', + // terms: [ + // 'meta', + // ], + // }, + { + type: 'mediaInfoScan', + terms: [ + 'mediaInfo', + ], + }, + { + type: 'closedCaptionScan', + terms: [ + 'hasClosedCaptions', + ], + }, + ]; + + const text = pluginsTextRaw.join(''); + + scannerTypes.forEach((scanner) => { + scanner.terms.forEach((term) => { + if (text.includes(term)) { + scanTypes[scanner.type] = true; + } + }); + }); + return scanTypes; +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.test.ts b/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.test.ts new file mode 100644 index 000000000..5f5a90bd4 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.test.ts @@ -0,0 +1,25 @@ +import { getEncoder } from './hardwareUtils'; + +const run = async () => { + const encoderProperties = await getEncoder({ + targetCodec: 'h264', + hardwareEncoding: true, + hardwareType: 'auto', + // @ts-expect-error type + args: { + workerType: 'transcodegpu', + ffmpegPath: 'ffmpeg', + jobLog: (t:string) => { + // eslint-disable-next-line no-console + console.log(t); + }, + }, + }); + + // eslint-disable-next-line no-console + console.log({ + encoderProperties, + }); +}; + +void run(); diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.ts b/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.ts new file mode 100644 index 000000000..5052cb441 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/hardwareUtils.ts @@ -0,0 +1,409 @@ +import os from 'os'; +import { IpluginInputArgs } from './interfaces/interfaces'; + +export const hasEncoder = async ({ + ffmpegPath, + encoder, + inputArgs, + outputArgs, + filter, + args, +}: { + ffmpegPath: string, + encoder: string, + inputArgs: string[], + outputArgs: string[], + filter: string, + args: IpluginInputArgs, +}): Promise => { + const { spawn } = require('child_process'); + let isEnabled = false; + try { + const commandArr = [ + ...inputArgs, + '-f', + 'lavfi', + '-i', + 'color=c=black:s=256x256:d=1:r=30', + ...(filter ? filter.split(' ') : []), + '-c:v', + encoder, + ...outputArgs, + '-f', + 'null', + '/dev/null', + ]; + + args.jobLog(`Checking for encoder ${encoder} with command:`); + args.jobLog(`${ffmpegPath} ${commandArr.join(' ')}`); + + isEnabled = await new Promise((resolve) => { + const error = () => { + resolve(false); + }; + let stderr = ''; + + try { + const thread = spawn(ffmpegPath, commandArr); + thread.on('error', () => { + // catches execution error (bad file) + error(); + }); + + thread.stdout.on('data', (data: string) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + stderr += data; + }); + + thread.stderr.on('data', (data: string) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + stderr += data; + }); + + thread.on('close', (code: number) => { + if (code !== 0) { + error(); + } else { + resolve(true); + } + }); + } catch (err) { + // catches execution error (no file) + error(); + } + }); + + args.jobLog(`Encoder ${encoder} is ${isEnabled ? 'enabled' : 'disabled'}`); + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + + return isEnabled; +}; + +interface IgpuEncoder { + encoder: string, + enabled: boolean, + + inputArgs: string[], + outputArgs: string[], + filter: string, +} + +// credit to UNCode101 for this +export const getBestNvencDevice = ({ + args, + nvencDevice, +}: { + args: IpluginInputArgs + nvencDevice: IgpuEncoder, +}): IgpuEncoder => { + 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: string[] = []; + // 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(); + const gpu_namesArr = 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_namesArr[0].includes('nvidia-smi')) { + gpu_count = gpu_namesArr.length; + } + } catch (error) { + args.jobLog('Error in reading nvidia-smi output! \n'); + } + + if (gpu_count > 0) { + for (let gpui = 0; gpui < gpu_count; gpui += 1) { + // Check if GPU # is in GPUs to exclude + if (gpus_to_exclude.includes(String(gpui))) { + args.jobLog(`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") { + args.jobLog(`GPU ${gpui} : Utilization ${result_util}%\n`); + + if (result_util < lowest_gpu_util) { + gpu_num = gpui; + lowest_gpu_util = result_util; + } + } + } catch (error) { + args.jobLog(`Error in reading GPU ${gpui} Utilization\nError: ${error}\n`); + } + } + } + } + if (gpu_num >= 0) { + // eslint-disable-next-line no-param-reassign + nvencDevice.inputArgs.push('-hwaccel_device', `${gpu_num}`); + // eslint-disable-next-line no-param-reassign + nvencDevice.outputArgs.push('-gpu', `${gpu_num}`); + } + + return nvencDevice; +}; + +const encoderFilter = (encoder: string, targetCodec: string) => { + if (targetCodec === 'hevc' && (encoder.includes('hevc') || encoder.includes('h265'))) { + return true; + } if (targetCodec === 'h264' && encoder.includes('h264')) { + return true; + } if (targetCodec === 'av1' && encoder.includes('av1')) { + return true; + } + + return false; +}; + +export interface IgetEncoder { + encoder: string, + inputArgs: string[], + outputArgs: string[], + isGpu: boolean, + enabledDevices: IgpuEncoder[], +} + +export const getEncoder = async ({ + targetCodec, + hardwareEncoding, + hardwareType, + args, +}: { + targetCodec: string, + hardwareEncoding: boolean, + hardwareType: string, + args: IpluginInputArgs, +}): Promise => { + if ( + args.workerType + && args.workerType.includes('gpu') + && hardwareEncoding && (['hevc', 'h264', 'av1'].includes(targetCodec))) { + const gpuEncoders: IgpuEncoder[] = [ + { + encoder: 'hevc_nvenc', + enabled: false, + inputArgs: [ + '-hwaccel', + 'cuda', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'hevc_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'hevc_qsv', + enabled: false, + inputArgs: [ + '-hwaccel', + 'qsv', + ], + outputArgs: [ + ...(os.platform() === 'win32' ? ['-load_plugin', 'hevc_hw'] : []), + ], + filter: '', + }, + { + encoder: 'hevc_vaapi', + inputArgs: [ + '-hwaccel', + 'vaapi', + '-hwaccel_device', + '/dev/dri/renderD128', + '-hwaccel_output_format', + 'vaapi', + ], + outputArgs: [], + enabled: false, + filter: '-vf format=nv12,hwupload', + }, + { + encoder: 'hevc_videotoolbox', + enabled: false, + inputArgs: [ + '-hwaccel', + 'videotoolbox', + ], + outputArgs: [], + filter: '', + }, + + // h264 + { + encoder: 'h264_nvenc', + enabled: false, + inputArgs: [ + '-hwaccel', + 'cuda', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_qsv', + enabled: false, + inputArgs: [ + '-hwaccel', + 'qsv', + ], + outputArgs: [], + filter: '', + }, + { + encoder: 'h264_videotoolbox', + enabled: false, + inputArgs: [ + '-hwaccel', + 'videotoolbox', + ], + outputArgs: [], + filter: '', + }, + + // av1 + { + encoder: 'av1_nvenc', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_amf', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_qsv', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + { + encoder: 'av1_vaapi', + enabled: false, + inputArgs: [], + outputArgs: [], + filter: '', + }, + ]; + + const filteredGpuEncoders = gpuEncoders.filter((device) => encoderFilter(device.encoder, targetCodec)); + + if (hardwareEncoding && hardwareType !== 'auto') { + const idx = filteredGpuEncoders.findIndex((device) => device.encoder.includes(hardwareType)); + + if (idx === -1) { + throw new Error(`Could not find encoder ${targetCodec} for hardware ${hardwareType}`); + } + + return { + ...filteredGpuEncoders[idx], + isGpu: true, + enabledDevices: [], + }; + } + + args.jobLog(JSON.stringify({ filteredGpuEncoders })); + + // 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: args.ffmpegPath, + encoder: gpuEncoder.encoder, + inputArgs: gpuEncoder.inputArgs, + outputArgs: gpuEncoder.outputArgs, + filter: gpuEncoder.filter, + args, + }); + } + + const enabledDevices = filteredGpuEncoders.filter((device) => device.enabled === true); + + args.jobLog(JSON.stringify({ enabledDevices })); + + if (enabledDevices.length > 0) { + if (enabledDevices[0].encoder.includes('nvenc')) { + const res = getBestNvencDevice({ + args, + nvencDevice: enabledDevices[0], + }); + + return { + ...res, + isGpu: true, + enabledDevices, + }; + } + return { + encoder: enabledDevices[0].encoder, + inputArgs: enabledDevices[0].inputArgs, + outputArgs: enabledDevices[0].outputArgs, + isGpu: true, + enabledDevices, + }; + } + } + + if (targetCodec === 'hevc') { + return { + encoder: 'libx265', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }; + } if (targetCodec === 'h264') { + return { + encoder: 'libx264', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }; + } if (targetCodec === 'av1') { + return { + encoder: 'libsvtav1', + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }; + } + + return { + encoder: targetCodec, + inputArgs: [], + outputArgs: [], + isGpu: false, + enabledDevices: [], + }; +}; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/interfaces.ts b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/interfaces.ts new file mode 100644 index 000000000..917ee09e3 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/interfaces.ts @@ -0,0 +1,158 @@ +import { IFileObject, Istreams } from './synced/IFileObject'; +import Ijob from './synced/jobInterface'; + +export interface IpluginInputUi { + type: 'dropdown' | 'text' | 'textarea' | 'directory', + options?: string[], + style?:Record, + onSelect?: { + 'hevc': { + update: { + quality: '28', + }, + } + }, +} + +export interface IpluginInputs { + name: string, + type: 'string' | 'boolean' | 'number', + defaultValue: string, + inputUI: IpluginInputUi, + tooltip: string, +} + +export interface IpluginDetails { + name: string, + nameUI?:{ + type: 'text' | 'textarea', + style?:Record, + } + description: string, + style: { + borderColor: string, + opacity?: number, + borderRadius?: number | string, + width?: number | string, + height?: number | string, + backgroundColor?: string, + }, + tags: string, + isStartPlugin: boolean, + pType: 'start' | 'onFlowError' | '', + sidebarPosition: number, + icon: string, + inputs: IpluginInputs[], + + outputs: { + number: number, + tooltip: string, + }[], + requiresVersion: string, +} + +export interface Ilog { + (text: string): void +} + +export interface IupdateWorker { + (obj: Record): void, +} + +export interface IffmpegCommandStream extends Istreams { + removed: boolean, + forceEncoding: boolean, + inputArgs: string[], + outputArgs: string[], +} + +export interface IffmpegCommand { + inputFiles: string[], + streams: IffmpegCommandStream[] + container: string, + hardwareDecoding: boolean, + shouldProcess: boolean, + overallInputArguments: string[], + overallOuputArguments: string[], +} + +export interface Ivariables { + ffmpegCommand: IffmpegCommand, + flowFailed: boolean, +} + +export interface IpluginOutputArgs { + outputNumber: number, + outputFileObj: { + _id: string, + }, + variables: Ivariables +} + +export interface IpluginInputArgs { + inputFileObj: IFileObject, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + librarySettings: any, + inputs: Record, + jobLog: Ilog, + workDir: string, + platform: string, + arch: string, + handbrakePath: string, + ffmpegPath: string, + mkvpropeditPath: string, + originalLibraryFile: IFileObject, + nodeHardwareType: string, + workerType: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + config: any, + job: Ijob, + platform_arch_isdocker: string, + variables: Ivariables, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lastSuccesfulPlugin: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lastSuccessfulRun: any, + updateWorker: IupdateWorker, + logFullCliOutput: boolean, + logOutcome: (outcome: string) => void, + deps: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fsextra: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + parseArgsStringToArgv: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + importFresh(path: string): any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + axiosMiddleware: (endpoint: string, data: Record) => Promise, + requireFromString: (pluginText: string, relativePath: string) => Record, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + upath: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + gracefulfs: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mvdir: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + axios: any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + crudTransDBN: (collection: string, mode: string, docID: string, obj: any)=> any, + configVars:{ + config:{ + serverIP: string, + serverPort: string, + } + } + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + installClassicPluginDeps: (deps: string[]) => Promise, +} + +export interface IflowTemplate { + name: string, + description: string, + tags: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + flowPlugins:any[], + // eslint-disable-next-line @typescript-eslint/no-explicit-any + flowEdges: any[], +} diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/IFileObject.ts b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/IFileObject.ts new file mode 100644 index 000000000..6c36a2ca9 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/IFileObject.ts @@ -0,0 +1,198 @@ +export interface IstatSync { // tlint-disable-line statSync + mtimeMs: number, + ctimeMs: number, + + ctime?: '', + mtime?: '', + atime?: '', +} + +export interface Itags { + language?: string, + title?: string, + [key:string]: string | undefined, +} +export interface Istreams { + codec_name: string; + codec_type: string, + bit_rate?: number, + channels?: number, + tags?: Itags, + avg_frame_rate?: string, + nb_frames?: string, + + duration?: number; + width?: number, + height?: number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types + [index: string]: any +} + +export interface Iformat { + 'filename'?: string, + 'nb_streams'?: number, + 'nb_programs'?: number, + 'format_name'?: string, + 'format_long_name'?: string, + 'start_time'?: string, + 'duration'?: string, + 'size'?: string, + 'bit_rate'?: string, + 'probe_score'?: number, + [key:string]: string | number | undefined + } + +export interface IffProbeData { + streams?: Istreams[] + format?: Iformat +} + +export interface Imeta { + TrackDuration?: number, + MediaDuration?: number, + 'SourceFile'?: string, + 'errors'?: [], + 'Duration'?: number, + 'ExifToolVersion'?: number, + 'FileName'?: string, + 'Directory'?: string, + 'FileSize'?: string, + 'FileModifyDate'?: { + 'year'?: number, + 'month'?: number, + 'day'?: number, + 'hour'?: number, + 'minute'?: number, + 'second'?: number, + 'millisecond'?: number, + 'tzoffsetMinutes'?: number, + 'rawValue'?: string, + }, + 'FileAccessDate'?: { + 'year'?: number, + 'month'?: number, + 'day'?: number, + 'hour'?: number, + 'minute'?: number, + 'second'?: number, + 'millisecond'?: number, + 'tzoffsetMinutes'?: number, + 'rawValue'?: string, + }, + 'FileCreateDate'?: { + 'year'?: number, + 'month'?: number, + 'day'?: number, + 'hour'?: number, + 'minute'?: number, + 'second'?: number, + 'millisecond'?: number, + 'tzoffsetMinutes'?: number, + 'rawValue'?: string, + }, + 'FilePermissions'?: string, + 'FileType'?: string, + 'FileTypeExtension'?: string, + 'MIMEType'?: string, + 'EBMLVersion'?: 1, + 'EBMLReadVersion'?: 1, + 'DocType'?: string, + 'DocTypeVersion'?: 4, + 'DocTypeReadVersion'?: 2, + 'TimecodeScale'?: string, + 'MuxingApp'?: string, + 'WritingApp'?: string, + 'VideoFrameRate'?: number, + 'ImageWidth'?: number, + 'ImageHeight'?: number, + 'TrackNumber'?: number, + 'TrackLanguage'?: string, + 'CodecID'?: string, + 'TrackType'?: string, + 'AudioChannels'?: number, + 'AudioSampleRate'?: number, + 'AudioBitsPerSample'?: number, + 'TagName'?: 'DURATION', + 'TagString'?: string, + 'ImageSize'?: string, + 'Megapixels'?: number, +} + +export interface ImediaInfo { + track?: [{ + '@type': string, + 'UniqueID': string, + 'VideoCount': string, + 'AudioCount': string, + 'Format': string, + 'Format_Version': string, + 'FileSize': string, + 'Duration': string, + 'OverallBitRate': string, + 'FrameRate': string, + 'FrameCount': string, + 'IsStreamable': string, + 'Encoded_Application': string, + 'Encoded_Library': string, + BitRate: number, + 'extra': { + 'ErrorDetectionType': string, + } + }], +} + +export interface IFileObjectMin { + _id: string, + file: string, + DB: string, + footprintId: string, +} + +type IbaseStatus = '' | 'Hold' | 'Queued' +export type IHealthCheck = IbaseStatus | 'Success' | 'Error' | 'Cancelled' +export type ITranscodeDecisionMaker = IbaseStatus | 'Transcode success' + | 'Transcode error' | 'Transcode cancelled' | 'Not required' + +export interface IFileObjectStripped extends IFileObjectMin { + container: string, + scannerReads: { + ffProbeRead: string, + } + createdAt: number, + lastPluginDetails: string, + bit_rate: number, + statSync: IstatSync, // tlint-disable-line statSync + file_size: number, + ffProbeData: IffProbeData, + hasClosedCaptions: boolean, + bumped: boolean, + HealthCheck: IHealthCheck, + TranscodeDecisionMaker: ITranscodeDecisionMaker, + holdUntil: number, + fileMedium: string, + video_codec_name: string, + audio_codec_name: string, + video_resolution: string, + + lastHealthCheckDate: number, + lastTranscodeDate: number, + history: string, + oldSize: number, + newSize: number, + videoStreamIndex: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types + [index: string]: any, +} + +export interface IFileObject extends IFileObjectStripped { + scannerReads: { + ffProbeRead: string, + exiftoolRead: string, + mediaInfoRead: string, + closedCaptionRead: string, + } + meta?: Imeta, + mediaInfo?: ImediaInfo, + // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types + [index: string]: any, +} diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/jobInterface.ts b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/jobInterface.ts new file mode 100644 index 000000000..c3e10fa68 --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/interfaces/synced/jobInterface.ts @@ -0,0 +1,10 @@ +interface Ijob { + version: string, + footprintId: string, + jobId: string, + start: number, + type: string, + fileId: string +} + +export default Ijob; diff --git a/FlowPluginsTs/FlowHelpers/1.0.0/normJoinPath.ts b/FlowPluginsTs/FlowHelpers/1.0.0/normJoinPath.ts new file mode 100644 index 000000000..97e0f0fdf --- /dev/null +++ b/FlowPluginsTs/FlowHelpers/1.0.0/normJoinPath.ts @@ -0,0 +1,28 @@ +const formatWindowsRootFolder = (path: string): string => { + // Remove '.' from end of Windows root folder mapping e.g. 'E:.' + if ( + path.length === 3 + && path.charAt(1) === ':' + && path.charAt(2) === '.' + ) { + // eslint-disable-next-line no-param-reassign + path = path.slice(0, -1); + } + + return path; +}; + +const normJoinPath = ({ + upath, + paths, +}:{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + upath: any, + paths: string[] +}):string => { + let path = upath.joinSafe(...paths); + path = formatWindowsRootFolder(path); + return path; +}; + +export default normJoinPath; diff --git a/README.md b/README.md index 8621dcb27..d347c526c 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,15 @@ Check plugins using some extra custom rules: Run tests: `npm run test` + + +# Steps to write a Tdarr Flow plugin: + +1. Clone this repo +2. Set env variable `pluginsDir` to the location of the plugins repo and run Tdarr Server and Node. E.g. `export pluginsDir=C:/Tdarr_Plugins` +3. Browse the typescript plugins here https://github.com/HaveAGitGat/Tdarr_Plugins/tree/master/FlowPluginsTs/CommunityFlowPlugins and make edits locally or create a new one locally: +4. Make sure typescript is intalled with `npm i -g typescript` then run `tsc` to compile the changes. +5. Refresh the browser and Tdarr will pick up the changes + + + diff --git a/examples/Tdarr_Plugin_a9he_New_file_size_check.js b/examples/Tdarr_Plugin_a9he_New_file_size_check.js index 7b1f1f95b..b5531716c 100644 --- a/examples/Tdarr_Plugin_a9he_New_file_size_check.js +++ b/examples/Tdarr_Plugin_a9he_New_file_size_check.js @@ -12,7 +12,7 @@ const details = () => ({ const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object at some point in the function else plugin will fail. const response = { diff --git a/examples/Tdarr_Plugin_f001_Filter_Example.js b/examples/Tdarr_Plugin_f001_Filter_Example.js index 400e1f69f..d6be88258 100644 --- a/examples/Tdarr_Plugin_f001_Filter_Example.js +++ b/examples/Tdarr_Plugin_f001_Filter_Example.js @@ -10,10 +10,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); // Must return this object at some point in the function else plugin will fail. diff --git a/examples/Tdarr_Plugin_f002_Filter_Example.js b/examples/Tdarr_Plugin_f002_Filter_Example.js index ab6077b23..5a1961bbf 100644 --- a/examples/Tdarr_Plugin_f002_Filter_Example.js +++ b/examples/Tdarr_Plugin_f002_Filter_Example.js @@ -10,10 +10,10 @@ const details = () => ({ Inputs: [], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); - // eslint-disable-next-line no-unused-vars,no-param-reassign + // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign inputs = lib.loadDefaultValues(inputs, details); const response = { processFile: true, diff --git a/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js b/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js index 7c1979585..492fabc74 100644 --- a/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js +++ b/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js @@ -44,13 +44,15 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); // load default plugin inputs + // eslint-disable-next-line @typescript-eslint/no-unused-vars inputs = lib.loadDefaultValues(inputs, details); // Only 'require' dependencies within this function or other functions. Do not require in the top scope. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const importFresh = require('import-fresh'); console.log( diff --git a/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js b/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js index 51687cebb..76efd68fe 100644 --- a/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js +++ b/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js @@ -69,13 +69,14 @@ const details = () => ({ ], }); -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { const lib = require('../methods/lib')(); // load default plugin inputs inputs = lib.loadDefaultValues(inputs, details); // Only 'require' dependencies within this function or other functions. Do not require in the top scope. + // eslint-disable-next-line @typescript-eslint/no-unused-vars const importFresh = require('import-fresh'); // Must return following object at some point in the function else plugin will fail. diff --git a/examples/force_transcode_filter_notes b/examples/force_transcode_filter_notes new file mode 100644 index 000000000..6a5bbd431 --- /dev/null +++ b/examples/force_transcode_filter_notes @@ -0,0 +1,9 @@ + // add COPYRIGHT=processed during video transcoding + // -map 0 -c copy -c:v libx265 -metadata:s:v:0 COPYRIGHT=processed + + // check COPYRIGHT=processed metadata to see if file has been transcoded or not + if(file.ffProbeData.streams[0]?.tags?.COPYRIGHT === 'processed'){ + response.infoLog += 'File has already been transcoded \n'; + response.processFile = false; + return response; + } \ No newline at end of file diff --git a/methods/library/actions/transcodeAddAudioStream.js b/methods/library/actions/transcodeAddAudioStream.js index 1ed43c4e7..171beb7c0 100644 --- a/methods/library/actions/transcodeAddAudioStream.js +++ b/methods/library/actions/transcodeAddAudioStream.js @@ -122,7 +122,7 @@ module.exports = function transcodeAddAudioStream( }; } else { return { - preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${channelCount}`, + preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${channelCount} -max_muxing_queue_size 9999`, processFile: true, note: `The required channel count ${channelCount} is lower than the highest available channel count (${highestChannelCount.channels}). Adding! \n`, }; @@ -155,7 +155,7 @@ module.exports = function transcodeAddAudioStream( }; } else { return { - preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${highestChannelCount.channels}`, + preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${highestChannelCount.channels} -max_muxing_queue_size 9999`, processFile: true, note: `The required channel count (${channelCount}) is higher than the highest channel available in specified lang tag (${highestChannelCount.channels}). Adding lower channel track. \n`, }; @@ -229,7 +229,7 @@ module.exports = function transcodeAddAudioStream( }; } else { return { - preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${channelCount}`, + preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${channelCount} -max_muxing_queue_size 9999`, processFile: true, note: `The required channel count ${channelCount} is lower than the highest available channel count (${highestChannelCount.channels}). Adding! \n`, }; @@ -260,7 +260,7 @@ module.exports = function transcodeAddAudioStream( }; } else { return { - preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${highestChannelCount.channels}`, + preset: `,-map 0:v -map 0:${highestChannelCount.index} -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 ${audioEncoder} -ac ${highestChannelCount.channels} -max_muxing_queue_size 9999`, processFile: true, note: `The required channel count (${channelCount}) is higher than the highest channel available in specified lang tag (${highestChannelCount.channels}). Adding lower channel track. \n`, }; diff --git a/methods/library/actions/transcodeStandardiseAudioCodecs.js b/methods/library/actions/transcodeStandardiseAudioCodecs.js index 012b8da28..ef0646a61 100644 --- a/methods/library/actions/transcodeStandardiseAudioCodecs.js +++ b/methods/library/actions/transcodeStandardiseAudioCodecs.js @@ -1,4 +1,4 @@ -module.exports = function transcodeStandardiseAudioCodecs(file, audioEncoder) { +module.exports = (file, audioEncoder) => { // Function required responses // preset // processFile diff --git a/methods/library/filters/filterByAge.js b/methods/library/filters/filterByAge.js index 176d9ff48..bb37f120f 100644 --- a/methods/library/filters/filterByAge.js +++ b/methods/library/filters/filterByAge.js @@ -1,4 +1,4 @@ -function filterByAge(file, ageCutOff_Seconds, type) { +const filterByAge = (file, ageCutOff_Seconds, type) => { try { const timeNow = new Date(); const dateCreated = new Date(file.statSync.birthtime); @@ -24,6 +24,6 @@ function filterByAge(file, ageCutOff_Seconds, type) { }; return response; } -} +}; module.exports = filterByAge; diff --git a/methods/library/filters/filterByBitrate.js b/methods/library/filters/filterByBitrate.js index a269b66c7..17e0797dd 100644 --- a/methods/library/filters/filterByBitrate.js +++ b/methods/library/filters/filterByBitrate.js @@ -1,4 +1,4 @@ -function filterByBitrate(file, lowerBound, upperBound) { +const filterByBitrate = (file, lowerBound, upperBound) => { try { if ( file.bit_rate >= lowerBound @@ -22,6 +22,6 @@ function filterByBitrate(file, lowerBound, upperBound) { }; return response; } -} +}; module.exports = filterByBitrate; diff --git a/methods/library/filters/filterByCodec.js b/methods/library/filters/filterByCodec.js index 3e6b1ccfb..a3ae7eb15 100644 --- a/methods/library/filters/filterByCodec.js +++ b/methods/library/filters/filterByCodec.js @@ -1,4 +1,4 @@ -function filterByCodec(file, mode, codecs) { +const filterByCodec = (file, mode, codecs) => { try { // console.log(file,mode,codecs) @@ -54,6 +54,6 @@ function filterByCodec(file, mode, codecs) { }; return response; } -} +}; module.exports = filterByCodec; diff --git a/methods/library/filters/filterByMedium.js b/methods/library/filters/filterByMedium.js index 35703f10b..05d457b8c 100644 --- a/methods/library/filters/filterByMedium.js +++ b/methods/library/filters/filterByMedium.js @@ -1,4 +1,4 @@ -function filterByMedium(file, medium) { +const filterByMedium = (file, medium) => { try { if (file.fileMedium !== medium) { const response = { @@ -19,6 +19,6 @@ function filterByMedium(file, medium) { }; return response; } -} +}; module.exports = filterByMedium; diff --git a/methods/library/filters/filterByResolution.js b/methods/library/filters/filterByResolution.js index a3a0cfc7b..c185529d0 100644 --- a/methods/library/filters/filterByResolution.js +++ b/methods/library/filters/filterByResolution.js @@ -1,4 +1,4 @@ -function filterByResolution(file, mode, resolution) { +const filterByResolution = (file, mode, resolution) => { try { if (mode === 'exclude') { if ( @@ -40,6 +40,6 @@ function filterByResolution(file, mode, resolution) { } throw new Error('Plugin error, no filter mode specified'); -} +}; module.exports = filterByResolution; diff --git a/methods/library/filters/filterBySize.js b/methods/library/filters/filterBySize.js index ebd35f073..b979b1153 100644 --- a/methods/library/filters/filterBySize.js +++ b/methods/library/filters/filterBySize.js @@ -1,4 +1,4 @@ -function filterBySize(file, lowerBound, upperBound) { +const filterBySize = (file, lowerBound, upperBound) => { try { if ( file.file_size / 1000 >= lowerBound @@ -22,6 +22,6 @@ function filterBySize(file, lowerBound, upperBound) { }; return response; } -} +}; module.exports = filterBySize; diff --git a/methods/loadDefaultValues.js b/methods/loadDefaultValues.js index 94e92a2d1..b1fc07deb 100644 --- a/methods/loadDefaultValues.js +++ b/methods/loadDefaultValues.js @@ -4,7 +4,9 @@ const loadDefaultValues = (inputs, details) => { if (!inputs) { inputs = {}; } - const defaultInputs = details().Inputs; + + const dets = details(); + const defaultInputs = dets.Inputs || dets.inputs || []; for (let i = 0; i < defaultInputs.length; i += 1) { if (typeof inputs[defaultInputs[i].name] === 'string') { inputs[defaultInputs[i].name] = inputs[defaultInputs[i].name].trim(); diff --git a/methods/utils.js b/methods/utils.js new file mode 100644 index 000000000..11dfdc12f --- /dev/null +++ b/methods/utils.js @@ -0,0 +1,23 @@ +const strHasValue = (inputsArr, value, exactMatch) => { + let contains = false; + + for (let j = 0; j < inputsArr.length; j += 1) { + try { + if ( + (exactMatch && inputsArr[j] === String(value)) + || (!exactMatch && String(value).includes(inputsArr[j]))) { + contains = true; + break; + } + } catch (err) { + // eslint-disable-next-line no-console + console.log(err); + } + } + + return contains; +}; + +module.exports = { + strHasValue, +}; diff --git a/package-lock.json b/package-lock.json index 136bce756..a82f87b71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,73 +1,96 @@ { "name": "tdarr_plugins", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { + "packages": { + "": { + "name": "tdarr_plugins", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^4.1.2", + "import-fresh": "^3.3.0", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@types/node": "^20.5.1", + "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/parser": "^4.14.1", + "chai": "^4.3.6", + "eslint": "^7.14.0", + "eslint_d": "^11.1.1", + "eslint-config-airbnb-base": "^14.2.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prefer-arrow-functions": "^3.1.4" + } + }, + "node_modules/@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "dev": true, - "requires": { + "dependencies": { "@babel/highlight": "^7.10.4" } }, - "@babel/helper-validator-identifier": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, - "@babel/highlight": { + "node_modules/@babel/highlight": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, - "requires": { + "dependencies": { "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" - }, + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "@babel/runtime": { + "node_modules/@babel/runtime": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", "dev": true, - "requires": { + "dependencies": { "regenerator-runtime": "^0.13.4" } }, - "@babel/runtime-corejs3": { + "node_modules/@babel/runtime-corejs3": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", "dev": true, - "requires": { + "dependencies": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" } }, - "@eslint/eslintrc": { + "node_modules/@eslint/eslintrc": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", "dev": true, - "requires": { + "dependencies": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", @@ -78,169 +101,476 @@ "lodash": "^4.17.19", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "@types/json5": { + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "acorn": { + "node_modules/@types/node": { + "version": "20.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", + "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", + "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", + "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz", + "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==", + "dev": true, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz", + "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/visitor-keys": "4.33.0", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz", + "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "4.33.0", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "acorn-jsx": { + "node_modules/acorn-jsx": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "ajv": { + "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "requires": { + "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "ansi-colors": { + "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "argparse": { + "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "requires": { + "dependencies": { "sprintf-js": "~1.0.2" } }, - "aria-query": { + "node_modules/aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, - "requires": { + "dependencies": { "@babel/runtime": "^7.10.2", "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" } }, - "array-includes": { + "node_modules/array-includes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1", "get-intrinsic": "^1.0.1", "is-string": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "array.prototype.flat": { + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "assertion-error": { + "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "ast-types-flow": { + "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, - "astral-regex": { + "node_modules/astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "axe-core": { + "node_modules/axe-core": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz", "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "axobject-query": { + "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", "dev": true }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "call-bind": { + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "callsites": { + "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } }, - "chai": { + "node_modules/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", "dev": true, - "requires": { + "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", @@ -248,187 +578,298 @@ "loupe": "^2.3.1", "pathval": "^1.1.1", "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" } }, - "chalk": { + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { + "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "check-error": { + "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "color-convert": { + "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "requires": { + "dependencies": { "color-name": "1.1.3" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "confusing-browser-globals": { + "node_modules/confusing-browser-globals": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", "dev": true }, - "contains-path": { + "node_modules/contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core_d": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/core_d/-/core_d-3.2.0.tgz", + "integrity": "sha512-waKkgHU2P19huhuMjCqCDWTYjxCIHoB+nnYjI7pVMUOC1giWxMNDrXkPw9QjWY+PWCFm49bD3wA/J+c7BGZ+og==", + "dev": true, + "dependencies": { + "supports-color": "^8.1.0" + } }, - "core-js-pure": { + "node_modules/core_d/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/core_d/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/core-js-pure": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.1.tgz", "integrity": "sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==", - "dev": true + "deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, - "cross-spawn": { + "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "requires": { + "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "damerau-levenshtein": { + "node_modules/damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, - "debug": { + "node_modules/debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, - "requires": { + "dependencies": { "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "deep-eql": { + "node_modules/deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, - "requires": { + "dependencies": { "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" } }, - "deep-is": { + "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "define-properties": { + "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "dev": true, - "requires": { + "dependencies": { "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" } }, - "doctrine": { + "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "requires": { + "dependencies": { "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "emoji-regex": { + "node_modules/emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "enquirer": { + "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, - "requires": { + "dependencies": { "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" } }, - "error-ex": { + "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, - "requires": { + "dependencies": { "is-arrayish": "^0.2.1" } }, - "es-abstract": { + "node_modules/es-abstract": { "version": "1.18.0-next.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, - "requires": { + "dependencies": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", @@ -441,31 +882,46 @@ "object.assign": "^4.1.1", "string.prototype.trimend": "^1.0.1", "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "es-to-primitive": { + "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, - "requires": { + "dependencies": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "eslint": { + "node_modules/eslint": { "version": "7.15.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", "dev": true, - "requires": { + "dependencies": { "@babel/code-frame": "^7.0.0", "@eslint/eslintrc": "^0.2.2", "ajv": "^6.10.0", @@ -504,129 +960,108 @@ "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint_d": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/eslint_d/-/eslint_d-11.1.1.tgz", + "integrity": "sha512-PaNWblwIa10KZUt9EObGBzrsaxB+CPtk5d99sTveXmAtQrPSmrntKpWqxN/Mwot0qnI5gCllzkPZi6tO710KUA==", + "dev": true, "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "core_d": "^3.2.0", + "eslint": "^7.3.0", + "nanolru": "^1.0.0", + "optionator": "^0.9.1" + }, + "bin": { + "eslint_d": "bin/eslint_d.js" } }, - "eslint-config-airbnb-base": { + "node_modules/eslint-config-airbnb-base": { "version": "14.2.1", "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", "dev": true, - "requires": { + "dependencies": { "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.22.1" } }, - "eslint-import-resolver-node": { + "node_modules/eslint-import-resolver-node": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, - "requires": { + "dependencies": { "debug": "^2.6.9", "resolve": "^1.13.1" - }, + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "ms": "2.0.0" } }, - "eslint-module-utils": { + "node_modules/eslint-import-resolver-node/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-module-utils": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, - "requires": { + "dependencies": { "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "ms": "2.0.0" } }, - "eslint-plugin-import": { + "node_modules/eslint-module-utils/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-import": { "version": "2.22.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, - "requires": { + "dependencies": { "array-includes": "^3.1.1", "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", @@ -641,40 +1076,47 @@ "resolve": "^1.17.0", "tsconfig-paths": "^3.9.0" }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } + "ms": "2.0.0" } }, - "eslint-plugin-jsx-a11y": { + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "dependencies": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", "dev": true, - "requires": { + "dependencies": { "@babel/runtime": "^7.11.2", "aria-query": "^4.2.2", "array-includes": "^3.1.1", @@ -687,1052 +1129,1684 @@ "jsx-ast-utils": "^3.1.0", "language-tags": "^1.0.5" }, - "dependencies": { - "emoji-regex": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", - "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==", - "dev": true - } + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7" } }, - "eslint-scope": { + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", + "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==", + "dev": true + }, + "node_modules/eslint-plugin-prefer-arrow-functions": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.1.4.tgz", + "integrity": "sha512-LSO8VibqBKqzelr+L21mEIfachavCon+1SEumCJ6U8Ze2q0pntyojmomcVwd9RZBjrP+HV6k1Osz0B3Xwdq8WA==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "requires": { + "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" } }, - "eslint-utils": { + "node_modules/eslint-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, - "requires": { + "dependencies": { "eslint-visitor-keys": "^1.1.0" }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" } }, - "eslint-visitor-keys": { + "node_modules/eslint-visitor-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "espree": { + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, - "requires": { + "dependencies": { "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "esprima": { + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } }, - "esquery": { + "node_modules/esquery": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, - "requires": { + "dependencies": { "estraverse": "^5.1.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=0.10" } }, - "esrecurse": { + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "requires": { + "dependencies": { "estraverse": "^5.2.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=4.0" } }, - "estraverse": { + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "dev": true, + "engines": { + "node": ">=4.0" + } }, - "esutils": { + "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "fast-deep-equal": { + "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "fast-json-stable-stringify": { + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "fast-levenshtein": { + "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, - "file-entry-cache": { + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", "dev": true, - "requires": { + "dependencies": { "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "find-up": { + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, - "requires": { + "dependencies": { "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "flat-cache": { + "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, - "requires": { + "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "flatted": { + "node_modules/flatted": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", "dev": true }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "function-bind": { + "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "functional-red-black-tree": { + "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "get-func-name": { + "node_modules/get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "get-intrinsic": { + "node_modules/get-intrinsic": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "glob": { + "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "glob-parent": { + "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "requires": { + "dependencies": { "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "globals": { + "node_modules/globals": { "version": "12.4.0", "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", "dev": true, - "requires": { + "dependencies": { "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "graceful-fs": { + "node_modules/globby/node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "has": { + "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, - "requires": { + "dependencies": { "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" } }, - "has-flag": { + "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "has-symbols": { + "node_modules/has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "hosted-git-info": { + "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, - "ignore": { + "node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 4" + } }, - "import-fresh": { + "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { + "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.19" + } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-arrayish": { + "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-callable": { + "node_modules/is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-core-module": { + "node_modules/is-core-module": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", "dev": true, - "requires": { + "dependencies": { "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-date-object": { + "node_modules/is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "is-glob": { + "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-negative-zero": { + "node_modules/is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } }, - "is-regex": { + "node_modules/is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, - "requires": { + "dependencies": { "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "is-string": { + "node_modules/is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "is-symbol": { + "node_modules/is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, - "requires": { + "dependencies": { "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "isarray": { + "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "js-tokens": { + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "js-yaml": { + "node_modules/js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "requires": { + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "json-stable-stringify-without-jsonify": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, - "requires": { + "dependencies": { "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" } }, - "jsx-ast-utils": { + "node_modules/jsx-ast-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz", "integrity": "sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA==", "dev": true, - "requires": { + "dependencies": { "array-includes": "^3.1.1", "object.assign": "^4.1.1" + }, + "engines": { + "node": ">=4.0" } }, - "language-subtag-registry": { + "node_modules/language-subtag-registry": { "version": "0.3.21", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==", "dev": true }, - "language-tags": { + "node_modules/language-tags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", "dev": true, - "requires": { + "dependencies": { "language-subtag-registry": "~0.3.2" } }, - "levn": { + "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "requires": { + "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "load-json-file": { + "node_modules/load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, - "requires": { + "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "locate-path": { + "node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, - "requires": { + "dependencies": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "loupe": { + "node_modules/loupe": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", "dev": true, - "requires": { + "dependencies": { "get-func-name": "^2.0.0" } }, - "lru-cache": { + "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "requires": { + "dependencies": { "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, - "ms": { + "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "natural-compare": { + "node_modules/nanolru": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nanolru/-/nanolru-1.0.0.tgz", + "integrity": "sha512-GyQkE8M32pULhQk7Sko5raoIbPalAk90ICG+An4fq6fCsFHsP6fB2K46WGXVdoJpy4SGMnZ/EKbo123fZJomWg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "normalize-package-data": { + "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "requires": { + "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } } }, - "object-inspect": { + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/object-inspect": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "object-keys": { + "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.4" + } }, - "object.assign": { + "node_modules/object.assign": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "has-symbols": "^1.0.1", "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "object.entries": { + "node_modules/object.entries": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" } }, - "object.values": { + "node_modules/object.values": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "optionator": { + "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "dev": true, - "requires": { + "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-limit": { + "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, - "requires": { + "dependencies": { "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" } }, - "p-locate": { + "node_modules/p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, - "requires": { + "dependencies": { "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" } }, - "p-try": { + "node_modules/p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "parent-module": { + "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { + "dependencies": { "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "parse-json": { + "node_modules/parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, - "requires": { + "dependencies": { "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "path-exists": { + "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-parse": { + "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-type": { + "node_modules/path-type": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, - "requires": { + "dependencies": { "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "pathval": { + "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "pify": { + "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "pkg-dir": { + "node_modules/pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, - "requires": { + "dependencies": { "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" } }, - "prelude-ls": { + "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8.0" + } }, - "progress": { + "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "punycode": { + "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "read-pkg": { + "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, - "requires": { + "dependencies": { "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "read-pkg-up": { + "node_modules/read-pkg-up": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, - "requires": { + "dependencies": { "find-up": "^2.0.0", "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "regenerator-runtime": { + "node_modules/regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, - "regexpp": { + "node_modules/regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } }, - "resolve": { + "node_modules/resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, - "requires": { + "dependencies": { "is-core-module": "^2.1.0", "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "resolve-from": { + "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } }, - "rimraf": { + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "requires": { + "dependencies": { "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "slice-ansi": { + "node_modules/slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "spdx-correct": { + "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, - "requires": { + "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-exceptions": { + "node_modules/spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, - "spdx-expression-parse": { + "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "requires": { + "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-license-ids": { + "node_modules/spdx-license-ids": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", "dev": true }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "string-width": { + "node_modules/string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" } }, - "string.prototype.trimend": { + "node_modules/string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "string.prototype.trimstart": { + "node_modules/string.prototype.trimstart": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", "dev": true, - "requires": { + "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "strip-bom": { + "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "table": { + "node_modules/table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, - "requires": { + "dependencies": { "ajv": "^6.10.2", "lodash": "^4.17.14", "slice-ansi": "^2.1.0", "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" } }, - "text-table": { + "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "tsconfig-paths": { + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", "dev": true, - "requires": { + "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, - "type-check": { + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-detect": { + "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "type-fest": { + "node_modules/type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "dev": true, - "requires": { + "dependencies": { "punycode": "^2.1.0" } }, - "v8-compile-cache": { + "node_modules/v8-compile-cache": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, - "validate-npm-package-license": { + "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "requires": { + "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "word-wrap": { + "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", diff --git a/package.json b/package.json index 7897352a4..ab6ef323c 100644 --- a/package.json +++ b/package.json @@ -9,16 +9,21 @@ "lodash": "^4.17.21" }, "devDependencies": { + "@types/node": "^20.5.1", + "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/parser": "^4.14.1", "chai": "^4.3.6", "eslint": "^7.14.0", "eslint-config-airbnb-base": "^14.2.1", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jsx-a11y": "^6.4.1" + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prefer-arrow-functions": "^3.1.4", + "eslint_d": "^11.1.1" }, "scripts": { "test": "node ./tests/runTests.js", - "lint": "eslint Community methods examples tests --ext js", - "lint:fix": "eslint Community methods examples tests --ext js --fix", + "lint": "eslint_d FlowPluginsTs Community methods examples tests --ext js,ts", + "lint:fix": "eslint_d FlowPluginsTs Community methods examples tests --ext js,ts --fix", "checkPlugins": "node ./tests/checkPlugins.js" }, "repository": { diff --git a/tests/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js b/tests/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js index 3862352a8..a096bc439 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js +++ b/tests/Community/Tdarr_Plugin_00td_action_add_audio_stream_codec.js @@ -11,7 +11,7 @@ const tests = [ }, output: { processFile: true, - preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 aac -ac 2', + preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 aac -ac 2 -max_muxing_queue_size 9999', container: '.mp4', handBrakeMode: false, FFmpegMode: false, @@ -32,7 +32,7 @@ const tests = [ }, output: { processFile: true, - preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 2', + preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 2 -max_muxing_queue_size 9999', container: '.mp4', handBrakeMode: false, FFmpegMode: false, @@ -54,7 +54,7 @@ const tests = [ }, output: { processFile: true, - preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 6', + preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 6 -max_muxing_queue_size 9999', container: '.mp4', handBrakeMode: false, FFmpegMode: false, @@ -77,7 +77,7 @@ const tests = [ }, output: { processFile: true, - preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 6', + preset: ',-map 0:v -map 0:1 -map 0:a -map 0:s? -map 0:d? -c copy -c:a:0 eac3 -ac 6 -max_muxing_queue_size 9999', container: '.mp4', handBrakeMode: false, FFmpegMode: false, @@ -89,4 +89,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js b/tests/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js index 5777e2840..1bbb25b1b 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js +++ b/tests/Community/Tdarr_Plugin_00td_action_handbrake_basic_options.js @@ -71,4 +71,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js b/tests/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js index 6be2efd15..b4105a4ec 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js +++ b/tests/Community/Tdarr_Plugin_00td_action_handbrake_ffmpeg_custom.js @@ -63,4 +63,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js b/tests/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js index 26d68f4a5..a72546b6d 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js +++ b/tests/Community/Tdarr_Plugin_00td_action_keep_one_audio_stream.js @@ -86,4 +86,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js b/tests/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js index 96ca95f44..2ed4b36be 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js +++ b/tests/Community/Tdarr_Plugin_00td_action_re_order_all_streams_v2.js @@ -157,4 +157,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js b/tests/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js index d30481385..da0ff4adf 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js +++ b/tests/Community/Tdarr_Plugin_00td_action_remove_audio_by_channel_count.js @@ -103,4 +103,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js b/tests/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js new file mode 100644 index 000000000..1457d044d --- /dev/null +++ b/tests/Community/Tdarr_Plugin_00td_action_remove_stream_by_specified_property.js @@ -0,0 +1,222 @@ +/* eslint max-len: 0 */ +const _ = require('lodash'); +const run = require('../helpers/run'); + +const tests = [ + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, + output: { + processFile: false, + preset: '', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: 'No input propertyToCheck entered in plugin, skipping \n', + }, + }, + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: '', + }, + otherArguments: {}, + }, + output: { + processFile: false, + preset: '', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: 'No input propertyToCheck entered in plugin, skipping \n', + }, + }, + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_tag', + valuesToRemove: '', + }, + otherArguments: {}, + }, + output: { + processFile: false, + preset: '', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: 'No input valuesToRemove entered in plugin, skipping \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_tag', + valuesToRemove: '0x31637661', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999 -map -0:0 ', + container: '.mp4', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Removing stream 0 which is has codec_tag of 0x31637661 \n' + + ' Files has streams which need to be removed, processing \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_tag', + valuesToRemove: '0x31637661,0x6134706d', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999 -map -0:0 -map -0:1 ', + container: '.mp4', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Removing stream 0 which is has codec_tag of 0x31637661 \n' + + ' Removing stream 1 which is has codec_tag of 0x6134706d \n' + + ' Files has streams which need to be removed, processing \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_tag', + valuesToRemove: 'random', + }, + otherArguments: {}, + }, + output: { + processFile: false, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Files does not have streams which need to be removed \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_type', + valuesToRemove: 'video', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999 -map -0:0 ', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Removing stream 0 which is has codec_type of video \n' + + ' Files has streams which need to be removed, processing \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_type', + valuesToRemove: 'video,audio', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999 -map -0:0 -map -0:1 -map -0:2 -map -0:3 -map -0:4 -map -0:5 ', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Removing stream 0 which is has codec_type of video \n' + + ' Removing stream 1 which is has codec_type of audio \n' + + ' Removing stream 2 which is has codec_type of audio \n' + + ' Removing stream 3 which is has codec_type of audio \n' + + ' Removing stream 4 which is has codec_type of audio \n' + + ' Removing stream 5 which is has codec_type of audio \n' + + ' Files has streams which need to be removed, processing \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'codec_type', + valuesToRemove: 'random', + }, + otherArguments: {}, + }, + output: { + processFile: false, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Files does not have streams which need to be removed \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyToCheck: 'level', + valuesToRemove: '41', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ', -map 0 -c copy -max_muxing_queue_size 9999 -map -0:0 ', + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: false, + infoLog: ' Removing stream 0 which is has level of 41 \n' + + ' Files has streams which need to be removed, processing \n', + }, + }, +]; + +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_remux_container.js b/tests/Community/Tdarr_Plugin_00td_action_remux_container.js index 985688bc2..8e1cc6a56 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_remux_container.js +++ b/tests/Community/Tdarr_Plugin_00td_action_remux_container.js @@ -62,4 +62,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js b/tests/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js index 63e1505af..a9752172c 100644 --- a/tests/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js +++ b/tests/Community/Tdarr_Plugin_00td_action_standardise_audio_stream_codecs.js @@ -63,4 +63,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_action_transcode.js b/tests/Community/Tdarr_Plugin_00td_action_transcode.js new file mode 100644 index 000000000..d02649a10 --- /dev/null +++ b/tests/Community/Tdarr_Plugin_00td_action_transcode.js @@ -0,0 +1,142 @@ +/* eslint max-len: 0 */ +const _ = require('lodash'); +const run = require('../helpers/run'); + +const tests = [ + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ' -map 0 -c copy -c:v libx265 -cq:v 19 -b:v 795571.5361445782 -minrate 556900.0753012047 -maxrate 1034242.9969879518 -bufsize 1591143.0722891565 -spatial_aq:v 1 -rc-lookahead:v 32 -max_muxing_queue_size 9999 ', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'Container for output selected as mkv. \n' + + 'Current bitrate = 1591143.0722891565 \n' + + 'Bitrate settings: \n' + + 'Target = 795571.5361445782 \n' + + 'Minimum = 556900.0753012047 \n' + + 'Maximum = 1034242.9969879518 \n' + + 'File is not in hevc. Transcoding. \n', + container: '.mkv', + }, + }, + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH265_1.json')), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, + output: { + processFile: false, + preset: '', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'File is already hevc and in mkv. \n', + container: '.mkv', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: { + target_codec: 'hevc', + target_bitrate_multiplier: 0.75, + try_use_gpu: true, + container: 'mkv', + bitrate_cutoff: 0, + + enable_10bit: false, + bframes_enabled: false, + bframes_value: 5, + force_conform: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ' -map 0 -c copy -c:v libx265 -cq:v 19 -b:v 1193357.3042168673 -minrate 835350.1129518071 -maxrate 1551364.4954819276 -bufsize 1591143.0722891565 -spatial_aq:v 1 -rc-lookahead:v 32 -max_muxing_queue_size 9999 ', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'Container for output selected as mkv. \n' + + 'Current bitrate = 1591143.0722891565 \n' + + 'Bitrate settings: \n' + + 'Target = 1193357.3042168673 \n' + + 'Minimum = 835350.1129518071 \n' + + 'Maximum = 1551364.4954819276 \n' + + 'File is not in hevc. Transcoding. \n', + container: '.mkv', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: { + target_codec: 'h264', + target_bitrate_multiplier: 0.75, + try_use_gpu: true, + container: 'mkv', + bitrate_cutoff: 0, + enable_10bit: false, + bframes_enabled: false, + bframes_value: 5, + force_conform: false, + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ' -map 0 -c copy ', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'File is in h264 but is not in mkv container. Remuxing. \n', + container: '.mkv', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_1.json')), + librarySettings: {}, + inputs: { + target_codec: 'hevc', + target_bitrate_multiplier: 0.75, + try_use_gpu: true, + container: 'mkv', + bitrate_cutoff: 10000000, + + enable_10bit: false, + bframes_enabled: false, + bframes_value: 5, + force_conform: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + preset: '', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'Current bitrate is below set cutoff of 10000000. Cancelling plugin. \n', + container: '.mkv', + }, + }, +]; + +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_bit_depth.js b/tests/Community/Tdarr_Plugin_00td_filter_bit_depth.js index c3a2bd33a..97d43ec56 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_bit_depth.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_bit_depth.js @@ -239,4 +239,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js b/tests/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js index 0d59019d3..4c048a3f0 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_break_stack_if_processed.js @@ -37,4 +37,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_bitrate.js b/tests/Community/Tdarr_Plugin_00td_filter_by_bitrate.js index 4c221164b..0ac71d7d2 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_by_bitrate.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_bitrate.js @@ -61,4 +61,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_codec.js b/tests/Community/Tdarr_Plugin_00td_filter_by_codec.js index e8e6cd33e..f0475d719 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_by_codec.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_codec.js @@ -56,4 +56,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js b/tests/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js index 461665034..e8a9e0710 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_codec_tag_string.js @@ -56,4 +56,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_file_property.js b/tests/Community/Tdarr_Plugin_00td_filter_by_file_property.js new file mode 100644 index 000000000..7e2cdb758 --- /dev/null +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_file_property.js @@ -0,0 +1,535 @@ +/* eslint max-len: 0 */ +const _ = require('lodash'); +const run = require('../helpers/run'); + +const tests = [ + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv == input value of mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'avi', + continueIfPropertyFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv == input value of avi \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mkv', + continueIfPropertyFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv == input value of mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'video_resolution', + propertyValues: '720p,1080p', + continueIfPropertyFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 1080p == input value of 720p \n' + + ' Checking property value of 1080p == input value of 1080p \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'video_resolution', + propertyValues: '721p,1081p', + continueIfPropertyFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of 1080p == input value of 721p \n' + + ' Checking property value of 1080p == input value of 1081p \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file', + propertyValues: 'Source Folder/h264.mkv', + continueIfPropertyFound: false, + exactMatch: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of C:/Transcode/Source Folder/h264.mkv includes input value of Source Folder/h264.mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + // // continueIfPropertyFound: true + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'avi', + continueIfPropertyFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv == input value of avi \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: true \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mkv', + continueIfPropertyFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv == input value of mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: true \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mkv,mp4', + continueIfPropertyFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv == input value of mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: true \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'video_resolution', + propertyValues: '721p,1081p', + continueIfPropertyFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 1080p == input value of 721p \n' + + ' Checking property value of 1080p == input value of 1081p \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: true \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file', + propertyValues: 'Source Folder/h264.mkv', + continueIfPropertyFound: true, + exactMatch: false, + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of C:/Transcode/Source Folder/h264.mkv includes input value of Source Folder/h264.mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: true \n' + + 'Continuing to next plugin \n', + }, + }, + + // check other conditions + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mkv', + condition: '==', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv == input value of mkv \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'avi', + condition: '==', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv == input value of avi \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '60', + condition: '>', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 64.9300765991211 > input value of 60 \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '70', + condition: '>', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of 64.9300765991211 > input value of 70 \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '60', + condition: '>=', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 64.9300765991211 >= input value of 60 \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '70', + condition: '>=', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of 64.9300765991211 >= input value of 70 \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '60', + condition: '<', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of 64.9300765991211 < input value of 60 \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '70', + condition: '<', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 64.9300765991211 < input value of 70 \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '60', + condition: '<=', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of 64.9300765991211 <= input value of 60 \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'file_size', + propertyValues: '70', + condition: '<=', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of 64.9300765991211 <= input value of 70 \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mk', + condition: 'includes', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv includes input value of mk \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'av', + condition: 'includes', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv includes input value of av \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'mk', + condition: 'not includes', + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: ' Checking property value of mkv not includes input value of mk \n' + + ' isConditionMet: false \n' + + ' continueIfPropertyFound: false \n' + + 'Continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + propertyName: 'container', + propertyValues: 'av', + condition: 'not includes', + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: ' Checking property value of mkv not includes input value of av \n' + + ' isConditionMet: true \n' + + ' continueIfPropertyFound: false \n' + + 'Breaking out of stack \n', + }, + }, +]; + +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_resolution.js b/tests/Community/Tdarr_Plugin_00td_filter_by_resolution.js index 91cc03f4e..b4148fbea 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_by_resolution.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_resolution.js @@ -71,4 +71,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_size.js b/tests/Community/Tdarr_Plugin_00td_filter_by_size.js index a869c4c2d..cdc40169f 100644 --- a/tests/Community/Tdarr_Plugin_00td_filter_by_size.js +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_size.js @@ -61,4 +61,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js b/tests/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js new file mode 100644 index 000000000..3c6de5264 --- /dev/null +++ b/tests/Community/Tdarr_Plugin_00td_filter_by_stream_tag.js @@ -0,0 +1,235 @@ +/* eslint max-len: 0 */ +const _ = require('lodash'); +const run = require('../helpers/run'); + +const tests = [ + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing processed has not been found, continuing to next plugin \n', + }, + }, + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'processed', + continueIfTagFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing processed has not been found, continuing to next plugin \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'processed', + continueIfTagFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'A stream with tag name COPYRIGHT containing processed has been found, breaking out of stack \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[1].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,processed', + continueIfTagFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'A stream with tag name COPYRIGHT containing proc,processed has been found, breaking out of stack \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,proce', + continueIfTagFound: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing proc,proce has not been found, continuing to next plugin \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,proce', + continueIfTagFound: false, + exactMatch: false, + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'A stream with tag name COPYRIGHT containing proc,proce has been found, breaking out of stack \n', + }, + }, + + // continueIfTagFound: true + + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'processed', + continueIfTagFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'A stream with tag name COPYRIGHT containing processed has not been found, breaking out of stack \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'processed', + continueIfTagFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing processed has been found, continuing to next plugin \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[1].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,processed', + continueIfTagFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing proc,processed has been found, continuing to next plugin \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,proce', + continueIfTagFound: true, + + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'A stream with tag name COPYRIGHT containing proc,proce has not been found, breaking out of stack \n', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH265_1.json')); + file.ffProbeData.streams[0].tags.COPYRIGHT = 'processed'; + return file; + })(), + librarySettings: {}, + inputs: { + tagName: 'COPYRIGHT', + tagValues: 'proc,proce', + continueIfTagFound: true, + exactMatch: false, + + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'A stream with tag name COPYRIGHT containing proc,proce has been found, continuing to next plugin \n', + }, + }, +]; + +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js b/tests/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js index 9365ddf16..3daa60814 100644 --- a/tests/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js +++ b/tests/Community/Tdarr_Plugin_075a_FFMPEG_HEVC_Generic.js @@ -38,4 +38,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_075a_Transcode_Customisable.js b/tests/Community/Tdarr_Plugin_075a_Transcode_Customisable.js index c6627a62e..8ed713d9d 100644 --- a/tests/Community/Tdarr_Plugin_075a_Transcode_Customisable.js +++ b/tests/Community/Tdarr_Plugin_075a_Transcode_Customisable.js @@ -78,4 +78,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js b/tests/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js index d6dc22ce6..265d30968 100644 --- a/tests/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js +++ b/tests/Community/Tdarr_Plugin_075b_FFMPEG_HEVC_Generic_Video_Audio_Only.js @@ -38,4 +38,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js b/tests/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js index 88633afab..17d5c70c6 100644 --- a/tests/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js +++ b/tests/Community/Tdarr_Plugin_075c_FFMPEG_HEVC_Generic_Video_Audio_Only_CRF20.js @@ -38,4 +38,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js b/tests/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js index 0d7ee5402..74ea621e5 100644 --- a/tests/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js +++ b/tests/Community/Tdarr_Plugin_075d_FFMPEG_HEVC_GPU_Generic_Video_Audio_Only_CRF20.js @@ -38,4 +38,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_076a_re_order_audio_streams.js b/tests/Community/Tdarr_Plugin_076a_re_order_audio_streams.js index 2f226dcee..231744165 100644 --- a/tests/Community/Tdarr_Plugin_076a_re_order_audio_streams.js +++ b/tests/Community/Tdarr_Plugin_076a_re_order_audio_streams.js @@ -87,4 +87,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js b/tests/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js index cc9ef73b5..ba3406277 100644 --- a/tests/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js +++ b/tests/Community/Tdarr_Plugin_076b_re_order_subtitle_streams.js @@ -59,4 +59,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js b/tests/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js index 9a3053c1e..ad6911225 100644 --- a/tests/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js +++ b/tests/Community/Tdarr_Plugin_077b_HandBrake_NVENC_264_Configurable.js @@ -41,4 +41,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js b/tests/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js index 780bf5dda..a910d120e 100644 --- a/tests/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js +++ b/tests/Community/Tdarr_Plugin_A47j_FFMPEG_NVENC_HEVC_Video_Only.js @@ -109,4 +109,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js b/tests/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js index 6557f17ed..21fc74ad0 100644 --- a/tests/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js +++ b/tests/Community/Tdarr_Plugin_DOOM_NVENC_Tiered_MKV_CleanAll.js @@ -72,4 +72,4 @@ const tests = [ ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js b/tests/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js index 93e847c95..00e78dd83 100644 --- a/tests/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js +++ b/tests/Community/Tdarr_Plugin_ER01_Transcode audio and video with HW (PC and Mac).js @@ -136,4 +136,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js b/tests/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js index ef5221c6b..c7318b215 100644 --- a/tests/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js +++ b/tests/Community/Tdarr_Plugin_Greg_MP3_FFMPEG_CPU.js @@ -56,4 +56,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js b/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js index a329f180f..7f75c2d5b 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG.js @@ -120,6 +120,34 @@ const tests = [ container: '.mp4', }, }, + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + container: 'original', + enable_10bit: 'true', + force_conform: 'true', + bitrate_cutoff: '1000', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: '-c:v h264_cuvid, -map 0 -c:v hevc_nvenc -cq:v 19 -b:v 3933k -minrate 2753k -maxrate 5112k -bufsize 7866k -spatial_aq:v 1 -rc-lookahead:v 32 -c:a copy -c:s copy -max_muxing_queue_size 9999 -map -0:d -pix_fmt p010le ', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'Container for output selected as mkv. \n' + + 'Current bitrate = 7866 \n' + + 'Bitrate settings: \n' + + 'Target = 3933 \n' + + 'Minimum = 2753 \n' + + 'Maximum = 5112 \n' + + 'File is not hevc or vp9. Transcoding. \n', + container: '.mkv', + }, + }, { input: { file: _.cloneDeep(require('../sampleData/media/sampleH265_1.json')), @@ -142,4 +170,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js b/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js index 6e1afa316..077e81100 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz1FFMPEG_CPU.js @@ -120,6 +120,34 @@ const tests = [ container: '.mp4', }, }, + { + input: { + file: _.cloneDeep(require('../sampleData/media/sampleH264_2.json')), + librarySettings: {}, + inputs: { + container: 'original', + enable_10bit: 'true', + force_conform: 'true', + bitrate_cutoff: '1000', + }, + otherArguments: {}, + }, + output: { + processFile: true, + preset: ',-map 0 -c:v libx265 -b:v 3933k -minrate 2753k -maxrate 5112k -bufsize 7866k -c:a copy -c:s copy -max_muxing_queue_size 9999 -map -0:d -pix_fmt p010le ', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: 'Container for output selected as mkv. \n' + + 'Current bitrate = 7866 \n' + + 'Bitrate settings: \n' + + 'Target = 3933 \n' + + 'Minimum = 2753 \n' + + 'Maximum = 5112 \n' + + 'File is not hevc or vp9. Transcoding. \n', + container: '.mkv', + }, + }, { input: { file: _.cloneDeep(require('../sampleData/media/sampleH265_1.json')), @@ -142,4 +170,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz1Remux.js b/tests/Community/Tdarr_Plugin_MC93_Migz1Remux.js index 16051f06c..74b062cd3 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz1Remux.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz1Remux.js @@ -42,4 +42,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js b/tests/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js index 5208106b1..424e114a9 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz2CleanTitle.js @@ -43,4 +43,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js b/tests/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js index 11bd86454..ad1cad458 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz3CleanAudio.js @@ -95,4 +95,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js b/tests/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js index fa10ac56e..a9d0d98da 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz4CleanSubs.js @@ -69,4 +69,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js b/tests/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js index 9d9497c29..84779902e 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz5ConvertAudio.js @@ -16,7 +16,7 @@ const tests = [ handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☒Plugin has not been configured, please configure required options. Skipping this plugin. \n', + infoLog: '☑File contains all required audio formats. \n', }, }, @@ -85,6 +85,158 @@ const tests = [ }, }, + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_2.json')); + file.ffProbeData.streams[1].channels = 8; + file.ffProbeData.streams[2].channels = 8; + return file; + })(), + librarySettings: {}, + inputs: { + aac_stereo: 'false', + downmix: 'true', + }, + otherArguments: {}, + }, + output: { + processFile: true, + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n' + + '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n', + preset: ', -map 0 -c:v copy -c:a copy -map 0:1 -c:a:0 ac3 -ac 6 -metadata:s:a:0 title="5.1" -map 0:2 -c:a:1 ac3 -ac 6 -metadata:s:a:1 title="5.1" -strict -2 -c:s copy -max_muxing_queue_size 9999 ', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_2.json')); + file.ffProbeData.streams[1].channels = 8; + file.ffProbeData.streams[2].channels = 8; + file.ffProbeData.streams[3].channels = 6; + file.ffProbeData.streams[4].channels = 6; + file.ffProbeData.streams[5].channels = 6; + return file; + })(), + librarySettings: {}, + inputs: { + aac_stereo: 'false', + downmix: 'true', + }, + otherArguments: {}, + }, + output: { + processFile: true, + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n' + + '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n' + + '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n', + preset: ', -map 0 -c:v copy -c:a copy -map 0:3 -c:a:2 aac -ac 2 -metadata:s:a:2 title="2.0" -map 0:4 -c:a:3 aac -ac 2 -metadata:s:a:3 title="2.0" -map 0:5 -c:a:4 aac -ac 2 -metadata:s:a:4 title="2.0" -strict -2 -c:s copy -max_muxing_queue_size 9999 ', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_2.json')); + file.ffProbeData.streams[1].channels = 8; + file.ffProbeData.streams[2].channels = 8; + file.ffProbeData.streams[3].channels = 8; + file.ffProbeData.streams[4].channels = 6; + file.ffProbeData.streams[5].channels = 6; + return file; + })(), + librarySettings: {}, + inputs: { + aac_stereo: 'false', + downmix: 'true', + downmix_single_track: 'true', + }, + otherArguments: {}, + }, + output: { + processFile: true, + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '☒Audio track is 6 channel, no 2 channel exists. Creating 2 channel from 6 channel. \n', + preset: ', -map 0 -c:v copy -c:a copy -map 0:4 -c:a:3 aac -ac 2 -metadata:s:a:3 title="2.0" -strict -2 -c:s copy -max_muxing_queue_size 9999 ', + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_2.json')); + file.ffProbeData.streams[1].channels = 8; + file.ffProbeData.streams[2].channels = 8; + file.ffProbeData.streams[3].channels = 8; + file.ffProbeData.streams[4].channels = 8; + file.ffProbeData.streams[5].channels = 8; + return file; + })(), + librarySettings: {}, + inputs: { + aac_stereo: 'false', + downmix: 'true', + downmix_single_track: 'false', + }, + otherArguments: {}, + }, + output: { + processFile: true, + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n' + + '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n' + + '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n' + + '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n' + + '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n', + preset: ', -map 0 -c:v copy -c:a copy -map 0:1 -c:a:0 ac3 -ac 6 -metadata:s:a:0 title="5.1" -map 0:2 -c:a:1 ac3 -ac 6 -metadata:s:a:1 title="5.1" -map 0:3 -c:a:2 ac3 -ac 6 -metadata:s:a:2 title="5.1" -map 0:4 -c:a:3 ac3 -ac 6 -metadata:s:a:3 title="5.1" -map 0:5 -c:a:4 ac3 -ac 6 -metadata:s:a:4 title="5.1" -strict -2 -c:s copy -max_muxing_queue_size 9999 ', + + }, + }, + + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_2.json')); + file.ffProbeData.streams[1].channels = 8; + file.ffProbeData.streams[2].channels = 8; + file.ffProbeData.streams[3].channels = 8; + file.ffProbeData.streams[4].channels = 8; + file.ffProbeData.streams[5].channels = 8; + return file; + })(), + librarySettings: {}, + inputs: { + aac_stereo: 'false', + downmix: 'true', + downmix_single_track: 'true', + }, + otherArguments: {}, + }, + output: { + processFile: true, + container: '.mkv', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + infoLog: '☒Audio track is 8 channel, no 6 channel exists. Creating 6 channel from 8 channel. \n', + preset: ', -map 0 -c:v copy -c:a copy -map 0:1 -c:a:0 ac3 -ac 6 -metadata:s:a:0 title="5.1" -strict -2 -c:s copy -max_muxing_queue_size 9999 ', + }, + }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js b/tests/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js index d4a111a1f..82545e74c 100644 --- a/tests/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js +++ b/tests/Community/Tdarr_Plugin_MC93_Migz6OrderStreams.js @@ -100,4 +100,4 @@ const tests = [ ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js b/tests/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js index bdb0c0225..828317e22 100644 --- a/tests/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js +++ b/tests/Community/Tdarr_Plugin_MC93_MigzImageRemoval.js @@ -43,4 +43,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js b/tests/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js index 40adead3d..b9dd8b5a2 100644 --- a/tests/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js +++ b/tests/Community/Tdarr_Plugin_MP01_MichPasCleanSubsAndAudioCodecs.js @@ -44,4 +44,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js b/tests/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js index 1c5438d1d..42261910f 100644 --- a/tests/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js +++ b/tests/Community/Tdarr_Plugin_Mthr_VaapiHEVCTranscode.js @@ -104,4 +104,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js b/tests/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js index d1fbf7160..65d50ad6d 100644 --- a/tests/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js +++ b/tests/Community/Tdarr_Plugin_SV6x_Smoove1FFMPEG_NVENC_H264.js @@ -37,6 +37,7 @@ const tests = [ handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, + preset: '', container: '.mkv', }, }, @@ -47,12 +48,41 @@ const tests = [ inputs: {}, otherArguments: {}, }, + output: { + processFile: true, + infoLog: 'Container for output selected as mkv. \n' + + 'Current bitrate = 3058 \n' + + 'Bitrate settings: \n' + + 'Target = 3058 \n' + + 'Minimum = 2140 \n' + + 'Maximum = 3975 \n' + + 'File is not h264. Transcoding. \n', + handBrakeMode: false, + FFmpegMode: true, + reQueueAfter: true, + preset: ',-map 0 -c:v h264_nvenc -preset fast -crf 23 -b:v 3058k -minrate 2140k -maxrate 3975k -bufsize 3058k -c:a copy -c:s copy -max_muxing_queue_size 9999 -pix_fmt yuv420p ', + container: '.mkv', + }, + }, + { + input: { + file: (() => { + const file = _.cloneDeep(require('../sampleData/media/sampleH264_1.json')); + delete file.ffProbeData.streams[0].duration; + delete file.ffProbeData.format.duration; + return file; + })(), + librarySettings: {}, + inputs: {}, + otherArguments: {}, + }, output: { processFile: false, infoLog: 'Target bitrate could not be calculated. Skipping this plugin. \n', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, + preset: '', container: '.mkv', }, }, @@ -79,8 +109,8 @@ const tests = [ handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, + preset: ',-map 0 -c:v h264_nvenc -preset fast -crf 23 -b:v 1526k -minrate 1068k -maxrate 1983k -bufsize 1526k -c:a copy -c:s copy -max_muxing_queue_size 9999 -pix_fmt yuv420p ', container: '.mkv', - preset: 'undefined,-map 0 -c:v h264_nvenc -preset fast -crf 23 -tune film -b:v 1526k -minrate 1068k -maxrate 1983k -bufsize 1526k -c:a copy -c:s copy -max_muxing_queue_size 9999 -pix_fmt yuv420p ', }, }, { @@ -98,9 +128,10 @@ const tests = [ handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, + preset: '', container: '.mp4', }, }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js b/tests/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js index 228ff5c08..f7a5a31ea 100644 --- a/tests/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js +++ b/tests/Community/Tdarr_Plugin_VP92_VP9_Match_Bitrate_One_Pass.js @@ -502,4 +502,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js b/tests/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js index 22d571dc5..1d1c2ba6e 100644 --- a/tests/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js +++ b/tests/Community/Tdarr_Plugin_a37x_Drawmonster_MP4_No_Title_Meta.js @@ -39,4 +39,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js b/tests/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js index 805c98480..1ead8be34 100644 --- a/tests/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js +++ b/tests/Community/Tdarr_Plugin_a8hc_HaveAGitGat_HandBrake_H264_VeryFast1080p30.js @@ -63,4 +63,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js b/tests/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js index 47cc98cd9..fd72aafdc 100644 --- a/tests/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js +++ b/tests/Community/Tdarr_Plugin_a9hc_HaveAGitGat_HandBrake_H264_Fast1080p30.js @@ -63,4 +63,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js b/tests/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js index 4bf0913df..8d77820bb 100644 --- a/tests/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js +++ b/tests/Community/Tdarr_Plugin_a9hd_FFMPEG_Transcode_Specific_Audio_Stream_Codecs.js @@ -42,4 +42,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a9he_New_file_size_check.js b/tests/Community/Tdarr_Plugin_a9he_New_file_size_check.js index e990b360d..7d69ab93a 100644 --- a/tests/Community/Tdarr_Plugin_a9he_New_file_size_check.js +++ b/tests/Community/Tdarr_Plugin_a9he_New_file_size_check.js @@ -65,4 +65,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js b/tests/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js index 949771483..df8ed5d0f 100644 --- a/tests/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js +++ b/tests/Community/Tdarr_Plugin_a9hf_New_file_duration_check.js @@ -65,4 +65,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js b/tests/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js index 9c01863c7..37b777ab0 100644 --- a/tests/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js +++ b/tests/Community/Tdarr_Plugin_b38x_Nosirus_h265_aac_no_meta.js @@ -42,4 +42,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js b/tests/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js index a3b9e8008..acffef5ba 100644 --- a/tests/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js +++ b/tests/Community/Tdarr_Plugin_b39x_the1poet_surround_sound_to_ac3.js @@ -56,4 +56,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js b/tests/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js index 6d0dd963c..14c773435 100644 --- a/tests/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js +++ b/tests/Community/Tdarr_Plugin_bsh1_Boosh_FFMPEG_QSV_HEVC.js @@ -13,52 +13,53 @@ const tests = [ output: { linux: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -c:v h264_qsv -map 0 -c:v hevc_qsv -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset slow \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 ', + preset: '-fflags +genpts -hwaccel qsv -hwaccel_output_format qsv \n' + + ' -init_hw_device qsv:hw_any,child_device_type=vaapi -c:v h264_qsv -map 0 -c:v hevc_qsv -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset slow -c:a copy -c:s copy -max_muxing_queue_size 9999 ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' - + '\n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + 'Container for output selected as mkv. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + 'File Transcoding... \n', container: '.mkv', }, win32: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -c:v h264_qsv -map 0 -c:v hevc_qsv -load_plugin hevc_hw -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset slow \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 ', + preset: '-fflags +genpts -hwaccel qsv -hwaccel_output_format qsv \n' + + ' -init_hw_device qsv:hw_any,child_device_type=d3d11va -c:v h264_qsv -map 0 -c:v hevc_qsv -load_plugin hevc_hw -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset slow -c:a copy -c:s copy -max_muxing_queue_size 9999 ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' - + '\n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + 'Container for output selected as mkv. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + 'File Transcoding... \n', container: '.mkv', }, darwin: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -c:v h264_qsv -map 0 -c:v hevc_videotoolbox -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset slow \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 ', + preset: '-fflags +genpts -hwaccel videotoolbox -map 0 -c:v hevc_videotoolbox -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset slow -c:a copy -c:s copy -max_muxing_queue_size 9999 ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' - + '\n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + 'Container for output selected as mkv. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + + '==ALERT== OS detected as MAC - This will use VIDEOTOOLBOX to encode which is NOT QSV\n' + + 'cmds set in extra_qsv_options will be IGNORED!\n' + 'File Transcoding... \n', container: '.mkv', }, @@ -78,55 +79,56 @@ const tests = [ output: { linux: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -map 0 -c:v hevc_qsv -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset fast \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -pix_fmt p010le ', + preset: '-fflags +genpts -hwaccel qsv -hwaccel_output_format qsv \n' + + ' -init_hw_device qsv:hw_any,child_device_type=vaapi -c:v h264_qsv -map 0 -c:v hevc_qsv -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset fast -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -vf scale_qsv=format=p010le ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n' - + '\n' + 'Container for output selected as mp4. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + 'File Transcoding... \n', container: '.mp4', }, win32: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -map 0 -c:v hevc_qsv -load_plugin hevc_hw -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset fast \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -pix_fmt p010le ', + preset: '-fflags +genpts -hwaccel qsv -hwaccel_output_format qsv \n' + + ' -init_hw_device qsv:hw_any,child_device_type=d3d11va -c:v h264_qsv -map 0 -c:v hevc_qsv -load_plugin hevc_hw -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset fast -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -vf scale_qsv=format=p010le ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n' - + '\n' + 'Container for output selected as mp4. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + 'File Transcoding... \n', container: '.mp4', }, darwin: { processFile: true, - preset: '-fflags +genpts -hwaccel qsv -map 0 -c:v hevc_videotoolbox -b:v 759k -minrate 569k -maxrate 949k -bufsize 1517k -preset fast \n' - + ' -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -pix_fmt p010le ', + preset: '-fflags +genpts -hwaccel videotoolbox -map 0 -c:v hevc_videotoolbox -b:v 603k -minrate 452k -maxrate 754k -bufsize 1206k -preset fast -c:a copy -c:s copy -max_muxing_queue_size 9999 -profile:v main10 -vf scale_qsv=format=p010le ', handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + '10 bit encode enabled. Setting Main10 Profile & 10 bit pixel format \n' - + '\n' + 'Container for output selected as mp4. \n' + 'Encode variable bitrate settings: \n' - + 'Target = 759k \n' - + 'Minimum = 569k \n' - + 'Maximum = 949k \n' + + 'Target = 603k \n' + + 'Minimum = 452k \n' + + 'Maximum = 754k \n' + + '==ALERT== OS detected as MAC - This will use VIDEOTOOLBOX to encode which is NOT QSV\n' + + 'cmds set in extra_qsv_options will be IGNORED!\n' + 'File Transcoding... \n', container: '.mp4', }, @@ -150,11 +152,13 @@ const tests = [ handBrakeMode: false, FFmpegMode: true, reQueueAfter: true, - infoLog: '☑ It looks like the current bitrate is 1517k. \n' - + '☑ Current bitrate is below set cutoff of 2000k. Cancelling plugin. \n', + infoLog: 'Input file is not MKV so cannot use mkvpropedit to get new file stats. Continuing but file stats will likely be inaccurate...\n' + + '☑ It looks like the current video bitrate is 1206kbps. \n' + + '☑ Current bitrate is below set cutoff of 2000kbps. \n' + + 'Cancelling plugin. \n', container: '.mp4', }, }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js b/tests/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js index 1c012bf0a..0785865e5 100644 --- a/tests/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js +++ b/tests/Community/Tdarr_Plugin_c0r1_SetDefaultAudioStream.js @@ -46,4 +46,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js b/tests/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js index 455b2ca53..712046574 100644 --- a/tests/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js +++ b/tests/Community/Tdarr_Plugin_d5d3_iiDrakeii_FFMPEG_NVENC_Tiered_MKV.js @@ -47,4 +47,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js b/tests/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js index c80b3dc68..5052f5834 100644 --- a/tests/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js +++ b/tests/Community/Tdarr_Plugin_d5d4_iiDrakeii_Not_A_Video_Mjpeg_Fix.js @@ -44,4 +44,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js b/tests/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js index e382ca877..44ed8c951 100644 --- a/tests/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js +++ b/tests/Community/Tdarr_Plugin_da11_Dallas_FFmpeg_Presets_H264_MP4.js @@ -59,4 +59,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js b/tests/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js index 250a2a74b..d5b59ac71 100644 --- a/tests/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js +++ b/tests/Community/Tdarr_Plugin_drdd_standardise_all_in_one.js @@ -20,9 +20,7 @@ const tests = [ + '• Original Bitrate: 1517\n' + '• Target Bitrate: 1517\n' + '• Minimum Bitrate: 1061\n' - + '• Maximum Bitrate: 1972\n' - + '\n' - + '☑ No subtitle processing necessary', + + '• Maximum Bitrate: 1972\n', processFile: true, preset: ',-map 0 -map -0:d -c:v libx265 -b:v 1517k -minrate 1061k -maxrate 1972k -bufsize 1517k -c:a copy -c:a:0 ac3 -c:s copy -max_muxing_queue_size 4096', reQueueAfter: false, @@ -47,9 +45,7 @@ const tests = [ + '• Original Bitrate: 1517\n' + '• Target Bitrate: 1517\n' + '• Minimum Bitrate: 1061\n' - + '• Maximum Bitrate: 1972\n' - + '\n' - + '☑ No subtitle processing necessary', + + '• Maximum Bitrate: 1972\n', processFile: true, preset: '-c:v h264_cuvid,-map 0 -map -0:d -c:v hevc_nvenc -cq:v 19 -b:v 1517k -minrate 1061k -maxrate 1972k -bufsize 1517k -spatial_aq:v 1 -rc-lookahead:v 32 -c:a copy -c:a:0 ac3 -c:s copy -max_muxing_queue_size 4096', reQueueAfter: false, @@ -62,6 +58,7 @@ const tests = [ inputs: { nvenc: 'false', qsv: 'true', + wanted_subtitle_languages: 'eng,fre', }, otherArguments: {}, }, @@ -87,7 +84,9 @@ const tests = [ input: { file: _.cloneDeep(require('../sampleData/media/sampleH265_1.json')), librarySettings: {}, - inputs: {}, + inputs: { + wanted_subtitle_languages: 'eng,fre', + }, otherArguments: {}, }, output: { @@ -107,4 +106,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js b/tests/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js index edbb61011..a2b6cf8f1 100644 --- a/tests/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js +++ b/tests/Community/Tdarr_Plugin_e3jc_Tharic_H.264_MKV_480p30_No_Subs_No_Title_Meta.js @@ -94,4 +94,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js b/tests/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js index 4371d6cb3..dfdda5a33 100644 --- a/tests/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js +++ b/tests/Community/Tdarr_Plugin_e3jd_Tharic_H.264_MKV_720p30_No_Subs_No_Title_Meta.js @@ -96,4 +96,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js b/tests/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js index 046e210c8..049e3be0e 100644 --- a/tests/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js +++ b/tests/Community/Tdarr_Plugin_e3je_Tharic_H.264_MKV_1080p30_No_Subs_No_Title_Meta.js @@ -93,4 +93,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js b/tests/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js index 5a70598a4..98913910f 100644 --- a/tests/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js +++ b/tests/Community/Tdarr_Plugin_e5c3_CnT_Keep_Preferred_Audio.js @@ -91,4 +91,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js b/tests/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js index 20ef02605..9f95039c3 100644 --- a/tests/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js +++ b/tests/Community/Tdarr_Plugin_f4k1_aune_audio_to_flac.js @@ -82,4 +82,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js b/tests/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js index 86bb3c1f3..bc5016b0d 100644 --- a/tests/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js +++ b/tests/Community/Tdarr_Plugin_fd5T_Sparticus_4K_AC3_No_Subs.js @@ -45,4 +45,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js b/tests/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js index 71c80980c..4f0d9481c 100644 --- a/tests/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js +++ b/tests/Community/Tdarr_Plugin_henk_Add_Specific_Audio_Codec.js @@ -82,4 +82,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js b/tests/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js index 894d3a08f..8a4eb66df 100644 --- a/tests/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js +++ b/tests/Community/Tdarr_Plugin_hk75_Drawmonster_MP4_AAC_No_Subs_No_metaTitle.js @@ -65,4 +65,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js b/tests/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js index df39e53bd..bb567e68e 100644 --- a/tests/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js +++ b/tests/Community/Tdarr_Plugin_hk76_GilbN_MP4_AAC_No_metaTitle.js @@ -63,4 +63,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js b/tests/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js index 229b0510c..f40e36b61 100644 --- a/tests/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js +++ b/tests/Community/Tdarr_Plugin_jeons001_Downmix_to_stereo_and_apply_DRC.js @@ -39,4 +39,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js b/tests/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js index 197cc3904..c196ad3bd 100644 --- a/tests/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js +++ b/tests/Community/Tdarr_Plugin_lmg1_Reorder_Streams.js @@ -47,4 +47,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js b/tests/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js index 9dfabae03..d7efca852 100644 --- a/tests/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js +++ b/tests/Community/Tdarr_Plugin_nc7x_Drawmonster_No_Title_Meta.js @@ -39,4 +39,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js b/tests/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js index 4acaaaf40..c92011179 100644 --- a/tests/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js +++ b/tests/Community/Tdarr_Plugin_r002_rootuser_FFMPEG_HQ_HEVC_MKV_Animation.js @@ -41,4 +41,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js b/tests/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js index 3cdab9692..cc069a318 100644 --- a/tests/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js +++ b/tests/Community/Tdarr_Plugin_raf4_Floorpie_FFmpeg_Tiered_HEVC_MKV.js @@ -64,4 +64,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js b/tests/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js index a6aa2ff31..e3d5c5132 100644 --- a/tests/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js +++ b/tests/Community/Tdarr_Plugin_s710_nick_h265_nvenc_4K.js @@ -81,4 +81,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_s7x8_winsome_h265.js b/tests/Community/Tdarr_Plugin_s7x8_winsome_h265.js index 36ea9c203..6277e956b 100644 --- a/tests/Community/Tdarr_Plugin_s7x8_winsome_h265.js +++ b/tests/Community/Tdarr_Plugin_s7x8_winsome_h265.js @@ -60,4 +60,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js b/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js index 6fad6fc52..ae60e65fa 100644 --- a/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js +++ b/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_10bit.js @@ -60,4 +60,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js b/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js index e81f664b7..f33b50b13 100644 --- a/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js +++ b/tests/Community/Tdarr_Plugin_s7x9_winsome_h265_nvenc.js @@ -60,4 +60,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js b/tests/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js index 0cbc71660..4d3cbad1a 100644 --- a/tests/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js +++ b/tests/Community/Tdarr_Plugin_sdd3_Remove_Commentary_Tracks.js @@ -45,4 +45,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js b/tests/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js index 10424ec4b..5fca337a5 100644 --- a/tests/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js +++ b/tests/Community/Tdarr_Plugin_sdf5_Thierrrrry_Remove_Non_English_Audio.js @@ -60,4 +60,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_tsld_filter_modified_date.js b/tests/Community/Tdarr_Plugin_tsld_filter_modified_date.js new file mode 100644 index 000000000..1a5ad0cbd --- /dev/null +++ b/tests/Community/Tdarr_Plugin_tsld_filter_modified_date.js @@ -0,0 +1,63 @@ +/* eslint max-len: 0 */ +const run = require('../helpers/run'); + +const tests = [ + { + input: { + file: require('../sampleData/media/sampleH264_2.json'), + librarySettings: {}, + inputs: { + minModifiedDaysOld: 1, + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'File modified date old enough. Moving to next plugin.', + }, + }, + { + input: { + file: require('../sampleData/media/sampleH264_1.json'), + librarySettings: {}, + inputs: { + minModifiedDaysOld: 9999, + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'Skipping, file modified date not old enough', + }, + }, + { + input: { + file: require('../sampleData/media/sampleH264_1.json'), + librarySettings: {}, + inputs: { + minModifiedDaysOld: 1, + }, + otherArguments: {}, + }, + output: { + processFile: true, + infoLog: 'File modified date old enough. Moving to next plugin.', + }, + }, + { + input: { + file: require('../sampleData/media/sampleH264_1.json'), + librarySettings: {}, + inputs: { + minModifiedDaysOld: 9999, + }, + otherArguments: {}, + }, + output: { + processFile: false, + infoLog: 'Skipping, file modified date not old enough', + }, + }, +]; + +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js b/tests/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js index 74a931981..bed03fdff 100644 --- a/tests/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js +++ b/tests/Community/Tdarr_Plugin_vdka_Remove_DataStreams.js @@ -43,4 +43,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js b/tests/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js index a916de016..b7141b377 100644 --- a/tests/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js +++ b/tests/Community/Tdarr_Plugin_vdka_Tiered_CPU_CRF_Based_Configurable.js @@ -241,4 +241,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js b/tests/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js index fa423cfc0..fe9288e72 100644 --- a/tests/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js +++ b/tests/Community/Tdarr_Plugin_vdka_Tiered_NVENC_CQV_BASED_CONFIGURABLE.js @@ -167,4 +167,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_x7ab_Remove_Subs.js b/tests/Community/Tdarr_Plugin_x7ab_Remove_Subs.js index 9eca971ac..4dad3996a 100644 --- a/tests/Community/Tdarr_Plugin_x7ab_Remove_Subs.js +++ b/tests/Community/Tdarr_Plugin_x7ab_Remove_Subs.js @@ -39,4 +39,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js b/tests/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js index 01df48b5f..49315df9e 100644 --- a/tests/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js +++ b/tests/Community/Tdarr_Plugin_x7ac_Remove_Closed_Captions.js @@ -64,4 +64,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js b/tests/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js index 7f04b00af..ce628967f 100644 --- a/tests/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js +++ b/tests/Community/Tdarr_Plugin_z0ab_TheRealShadoh_FFmpeg_Subs_H264_Medium.js @@ -108,4 +108,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js b/tests/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js index 56e03e752..d498daf01 100644 --- a/tests/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js +++ b/tests/Community/Tdarr_Plugin_z1ab_TheRealShadoh_FFmpeg_Subs_H264_Fast.js @@ -108,4 +108,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js b/tests/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js index 3d3759326..56bb21287 100644 --- a/tests/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js +++ b/tests/Community/Tdarr_Plugin_z2ab_TheRealShadoh_FFmpeg_Subs_H264_Slow.js @@ -108,4 +108,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js b/tests/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js index 8216fb3d2..a557ccc92 100644 --- a/tests/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js +++ b/tests/Community/Tdarr_Plugin_z3ab_TheRealShadoh_FFmpeg_Subs_H264_VeryFast.js @@ -108,4 +108,4 @@ const tests = [ }, ]; -run(tests); +void run(tests); diff --git a/tests/checkPlugins.js b/tests/checkPlugins.js index 13bab9d25..c98e96eca 100644 --- a/tests/checkPlugins.js +++ b/tests/checkPlugins.js @@ -186,6 +186,12 @@ module.exports.plugin = plugin;`; } else if (inputs[j].defaultValue === undefined) { console.log(chalk.red(`Plugin Input does not have a default value: '${folder}/${files[i]}' : ${inputs[j].name}`)); errorEncountered = true; + } else if (inputs[j].inputUI.type === 'dropdown' && !Array.isArray(inputs[j].inputUI.options)) { + console.log(chalk.red(`Plugin Input is dropdown but does not have dropdown array: '${folder}/${files[i]}' : ${inputs[j].name}`)); + errorEncountered = true; + } else if (Array.isArray(inputs[j].inputUI.options) && inputs[j].inputUI.options.some((option) => typeof option === 'boolean')) { + console.log(chalk.red(`Plugin Input has a boolean dropdown input: '${folder}/${files[i]}' : ${inputs[j].name}`)); + errorEncountered = true; } const count = read.split(inputs[j].name).length - 1; diff --git a/tests/helpers/run.js b/tests/helpers/run.js index ff1dabf69..012fcfb18 100644 --- a/tests/helpers/run.js +++ b/tests/helpers/run.js @@ -46,7 +46,7 @@ const run = async (tests) => { testOutput = test.outputModify(testOutput); } - if (test.error && test.error.shouldThrow) { + if (test?.error?.shouldThrow) { if (errorEncountered !== false) { // eslint-disable-next-line no-console console.log(errorEncountered); @@ -54,6 +54,8 @@ const run = async (tests) => { } else { throw new Error('Expected plugin error but none was thrown!'); } + } else if (!test?.error?.shouldThrow && errorEncountered !== false) { + throw new Error(`Unexpected plugin error!${errorEncountered}`); } else { chai.assert.deepEqual(testOutput, expectedOutput); } diff --git a/tests/runTests.js b/tests/runTests.js index c96915d9c..971cc24a4 100644 --- a/tests/runTests.js +++ b/tests/runTests.js @@ -7,7 +7,7 @@ const childProcess = require('child_process'); const filenames = fs.readdirSync(`${process.cwd()}/Community`).reverse(); const errorsEncountered = []; -const run = async () => { +const run = () => { const pluginsToRun = []; for (let i = 0; i < filenames.length; i += 1) { const filename = filenames[i]; @@ -74,4 +74,4 @@ const run = async () => { } }; -run(); +void run(); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 000000000..7ad95852f --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": [] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..88285c4a5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,67 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./FlowPlugins", /* Redirect output structure to the directory. */ + "rootDir": "./FlowPluginsTs", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "file": [ + "./node_modules/@types/express/index.d.ts" + ], + "exclude": [] +} \ No newline at end of file