From 9bbc4fbec9a535799e36587a577bfa3f23a2c8fc Mon Sep 17 00:00:00 2001 From: HaveAGitGat <43864057+HaveAGitGat@users.noreply.github.com> Date: Fri, 24 Dec 2021 09:35:35 +0000 Subject: [PATCH] Check plugins (#221) * Include examples in checkPlugins. Fix errors and lint. * Remove on push * Show tick for successful tests --- .github/workflows/lint.yml | 2 - Tdarr_Plugin_aaaa_Pre_Proc_Example.js | 405 ------------------ Tdarr_Plugin_zzzz_Post_Proc_Example.js | 303 ------------- .../Tdarr_Plugin_a9he_New_file_size_check.js | 2 +- .../Tdarr_Plugin_f001_Filter_Example.js | 28 +- ...js => Tdarr_Plugin_f002_Filter_Example.js} | 4 +- .../Tdarr_Plugin_pos1_Post_Proc_Example.js | 83 ++++ .../Tdarr_Plugin_pre1_Pre_Proc_Example.js | 163 +++++++ tests/checkPlugins.js | 270 ++++++------ 9 files changed, 405 insertions(+), 855 deletions(-) delete mode 100644 Tdarr_Plugin_aaaa_Pre_Proc_Example.js delete mode 100644 Tdarr_Plugin_zzzz_Post_Proc_Example.js rename Tdarr_Plugin_bbbb_Filter_Example.js => examples/Tdarr_Plugin_f001_Filter_Example.js (62%) rename examples/{filters/Tdarr_Plugin_bbbc_Filter_Example.js => Tdarr_Plugin_f002_Filter_Example.js} (95%) create mode 100644 examples/Tdarr_Plugin_pos1_Post_Proc_Example.js create mode 100644 examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 55e4896..9c76f0f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,8 +1,6 @@ name: Node.js CI on: - push: - branches: ['**'] pull_request: branches: ['**'] diff --git a/Tdarr_Plugin_aaaa_Pre_Proc_Example.js b/Tdarr_Plugin_aaaa_Pre_Proc_Example.js deleted file mode 100644 index 891c309..0000000 --- a/Tdarr_Plugin_aaaa_Pre_Proc_Example.js +++ /dev/null @@ -1,405 +0,0 @@ -const loadDefaultValues = require('../methods/loadDefaultValues'); -// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs: -module.exports.dependencies = [ - 'import-fresh', -]; - -const details = () => { - return { - id: 'Tdarr_Plugin_aaaa_Pre_Proc_Example', - Stage: 'Pre-processing', // Pre-processing or Post-processing. Determines when the plugin will be executed. - Name: 'No title meta data ', - Type: 'Video', - Operation: 'Transcode', - Description: 'This plugin removes metadata (if a title exists). The output container is the same as the original. \n\n', - Version: '1.00', - Tags: 'ffmpeg,h265', // Provide tags to categorise your plugin in the plugin browser.Tag options: h265,hevc,h264,nvenc h265,nvenc h264,video only,audio only,subtitle only,handbrake,ffmpeg,radarr,sonarr,pre-processing,post-processing,configurable - - Inputs: [ - // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI - { - name: 'language', - type: 'string', // set the data type of the input ('string', 'number', 'boolean') - defaultValue: 'eng', // set the default value of the input incase the user enters no input - inputUI: { - type: 'text', // specify how the input UI will appear to the user ('text' or 'dropdown') - }, - // inputUI: { // dropdown inputUI example - // type: 'dropdown', - // options: [ - // 'false', - // 'true', - // ], - // }, - - inputUI: { - type: 'text', - }, - tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep. - - \\nExample:\\n - eng - - \\nExample:\\n - - fr - - \\nExample:\\n - - de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks - }, - { - name: 'channels', - type: 'number', - defaultValue: 2, - inputUI: { - type: 'dropdown', - options: [ - '1', - '2', - '6', - ], - }, - tooltip: `Desired audio channel number. - \\nExample:\\n - 2`, - }, - ], - }; -}; - -// eslint-disable-next-line no-unused-vars -const plugin = (file, librarySettings, inputs, otherArguments) => { - // eslint-disable-next-line no-unused-vars,no-param-reassign - inputs = loadDefaultValues(inputs, details); - // Only 'require' dependencies within this function or other functions. Do not require in the top scope. - const importFresh = require('import-fresh'); - - // Must return this object at some point in the function else plugin will fail. - - const response = { - processFile: false, // If set to false, the file will be skipped. Set to true to have the file transcoded. - preset: '', // HandBrake/FFmpeg CLI arguments you'd like to use. - // For FFmpeg, the input arguments come first followed by , followed by the output argument. - // Examples - // HandBrake - // '-Z "Very Fast 1080p30"' - // FFmpeg - // '-sn -map_metadata -1 -c:v copy -c:a copy' - container: '.mp4', // The container of the transcoded output file. - handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding - FFmpegMode: false, - infoLog: '', // This will be shown when the user clicks the 'i' (info) button on a file in the output queue if - // it has been skipped. - // Give reasons why it has been skipped ('File has no title metadata, File meets conditions!') - - // Optional (include together) - file, - removeFromDB: false, // Tell Tdarr to remove file from database if true - updateDB: false, // Change file object above and update database if true - }; - - console.log(inputs.language); // eng if user entered 'eng' in input box in Tdarr plugin UI - console.log(inputs.channels); // 2 if user entered '2' in input box in Tdarr plugin UI - - // Here we specify that we want the output file container to be the same as the current container. - response.container = `.${file.container}`; - - // We will use FFmpeg for this procedure. - response.FFmpegMode = true; - - // Check if file has title metadata - if (file.meta.Title != undefined) { - // if so, remove it - - response.infoLog += ' File has title metadata'; - response.preset = ',-map_metadata -1 -c:v copy -c:a copy'; - response.processFile = true; - return response; - } - response.infoLog += ' File has no title metadata'; - - response.infoLog += ' File meets conditions!'; - return response; -}; - -module.exports.onTranscodeSuccess = function onTranscodeSuccess( - file, - librarySettings, - inputs, -) { - console.log( - 'Transcode success! Now do some stuff with the newly scanned file.', - ); - - // Optional response if you need to modify database - const response = { - file, - removeFromDB: false, - updateDB: false, - }; - - return response; -}; - -module.exports.onTranscodeError = function onTranscodeError( - file, - librarySettings, - inputs, -) { - console.log('Transcode fail! Now do some stuff with the original file.'); - - // Optional response if you need to modify database - const response = { - file, - removeFromDB: false, - updateDB: false, - }; - - return response; -}; -module.exports.details = details; -module.exports.plugin = plugin; - -// Example file object: -// { -// _id: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// DB: 'ZRPDmnmpyuAEQi7nG', -// HealthCheck: 'Not attempted', -// TranscodeDecisionMaker: 'Not attempted', -// bit_rate: 1690430.4, -// container: 'mp4', -// createdAt: 2019-09-26T06:46:31.929Z, -// ffProbeData: -// { streams: -// [ { index: 0, -// codec_name: 'h264', -// codec_long_name: 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10', -// profile: 'Main', -// codec_type: 'video', -// codec_time_base: '1/50', -// codec_tag_string: 'avc1', -// codec_tag: '0x31637661', -// width: 1280, -// height: 720, -// coded_width: 1280, -// coded_height: 720, -// has_b_frames: 0, -// sample_aspect_ratio: '1:1', -// display_aspect_ratio: '16:9', -// pix_fmt: 'yuv420p', -// level: 31, -// chroma_location: 'left', -// refs: 1, -// is_avc: 'true', -// nal_length_size: '4', -// r_frame_rate: '25/1', -// avg_frame_rate: '25/1', -// time_base: '1/12800', -// start_pts: 0, -// start_time: '0.000000', -// duration_ts: 67584, -// duration: '5.280000', -// bit_rate: '1205959', -// bits_per_raw_sample: '8', -// nb_frames: '132', -// disposition: -// { default: 1, -// dub: 0, -// original: 0, -// comment: 0, -// lyrics: 0, -// karaoke: 0, -// forced: 0, -// hearing_impaired: 0, -// visual_impaired: 0, -// clean_effects: 0, -// attached_pic: 0, -// timed_thumbnails: 0 }, -// tags: -// { creation_time: '1970-01-01T00:00:00.000000Z', -// language: 'und', -// handler_name: 'VideoHandler' } }, -// { index: 1, -// codec_name: 'aac', -// codec_long_name: 'AAC (Advanced Audio Coding)', -// profile: 'LC', -// codec_type: 'audio', -// codec_time_base: '1/48000', -// codec_tag_string: 'mp4a', -// codec_tag: '0x6134706d', -// sample_fmt: 'fltp', -// sample_rate: '48000', -// channels: 6, -// channel_layout: '5.1', -// bits_per_sample: 0, -// r_frame_rate: '0/0', -// avg_frame_rate: '0/0', -// time_base: '1/48000', -// start_pts: 0, -// start_time: '0.000000', -// duration_ts: 254976, -// duration: '5.312000', -// bit_rate: '384828', -// max_bit_rate: '400392', -// nb_frames: '249', -// disposition: -// { default: 1, -// dub: 0, -// original: 0, -// comment: 0, -// lyrics: 0, -// karaoke: 0, -// forced: 0, -// hearing_impaired: 0, -// visual_impaired: 0, -// clean_effects: 0, -// attached_pic: 0, -// timed_thumbnails: 0 }, -// tags: -// { creation_time: '1970-01-01T00:00:00.000000Z', -// language: 'und', -// handler_name: 'SoundHandler' } } ] }, -// ffProbeRead: 'success', -// file: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// fileMedium: 'video', -// file_size: 1.056519, -// meta: -// { SourceFile: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// errors: [], -// Duration: 5.312, -// PreviewDuration: 0, -// SelectionDuration: 0, -// TrackDuration: 5.28, -// MediaDuration: 5.312, -// ExifToolVersion: 11.65, -// FileName: 'Sample.mp4', -// Directory: 'C:/Users/H/Desktop/Test Input1', -// FileSize: '1032 kB', -// FileModifyDate: -// { year: 2019, -// month: 9, -// day: 24, -// hour: 7, -// minute: 24, -// second: 22, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:24 07:24:22+01:00' }, -// FileAccessDate: -// { year: 2019, -// month: 9, -// day: 26, -// hour: 7, -// minute: 44, -// second: 30, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:26 07:44:30+01:00' }, -// FileCreateDate: -// { year: 2019, -// month: 9, -// day: 26, -// hour: 7, -// minute: 44, -// second: 30, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:26 07:44:30+01:00' }, -// FilePermissions: 'rw-rw-rw-', -// FileType: 'MP4', -// FileTypeExtension: 'mp4', -// MIMEType: 'video/mp4', -// MajorBrand: 'MP4 Base Media v1 [IS0 14496-12:2003]', -// MinorVersion: '0.2.0', -// CompatibleBrands: [ 'isom', 'iso2', 'avc1', 'mp41' ], -// MovieDataSize: 0, -// MovieDataOffset: 1051515, -// MovieHeaderVersion: 0, -// CreateDate: -// { year: 1970, -// month: 1, -// day: 8, -// hour: 0, -// minute: 0, -// second: 0, -// millisecond: 0, -// rawValue: '1970:01:08 00:00:00' }, -// ModifyDate: -// { year: 2014, -// month: 7, -// day: 19, -// hour: 17, -// minute: 15, -// second: 29, -// millisecond: 0, -// rawValue: '2014:07:19 17:15:29' }, -// TimeScale: 1000, -// PreferredRate: 1, -// PreferredVolume: '100.00%', -// PreviewTime: '0 s', -// PosterTime: '0 s', -// SelectionTime: '0 s', -// CurrentTime: '0 s', -// NextTrackID: 3, -// TrackHeaderVersion: 0, -// TrackCreateDate: '0000:00:00 00:00:00', -// TrackModifyDate: '0000:00:00 00:00:00', -// TrackID: 1, -// TrackLayer: 0, -// TrackVolume: '0.00%', -// ImageWidth: 1280, -// ImageHeight: 720, -// GraphicsMode: 'srcCopy', -// OpColor: '0 0 0', -// CompressorID: 'avc1', -// SourceImageWidth: 1280, -// SourceImageHeight: 720, -// XResolution: 72, -// YResolution: 72, -// BitDepth: 24, -// VideoFrameRate: 25, -// MatrixStructure: '1 0 0 0 1 0 0 0 1', -// MediaHeaderVersion: 0, -// MediaCreateDate: '0000:00:00 00:00:00', -// MediaModifyDate: '0000:00:00 00:00:00', -// MediaTimeScale: 48000, -// MediaLanguageCode: 'und', -// HandlerDescription: 'SoundHandler', -// Balance: 0, -// AudioFormat: 'mp4a', -// AudioChannels: 2, -// AudioBitsPerSample: 16, -// AudioSampleRate: 48000, -// HandlerType: 'Metadata', -// HandlerVendorID: 'Apple', -// Encoder: 'Lavf53.24.2', -// Title: 'Sample title test', -// Composer: 'th', -// BeatsPerMinute: '', -// ContentCreateDate: 2018, -// Genre: 'this', -// Artist: 'hhj', -// Comment: 'hhk', -// Subtitle: 'jj', -// Mood: 'lik', -// ContentDistributor: 'cont', -// Conductor: 'jo', -// Writer: 'writ', -// InitialKey: 'ho', -// Producer: 'prod', -// ParentalRating: 'par', -// Director: 'dir', -// Period: 'pol', -// Publisher: 'pub', -// PromotionURL: 'prom', -// AuthorURL: 'auth', -// EncodedBy: 'enc', -// Category: 'h', -// ImageSize: '1280x720', -// Megapixels: 0.922, -// AvgBitrate: '1.58 Mbps', -// Rotation: 0 }, -// processingStatus: false, -// video_codec_name: 'h264', -// video_resolution: '720p' } diff --git a/Tdarr_Plugin_zzzz_Post_Proc_Example.js b/Tdarr_Plugin_zzzz_Post_Proc_Example.js deleted file mode 100644 index e594e68..0000000 --- a/Tdarr_Plugin_zzzz_Post_Proc_Example.js +++ /dev/null @@ -1,303 +0,0 @@ -// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs: -module.exports.dependencies = [ - 'import-fresh', -]; - -const details = () => { - return { - id: 'Tdarr_Plugin_zzzz_Post_Proc_Example', - Stage: 'Post-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed. This plugin does some stuff after all plugins have been executed - Name: 'Post proc ', - Type: 'Video', - Operation: '', - Description: 'This plugin does some stuff after all plugins have been executed. \n\n', - Version: '1.00', - Link: 'https://github.com/HaveAGitGat/Tdarr_Plugin_aaaa_Post_Proc_Example', - Tags: 'ffmpeg,h265', // Provide tags to categorise your plugin in the plugin browser.Tag options: h265,hevc,h264,nvenc h265,nvenc h264,video only,audio only,subtitle only,handbrake,ffmpeg,radarr,sonarr,pre-processing,post-processing,configurable - - Inputs: [ - // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI - { - name: 'language', - tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep. - - \\nExample:\\n - eng - - \\nExample:\\n - fr - - \\nExample:\\n - de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks - }, - { - name: 'channels', - tooltip: `Desired audio channel number. - - \\nExample:\\n - 2`, - }, - ], - }; -}; - -const plugin = (file, librarySettings, inputs, otherArguments) => { - // Only 'require' dependencies within this function or other functions. Do not require in the top scope. - const importFresh = require('import-fresh'); - - console.log( - 'Transcode success! Now do some stuff with the newly scanned file.', - ); - - // Optional response if you need to modify database - const response = { - file, - removeFromDB: false, - updateDB: false, - }; - - return response; -}; - -// Example file object: -// { -// _id: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// DB: 'ZRPDmnmpyuAEQi7nG', -// HealthCheck: 'Not attempted', -// TranscodeDecisionMaker: 'Not attempted', -// bit_rate: 1690430.4, -// container: 'mp4', -// createdAt: 2019-09-26T06:46:31.929Z, -// ffProbeData: -// { streams: -// [ { index: 0, -// codec_name: 'h264', -// codec_long_name: 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10', -// profile: 'Main', -// codec_type: 'video', -// codec_time_base: '1/50', -// codec_tag_string: 'avc1', -// codec_tag: '0x31637661', -// width: 1280, -// height: 720, -// coded_width: 1280, -// coded_height: 720, -// has_b_frames: 0, -// sample_aspect_ratio: '1:1', -// display_aspect_ratio: '16:9', -// pix_fmt: 'yuv420p', -// level: 31, -// chroma_location: 'left', -// refs: 1, -// is_avc: 'true', -// nal_length_size: '4', -// r_frame_rate: '25/1', -// avg_frame_rate: '25/1', -// time_base: '1/12800', -// start_pts: 0, -// start_time: '0.000000', -// duration_ts: 67584, -// duration: '5.280000', -// bit_rate: '1205959', -// bits_per_raw_sample: '8', -// nb_frames: '132', -// disposition: -// { default: 1, -// dub: 0, -// original: 0, -// comment: 0, -// lyrics: 0, -// karaoke: 0, -// forced: 0, -// hearing_impaired: 0, -// visual_impaired: 0, -// clean_effects: 0, -// attached_pic: 0, -// timed_thumbnails: 0 }, -// tags: -// { creation_time: '1970-01-01T00:00:00.000000Z', -// language: 'und', -// handler_name: 'VideoHandler' } }, -// { index: 1, -// codec_name: 'aac', -// codec_long_name: 'AAC (Advanced Audio Coding)', -// profile: 'LC', -// codec_type: 'audio', -// codec_time_base: '1/48000', -// codec_tag_string: 'mp4a', -// codec_tag: '0x6134706d', -// sample_fmt: 'fltp', -// sample_rate: '48000', -// channels: 6, -// channel_layout: '5.1', -// bits_per_sample: 0, -// r_frame_rate: '0/0', -// avg_frame_rate: '0/0', -// time_base: '1/48000', -// start_pts: 0, -// start_time: '0.000000', -// duration_ts: 254976, -// duration: '5.312000', -// bit_rate: '384828', -// max_bit_rate: '400392', -// nb_frames: '249', -// disposition: -// { default: 1, -// dub: 0, -// original: 0, -// comment: 0, -// lyrics: 0, -// karaoke: 0, -// forced: 0, -// hearing_impaired: 0, -// visual_impaired: 0, -// clean_effects: 0, -// attached_pic: 0, -// timed_thumbnails: 0 }, -// tags: -// { creation_time: '1970-01-01T00:00:00.000000Z', -// language: 'und', -// handler_name: 'SoundHandler' } } ] }, -// ffProbeRead: 'success', -// file: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// fileMedium: 'video', -// file_size: 1.056519, -// meta: -// { SourceFile: 'C:/Users/H/Desktop/Test Input1/Sample.mp4', -// errors: [], -// Duration: 5.312, -// PreviewDuration: 0, -// SelectionDuration: 0, -// TrackDuration: 5.28, -// MediaDuration: 5.312, -// ExifToolVersion: 11.65, -// FileName: 'Sample.mp4', -// Directory: 'C:/Users/H/Desktop/Test Input1', -// FileSize: '1032 kB', -// FileModifyDate: -// { year: 2019, -// month: 9, -// day: 24, -// hour: 7, -// minute: 24, -// second: 22, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:24 07:24:22+01:00' }, -// FileAccessDate: -// { year: 2019, -// month: 9, -// day: 26, -// hour: 7, -// minute: 44, -// second: 30, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:26 07:44:30+01:00' }, -// FileCreateDate: -// { year: 2019, -// month: 9, -// day: 26, -// hour: 7, -// minute: 44, -// second: 30, -// millisecond: 0, -// tzoffsetMinutes: 60, -// rawValue: '2019:09:26 07:44:30+01:00' }, -// FilePermissions: 'rw-rw-rw-', -// FileType: 'MP4', -// FileTypeExtension: 'mp4', -// MIMEType: 'video/mp4', -// MajorBrand: 'MP4 Base Media v1 [IS0 14496-12:2003]', -// MinorVersion: '0.2.0', -// CompatibleBrands: [ 'isom', 'iso2', 'avc1', 'mp41' ], -// MovieDataSize: 0, -// MovieDataOffset: 1051515, -// MovieHeaderVersion: 0, -// CreateDate: -// { year: 1970, -// month: 1, -// day: 8, -// hour: 0, -// minute: 0, -// second: 0, -// millisecond: 0, -// rawValue: '1970:01:08 00:00:00' }, -// ModifyDate: -// { year: 2014, -// month: 7, -// day: 19, -// hour: 17, -// minute: 15, -// second: 29, -// millisecond: 0, -// rawValue: '2014:07:19 17:15:29' }, -// TimeScale: 1000, -// PreferredRate: 1, -// PreferredVolume: '100.00%', -// PreviewTime: '0 s', -// PosterTime: '0 s', -// SelectionTime: '0 s', -// CurrentTime: '0 s', -// NextTrackID: 3, -// TrackHeaderVersion: 0, -// TrackCreateDate: '0000:00:00 00:00:00', -// TrackModifyDate: '0000:00:00 00:00:00', -// TrackID: 1, -// TrackLayer: 0, -// TrackVolume: '0.00%', -// ImageWidth: 1280, -// ImageHeight: 720, -// GraphicsMode: 'srcCopy', -// OpColor: '0 0 0', -// CompressorID: 'avc1', -// SourceImageWidth: 1280, -// SourceImageHeight: 720, -// XResolution: 72, -// YResolution: 72, -// BitDepth: 24, -// VideoFrameRate: 25, -// MatrixStructure: '1 0 0 0 1 0 0 0 1', -// MediaHeaderVersion: 0, -// MediaCreateDate: '0000:00:00 00:00:00', -// MediaModifyDate: '0000:00:00 00:00:00', -// MediaTimeScale: 48000, -// MediaLanguageCode: 'und', -// HandlerDescription: 'SoundHandler', -// Balance: 0, -// AudioFormat: 'mp4a', -// AudioChannels: 2, -// AudioBitsPerSample: 16, -// AudioSampleRate: 48000, -// HandlerType: 'Metadata', -// HandlerVendorID: 'Apple', -// Encoder: 'Lavf53.24.2', -// Title: 'Sample title test', -// Composer: 'th', -// BeatsPerMinute: '', -// ContentCreateDate: 2018, -// Genre: 'this', -// Artist: 'hhj', -// Comment: 'hhk', -// Subtitle: 'jj', -// Mood: 'lik', -// ContentDistributor: 'cont', -// Conductor: 'jo', -// Writer: 'writ', -// InitialKey: 'ho', -// Producer: 'prod', -// ParentalRating: 'par', -// Director: 'dir', -// Period: 'pol', -// Publisher: 'pub', -// PromotionURL: 'prom', -// AuthorURL: 'auth', -// EncodedBy: 'enc', -// Category: 'h', -// ImageSize: '1280x720', -// Megapixels: 0.922, -// AvgBitrate: '1.58 Mbps', -// Rotation: 0 }, -// processingStatus: false, -// video_codec_name: 'h264', -// video_resolution: '720p' } diff --git a/examples/Tdarr_Plugin_a9he_New_file_size_check.js b/examples/Tdarr_Plugin_a9he_New_file_size_check.js index 63d4789..bf672a0 100644 --- a/examples/Tdarr_Plugin_a9he_New_file_size_check.js +++ b/examples/Tdarr_Plugin_a9he_New_file_size_check.js @@ -9,8 +9,8 @@ const details = () => ({ Operation: 'Transcode', Description: 'Give an error if new file is larger than the original \n\n', Version: '1.00', - Link: '', Tags: '', + Inputs: [], }); const plugin = (file, librarySettings, inputs, otherArguments) => { diff --git a/Tdarr_Plugin_bbbb_Filter_Example.js b/examples/Tdarr_Plugin_f001_Filter_Example.js similarity index 62% rename from Tdarr_Plugin_bbbb_Filter_Example.js rename to examples/Tdarr_Plugin_f001_Filter_Example.js index 8dba7ca..c2d5a56 100644 --- a/Tdarr_Plugin_bbbb_Filter_Example.js +++ b/examples/Tdarr_Plugin_f001_Filter_Example.js @@ -1,15 +1,17 @@ -const details = () => { - return { - id: 'Tdarr_Plugin_bbbb_Filter_Example', - Stage: 'Pre-processing', - Name: 'Filter keywords ', - Type: 'Video', - Operation: 'Filter', - Description: 'This plugin prevents processing files which contain keywords \n\n', - Version: '1.00', - Tags: '', - }; -}; +// eslint-disable-next-line import/no-unresolved +const loadDefaultValues = require('../methods/loadDefaultValues'); + +const details = () => ({ + id: 'Tdarr_Plugin_f001_Filter_Example', + Stage: 'Pre-processing', + Name: 'Filter keywords ', + Type: 'Video', + Operation: 'Filter', + Description: 'This plugin prevents processing files which contain keywords \n\n', + Version: '1.00', + Tags: '', + Inputs: [], +}); // eslint-disable-next-line no-unused-vars const plugin = (file, librarySettings, inputs, otherArguments) => { @@ -37,4 +39,4 @@ const plugin = (file, librarySettings, inputs, otherArguments) => { return response; }; module.exports.details = details; -module.exports.plugin = plugin; \ No newline at end of file +module.exports.plugin = plugin; diff --git a/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js b/examples/Tdarr_Plugin_f002_Filter_Example.js similarity index 95% rename from examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js rename to examples/Tdarr_Plugin_f002_Filter_Example.js index 3c18a14..b29d518 100644 --- a/examples/filters/Tdarr_Plugin_bbbc_Filter_Example.js +++ b/examples/Tdarr_Plugin_f002_Filter_Example.js @@ -2,15 +2,15 @@ const loadDefaultValues = require('../methods/loadDefaultValues'); const details = () => ({ - id: 'Tdarr_Plugin_bbbc_Filter_Example', + id: 'Tdarr_Plugin_f002_Filter_Example', Stage: 'Pre-processing', Name: 'Filter resolutions', Type: 'Video', Operation: 'Filter', Description: 'This plugin prevents processing files with specified resolutions \n\n', Version: '1.00', - Link: '', Tags: '', + Inputs: [], }); // eslint-disable-next-line no-unused-vars diff --git a/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js b/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js new file mode 100644 index 0000000..343e3cd --- /dev/null +++ b/examples/Tdarr_Plugin_pos1_Post_Proc_Example.js @@ -0,0 +1,83 @@ +// Rules disabled for example purposes +/* eslint max-len: 0 */ // --> OFF +/* eslint no-unused-vars: 0 */ // --> OFF +/* eslint global-require: 0 */ // --> OFF +/* eslint import/no-extraneous-dependencies: 0 */ // --> OFF +/* eslint no-console: 0 */ // --> OFF + +// eslint-disable-next-line import/no-unresolved +const loadDefaultValues = require('../methods/loadDefaultValues'); + +// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs: +module.exports.dependencies = [ + 'import-fresh', +]; + +const details = () => ({ + id: 'Tdarr_Plugin_pos1_Post_Proc_Example', + Stage: 'Post-processing', // Preprocessing or Post-processing. Determines when the plugin will be executed. This plugin does some stuff after all plugins have been executed + Name: 'Post proc ', + Type: 'Video', + Operation: 'Transcode', + Description: 'This plugin does some stuff after all plugins have been executed. \n\n', + Version: '1.00', + Tags: 'ffmpeg,h265', // Provide tags to categorise your plugin in the plugin browser.Tag options: h265,hevc,h264,nvenc h265,nvenc h264,video only,audio only,subtitle only,handbrake,ffmpeg,radarr,sonarr,pre-processing,post-processing,configurable + + Inputs: [ + // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI + { + name: 'language', + type: 'string', + defaultValue: 'eng', + inputUI: { + type: 'text', + }, + tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep. + + \\nExample:\\n + eng + + \\nExample:\\n + fr + + \\nExample:\\n + de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks + }, + { + name: 'channels', + type: 'string', + defaultValue: 'eng', + inputUI: { + type: 'text', + }, + tooltip: `Desired audio channel number. + + \\nExample:\\n + 2`, + }, + ], +}); + +// eslint-disable-next-line no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + // eslint-disable-next-line no-unused-vars,no-param-reassign + inputs = loadDefaultValues(inputs, details); + + // Only 'require' dependencies within this function or other functions. Do not require in the top scope. + const importFresh = require('import-fresh'); + + console.log( + 'Transcode success! Now do some stuff with the newly scanned file.', + ); + + // Optional response if you need to modify database + const response = { + file, + removeFromDB: false, + updateDB: false, + }; + + return response; +}; +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js b/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js new file mode 100644 index 0000000..30c0d8f --- /dev/null +++ b/examples/Tdarr_Plugin_pre1_Pre_Proc_Example.js @@ -0,0 +1,163 @@ +// Rules disabled for example purposes +/* eslint max-len: 0 */ // --> OFF +/* eslint no-unused-vars: 0 */ // --> OFF +/* eslint global-require: 0 */ // --> OFF +/* eslint import/no-extraneous-dependencies: 0 */ // --> OFF +/* eslint no-console: 0 */ // --> OFF + +const loadDefaultValues = require('../methods/loadDefaultValues'); +// List any npm dependencies which the plugin needs, they will be auto installed when the plugin runs: +module.exports.dependencies = [ + 'import-fresh', +]; + +const details = () => ({ + id: 'Tdarr_Plugin_pre1_Pre_Proc_Example', + Stage: 'Pre-processing', // Pre-processing or Post-processing. Determines when the plugin will be executed. + Name: 'No title meta data ', + Type: 'Video', + Operation: 'Transcode', + Description: 'This plugin removes metadata (if a title exists). The output container is the same as the original. \n\n', + Version: '1.00', + Tags: 'ffmpeg,h265', // Provide tags to categorise your plugin in the plugin browser.Tag options: h265,hevc,h264,nvenc h265,nvenc h264,video only,audio only,subtitle only,handbrake,ffmpeg,radarr,sonarr,pre-processing,post-processing,configurable + + Inputs: [ + // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI + { + name: 'language', + type: 'string', // set the data type of the input ('string', 'number', 'boolean') + defaultValue: 'eng', // set the default value of the input incase the user enters no input + inputUI: { + type: 'text', // specify how the input UI will appear to the user ('text' or 'dropdown') + }, + // inputUI: { // dropdown inputUI example + // type: 'dropdown', + // options: [ + // 'false', + // 'true', + // ], + // }, + tooltip: `Enter one language tag here for the language of the subtitles you'd like to keep. + + \\nExample:\\n + eng + + \\nExample:\\n + + fr + + \\nExample:\\n + + de`, // Each line following `Example:` will be clearly formatted. \\n used for line breaks + }, + { + name: 'channels', + type: 'number', + defaultValue: 2, + inputUI: { + type: 'dropdown', + options: [ + '1', + '2', + '6', + ], + }, + tooltip: `Desired audio channel number. + \\nExample:\\n + 2`, + }, + ], +}); + +// eslint-disable-next-line no-unused-vars +const plugin = (file, librarySettings, inputs, otherArguments) => { + // eslint-disable-next-line no-unused-vars,no-param-reassign + inputs = loadDefaultValues(inputs, details); + // Only 'require' dependencies within this function or other functions. Do not require in the top scope. + const importFresh = require('import-fresh'); + + // Must return this object at some point in the function else plugin will fail. + + const response = { + processFile: false, // If set to false, the file will be skipped. Set to true to have the file transcoded. + preset: '', // HandBrake/FFmpeg CLI arguments you'd like to use. + // For FFmpeg, the input arguments come first followed by , followed by the output argument. + // Examples + // HandBrake + // '-Z "Very Fast 1080p30"' + // FFmpeg + // '-sn -map_metadata -1 -c:v copy -c:a copy' + container: '.mp4', // The container of the transcoded output file. + handBrakeMode: false, // Set whether to use HandBrake or FFmpeg for transcoding + FFmpegMode: false, + infoLog: '', // This will be shown when the user clicks the 'i' (info) button on a file in the output queue if + // it has been skipped. + // Give reasons why it has been skipped ('File has no title metadata, File meets conditions!') + + // Optional (include together) + file, + removeFromDB: false, // Tell Tdarr to remove file from database if true + updateDB: false, // Change file object above and update database if true + }; + + console.log(inputs.language); // eng if user entered 'eng' in input box in Tdarr plugin UI + console.log(inputs.channels); // 2 if user entered '2' in input box in Tdarr plugin UI + + // Here we specify that we want the output file container to be the same as the current container. + response.container = `.${file.container}`; + + // We will use FFmpeg for this procedure. + response.FFmpegMode = true; + + // Check if file has title metadata + if (file.meta.Title !== undefined) { + // if so, remove it + + response.infoLog += ' File has title metadata'; + response.preset = ',-map_metadata -1 -c:v copy -c:a copy'; + response.processFile = true; + return response; + } + response.infoLog += ' File has no title metadata'; + + response.infoLog += ' File meets conditions!'; + return response; +}; + +module.exports.onTranscodeSuccess = function onTranscodeSuccess( + file, + librarySettings, + inputs, +) { + console.log( + 'Transcode success! Now do some stuff with the newly scanned file.', + ); + + // Optional response if you need to modify database + const response = { + file, + removeFromDB: false, + updateDB: false, + }; + + return response; +}; + +module.exports.onTranscodeError = function onTranscodeError( + file, + librarySettings, + inputs, +) { + console.log('Transcode fail! Now do some stuff with the original file.'); + + // Optional response if you need to modify database + const response = { + file, + removeFromDB: false, + updateDB: false, + }; + + return response; +}; +module.exports.details = details; +module.exports.plugin = plugin; diff --git a/tests/checkPlugins.js b/tests/checkPlugins.js index 65eba49..e7171dd 100644 --- a/tests/checkPlugins.js +++ b/tests/checkPlugins.js @@ -2,158 +2,170 @@ const fs = require('fs'); -const files = fs.readdirSync('./Community'); - -const detailsOrder = [ - 'id', - 'Stage', - 'Name', - 'Type', - 'Operation', - 'Description', - 'Version', - 'Tags', - 'Inputs', +const folders = [ + './Community', + './examples', ]; -const pluginInputTypes = ['string', 'number', 'boolean']; -for (let i = 0; i < files.length; i += 1) { - console.log(`${files[i]}`); +folders.forEach((folder) => { + const files = fs.readdirSync(folder).filter((row) => row.includes('.js')); + + const detailsOrder = [ + 'id', + 'Stage', + 'Name', + 'Type', + 'Operation', + 'Description', + 'Version', + 'Tags', + 'Inputs', + ]; + const pluginInputTypes = ['string', 'number', 'boolean']; + + for (let i = 0; i < files.length; i += 1) { + let read = fs.readFileSync(`${folder}/${files[i]}`).toString(); + + const importDefaultValues = 'const loadDefaultValues = require(\'../methods/loadDefaultValues\');'; + if (!read.includes(importDefaultValues)) { + console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${importDefaultValues}`); + read = `${importDefaultValues}\n${read}`; + // fs.writeFileSync(`${folder}/${files[i]}`, read) + process.exit(1); + } - let read = fs.readFileSync(`./Community/${files[i]}`).toString(); + const detailsText = 'const details = () =>'; + if (!read.includes(detailsText)) { + console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${detailsText}`); + process.exit(1); + } - const importDefaultValues = 'const loadDefaultValues = require(\'../methods/loadDefaultValues\');'; - if (!read.includes(importDefaultValues)) { - console.log(`Plugin error: './Community/${files[i]}' does not contain ${importDefaultValues}`); - read = `${importDefaultValues}\n${read}`; - // fs.writeFileSync(`./Community/${files[i]}`, read) - process.exit(1); - } + const syncText = 'const plugin = (file, librarySettings, inputs, otherArguments) => {'; + const asyncText = 'const plugin = async (file, librarySettings, inputs, otherArguments) => {'; - const detailsText = 'const details = () =>'; - if (!read.includes(detailsText)) { - console.log(`Plugin error: './Community/${files[i]}' does not contain ${detailsText}`); - process.exit(1); - } + if (!read.includes(syncText) + && !read.includes(asyncText) + ) { + console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${syncText} or ${asyncText}`); + process.exit(1); + } - const syncText = 'const plugin = (file, librarySettings, inputs, otherArguments) => {'; - const asyncText = 'const plugin = async (file, librarySettings, inputs, otherArguments) => {'; + const inputsText = 'inputs = loadDefaultValues(inputs, details);'; + if (!read.includes(inputsText) + ) { + console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${inputsText}`); + process.exit(1); + } - if (!read.includes(syncText) - && !read.includes(asyncText) - ) { - console.log(`Plugin error: './Community/${files[i]}' does not contain ${syncText} or ${asyncText}`); - process.exit(1); - } + const exportText = `module.exports.details = details; +module.exports.plugin = plugin;`; - const inputsText = 'inputs = loadDefaultValues(inputs, details);'; - if (!read.includes(inputsText) - ) { - console.log(`Plugin error: './Community/${files[i]}' does not contain ${inputsText}`); - process.exit(1); - } + if (!read.includes(exportText)) { + console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${exportText}`); + read = read.replace('module.exports.details = details;', ''); + read = read.replace('module.exports.plugin = plugin;', ''); + read += `\n${exportText}`; + // fs.writeFileSync(`${folder}/${files[i]}`, read) + process.exit(1); + } - const exportText = `module.exports.details = details; -module.exports.plugin = plugin;`; + let pluginDetails; + try { + // eslint-disable-next-line import/no-dynamic-require,global-require + pluginDetails = require(`.${folder}/${files[i]}`).details(); + } catch (err) { + console.log(err.message); + process.exit(1); + } - if (!read.includes(exportText)) { - console.log(`Plugin error: './Community/${files[i]}' does not contain ${exportText}`); - read = read.replace('module.exports.details = details;', ''); - read = read.replace('module.exports.plugin = plugin;', ''); - read += `\n${exportText}`; - // fs.writeFileSync(`./Community/${files[i]}`, read) - process.exit(1); - } + const detailsKeys = Object.keys(pluginDetails); - let pluginDetails; - try { - // eslint-disable-next-line import/no-dynamic-require,global-require - pluginDetails = require(`../Community/${files[i]}`).details(); - } catch (err) { - console.log(err.message); - process.exit(1); - } + detailsOrder.forEach((detail) => { + if (detailsKeys.indexOf(detail) === -1) { + console.log(`Plugin details is missing '${folder}/${files[i]}' : ${detail}`); + process.exit(1); + } + }); - const detailsKeys = Object.keys(pluginDetails); + detailsKeys.forEach((detail, index) => { + if (detailsOrder[index] !== detail) { + console.log(`Plugin details keys are not in the correct order: '${folder}/${files[i]}' ${detail}`); + process.exit(1); + } + }); - detailsOrder.forEach((detail) => { - if (detailsKeys.indexOf(detail) === -1) { - console.log(`Plugin details is missing './Community/${files[i]}' : ${detail}`); + if (detailsKeys.length < detailsOrder.length) { + console.log(`Plugin details are too few '${folder}/${files[i]}'`); process.exit(1); } - }); - detailsKeys.forEach((detail, index) => { - if (detailsOrder[index] !== detail) { - console.log(`Plugin details keys are not in the correct order: './Community/${files[i]}' ${detail}`); + if (!['Pre-processing', 'Post-processing'].includes(pluginDetails.Stage)) { + console.log(`Plugin does not have a valid Type'${folder}/${files[i]}'`); process.exit(1); } - }); - - if (detailsKeys.length < detailsOrder.length) { - console.log(`Plugin details are too few './Community/${files[i]}'`); - process.exit(1); - } - - if (!['Pre-processing', 'Post-processing'].includes(pluginDetails.Stage)) { - console.log(`Plugin does not have a valid Type'./Community/${files[i]}'`); - process.exit(1); - } - if (!['Video', 'Audio', 'Subtitle', 'Any'].includes(pluginDetails.Type)) { - console.log(`Plugin does not have a valid Type'./Community/${files[i]}'`); - process.exit(1); - } + if (!['Video', 'Audio', 'Subtitle', 'Any'].includes(pluginDetails.Type)) { + console.log(`Plugin does not have a valid Type'${folder}/${files[i]}'`); + process.exit(1); + } - if (!['Transcode', 'Filter'].includes(pluginDetails.Operation)) { - console.log(`Plugin does not have a valid Operation './Community/${files[i]}'`); - process.exit(1); - } else if (detailsKeys.length > detailsOrder.length) { - console.log(`Plugin details are too many './Community/${files[i]}'`); - process.exit(1); - } else if (pluginDetails.Inputs && !Array.isArray(pluginDetails.Inputs)) { - // Check default values are set; - console.log(`Plugin Inputs is not an array: ${files[i]}`); - process.exit(1); - } else if (pluginDetails.Inputs && Array.isArray(pluginDetails.Inputs)) { - const inputs = pluginDetails.Inputs; - const savedInputs = {}; - for (let j = 0; j < inputs.length; j += 1) { - // Prevent duplicate plugin inputs - if (savedInputs[inputs[j].name] === true) { - console.log(`Plugin Input already exists: './Community/${files[i]}' : ${inputs[j].name}`); - process.exit(1); - } else { - savedInputs[inputs[j].name] = true; - } + if (files[i].split('.js').join('') !== pluginDetails.id) { + console.log(`Plugin file name does not match details id'${folder}/${files[i]}'`); + process.exit(1); + } - const inputKeys = Object.keys(inputs[j]); - if ( - inputKeys[0] !== 'name' - || inputKeys[1] !== 'type' - || inputKeys[2] !== 'defaultValue' - || inputKeys[3] !== 'inputUI' - || inputKeys[4] !== 'tooltip' - ) { - console.log(`Plugin Input keys are not in correct order: './Community/${files[i]}' : ${inputs[j].name}`); - process.exit(1); - } else if (inputs[j].type === undefined || !pluginInputTypes.includes(inputs[j].type)) { - console.log(`Plugin Input does not have a type: './Community/${files[i]}' : ${inputs[j].name}`); - process.exit(1); - } else if ( - (inputs[j].type === 'string' && typeof inputs[j].defaultValue !== 'string') - || (inputs[j].type === 'number' && typeof inputs[j].defaultValue !== 'number') - || (inputs[j].type === 'boolean' && typeof inputs[j].defaultValue !== 'boolean') - ) { - console.log(`Plugin Input type does not match defaultValue type: - './Community/${files[i]}' : ${inputs[j].name}`); - process.exit(1); - } else if (inputs[j].defaultValue === undefined) { - console.log(`Plugin Input does not have a default value: './Community/${files[i]}' : ${inputs[j].name}`); - process.exit(1); + if (!['Transcode', 'Filter'].includes(pluginDetails.Operation)) { + console.log(`Plugin does not have a valid Operation '${folder}/${files[i]}'`); + process.exit(1); + } else if (detailsKeys.length > detailsOrder.length) { + console.log(`Plugin details are too many '${folder}/${files[i]}'`); + process.exit(1); + } else if (pluginDetails.Inputs && !Array.isArray(pluginDetails.Inputs)) { + // Check default values are set; + console.log(`Plugin Inputs is not an array: ${files[i]}`); + process.exit(1); + } else if (pluginDetails.Inputs && Array.isArray(pluginDetails.Inputs)) { + const inputs = pluginDetails.Inputs; + const savedInputs = {}; + for (let j = 0; j < inputs.length; j += 1) { + // Prevent duplicate plugin inputs + if (savedInputs[inputs[j].name] === true) { + console.log(`Plugin Input already exists: '${folder}/${files[i]}' : ${inputs[j].name}`); + process.exit(1); + } else { + savedInputs[inputs[j].name] = true; + } + + const inputKeys = Object.keys(inputs[j]); + if ( + inputKeys[0] !== 'name' + || inputKeys[1] !== 'type' + || inputKeys[2] !== 'defaultValue' + || inputKeys[3] !== 'inputUI' + || inputKeys[4] !== 'tooltip' + ) { + console.log(`Plugin Input keys are not in correct order: '${folder}/${files[i]}' : ${inputs[j].name}`); + process.exit(1); + } else if (inputs[j].type === undefined || !pluginInputTypes.includes(inputs[j].type)) { + console.log(`Plugin Input does not have a type: '${folder}/${files[i]}' : ${inputs[j].name}`); + process.exit(1); + } else if ( + (inputs[j].type === 'string' && typeof inputs[j].defaultValue !== 'string') + || (inputs[j].type === 'number' && typeof inputs[j].defaultValue !== 'number') + || (inputs[j].type === 'boolean' && typeof inputs[j].defaultValue !== 'boolean') + ) { + console.log(`Plugin Input type does not match defaultValue type: + '${folder}/${files[i]}' : ${inputs[j].name}`); + process.exit(1); + } else if (inputs[j].defaultValue === undefined) { + console.log(`Plugin Input does not have a default value: '${folder}/${files[i]}' : ${inputs[j].name}`); + process.exit(1); + } } } + + console.log(`[✓]${folder}/${files[i]}`); } -} +}); console.log('Done!');