Check plugins (#221)

* Include examples in checkPlugins. Fix errors and lint.

* Remove on push

* Show tick for successful tests
make-only-subtitle-default
HaveAGitGat 4 years ago committed by GitHub
parent 31895e8afc
commit 9bbc4fbec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1,6 @@
name: Node.js CI name: Node.js CI
on: on:
push:
branches: ['**']
pull_request: pull_request:
branches: ['**'] branches: ['**']

@ -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 <io>, followed by the output argument.
// Examples
// HandBrake
// '-Z "Very Fast 1080p30"'
// FFmpeg
// '-sn <io> -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' }

@ -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' }

@ -9,8 +9,8 @@ const details = () => ({
Operation: 'Transcode', Operation: 'Transcode',
Description: 'Give an error if new file is larger than the original \n\n', Description: 'Give an error if new file is larger than the original \n\n',
Version: '1.00', Version: '1.00',
Link: '',
Tags: '', Tags: '',
Inputs: [],
}); });
const plugin = (file, librarySettings, inputs, otherArguments) => { const plugin = (file, librarySettings, inputs, otherArguments) => {

@ -1,6 +1,8 @@
const details = () => { // eslint-disable-next-line import/no-unresolved
return { const loadDefaultValues = require('../methods/loadDefaultValues');
id: 'Tdarr_Plugin_bbbb_Filter_Example',
const details = () => ({
id: 'Tdarr_Plugin_f001_Filter_Example',
Stage: 'Pre-processing', Stage: 'Pre-processing',
Name: 'Filter keywords ', Name: 'Filter keywords ',
Type: 'Video', Type: 'Video',
@ -8,8 +10,8 @@ const details = () => {
Description: 'This plugin prevents processing files which contain keywords \n\n', Description: 'This plugin prevents processing files which contain keywords \n\n',
Version: '1.00', Version: '1.00',
Tags: '', Tags: '',
}; Inputs: [],
}; });
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const plugin = (file, librarySettings, inputs, otherArguments) => { const plugin = (file, librarySettings, inputs, otherArguments) => {

@ -2,15 +2,15 @@
const loadDefaultValues = require('../methods/loadDefaultValues'); const loadDefaultValues = require('../methods/loadDefaultValues');
const details = () => ({ const details = () => ({
id: 'Tdarr_Plugin_bbbc_Filter_Example', id: 'Tdarr_Plugin_f002_Filter_Example',
Stage: 'Pre-processing', Stage: 'Pre-processing',
Name: 'Filter resolutions', Name: 'Filter resolutions',
Type: 'Video', Type: 'Video',
Operation: 'Filter', Operation: 'Filter',
Description: 'This plugin prevents processing files with specified resolutions \n\n', Description: 'This plugin prevents processing files with specified resolutions \n\n',
Version: '1.00', Version: '1.00',
Link: '',
Tags: '', Tags: '',
Inputs: [],
}); });
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars

@ -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;

@ -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 <io>, followed by the output argument.
// Examples
// HandBrake
// '-Z "Very Fast 1080p30"'
// FFmpeg
// '-sn <io> -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;

@ -2,9 +2,15 @@
const fs = require('fs'); const fs = require('fs');
const files = fs.readdirSync('./Community'); const folders = [
'./Community',
'./examples',
];
folders.forEach((folder) => {
const files = fs.readdirSync(folder).filter((row) => row.includes('.js'));
const detailsOrder = [ const detailsOrder = [
'id', 'id',
'Stage', 'Stage',
'Name', 'Name',
@ -14,25 +20,23 @@ const detailsOrder = [
'Version', 'Version',
'Tags', 'Tags',
'Inputs', 'Inputs',
]; ];
const pluginInputTypes = ['string', 'number', 'boolean']; const pluginInputTypes = ['string', 'number', 'boolean'];
for (let i = 0; i < files.length; i += 1) { for (let i = 0; i < files.length; i += 1) {
console.log(`${files[i]}`); let read = fs.readFileSync(`${folder}/${files[i]}`).toString();
let read = fs.readFileSync(`./Community/${files[i]}`).toString();
const importDefaultValues = 'const loadDefaultValues = require(\'../methods/loadDefaultValues\');'; const importDefaultValues = 'const loadDefaultValues = require(\'../methods/loadDefaultValues\');';
if (!read.includes(importDefaultValues)) { if (!read.includes(importDefaultValues)) {
console.log(`Plugin error: './Community/${files[i]}' does not contain ${importDefaultValues}`); console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${importDefaultValues}`);
read = `${importDefaultValues}\n${read}`; read = `${importDefaultValues}\n${read}`;
// fs.writeFileSync(`./Community/${files[i]}`, read) // fs.writeFileSync(`${folder}/${files[i]}`, read)
process.exit(1); process.exit(1);
} }
const detailsText = 'const details = () =>'; const detailsText = 'const details = () =>';
if (!read.includes(detailsText)) { if (!read.includes(detailsText)) {
console.log(`Plugin error: './Community/${files[i]}' does not contain ${detailsText}`); console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${detailsText}`);
process.exit(1); process.exit(1);
} }
@ -42,14 +46,14 @@ for (let i = 0; i < files.length; i += 1) {
if (!read.includes(syncText) if (!read.includes(syncText)
&& !read.includes(asyncText) && !read.includes(asyncText)
) { ) {
console.log(`Plugin error: './Community/${files[i]}' does not contain ${syncText} or ${asyncText}`); console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${syncText} or ${asyncText}`);
process.exit(1); process.exit(1);
} }
const inputsText = 'inputs = loadDefaultValues(inputs, details);'; const inputsText = 'inputs = loadDefaultValues(inputs, details);';
if (!read.includes(inputsText) if (!read.includes(inputsText)
) { ) {
console.log(`Plugin error: './Community/${files[i]}' does not contain ${inputsText}`); console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${inputsText}`);
process.exit(1); process.exit(1);
} }
@ -57,18 +61,18 @@ for (let i = 0; i < files.length; i += 1) {
module.exports.plugin = plugin;`; module.exports.plugin = plugin;`;
if (!read.includes(exportText)) { if (!read.includes(exportText)) {
console.log(`Plugin error: './Community/${files[i]}' does not contain ${exportText}`); console.log(`Plugin error: '${folder}/${files[i]}' does not contain ${exportText}`);
read = read.replace('module.exports.details = details;', ''); read = read.replace('module.exports.details = details;', '');
read = read.replace('module.exports.plugin = plugin;', ''); read = read.replace('module.exports.plugin = plugin;', '');
read += `\n${exportText}`; read += `\n${exportText}`;
// fs.writeFileSync(`./Community/${files[i]}`, read) // fs.writeFileSync(`${folder}/${files[i]}`, read)
process.exit(1); process.exit(1);
} }
let pluginDetails; let pluginDetails;
try { try {
// eslint-disable-next-line import/no-dynamic-require,global-require // eslint-disable-next-line import/no-dynamic-require,global-require
pluginDetails = require(`../Community/${files[i]}`).details(); pluginDetails = require(`.${folder}/${files[i]}`).details();
} catch (err) { } catch (err) {
console.log(err.message); console.log(err.message);
process.exit(1); process.exit(1);
@ -78,38 +82,43 @@ module.exports.plugin = plugin;`;
detailsOrder.forEach((detail) => { detailsOrder.forEach((detail) => {
if (detailsKeys.indexOf(detail) === -1) { if (detailsKeys.indexOf(detail) === -1) {
console.log(`Plugin details is missing './Community/${files[i]}' : ${detail}`); console.log(`Plugin details is missing '${folder}/${files[i]}' : ${detail}`);
process.exit(1); process.exit(1);
} }
}); });
detailsKeys.forEach((detail, index) => { detailsKeys.forEach((detail, index) => {
if (detailsOrder[index] !== detail) { if (detailsOrder[index] !== detail) {
console.log(`Plugin details keys are not in the correct order: './Community/${files[i]}' ${detail}`); console.log(`Plugin details keys are not in the correct order: '${folder}/${files[i]}' ${detail}`);
process.exit(1); process.exit(1);
} }
}); });
if (detailsKeys.length < detailsOrder.length) { if (detailsKeys.length < detailsOrder.length) {
console.log(`Plugin details are too few './Community/${files[i]}'`); console.log(`Plugin details are too few '${folder}/${files[i]}'`);
process.exit(1); process.exit(1);
} }
if (!['Pre-processing', 'Post-processing'].includes(pluginDetails.Stage)) { if (!['Pre-processing', 'Post-processing'].includes(pluginDetails.Stage)) {
console.log(`Plugin does not have a valid Type'./Community/${files[i]}'`); console.log(`Plugin does not have a valid Type'${folder}/${files[i]}'`);
process.exit(1); process.exit(1);
} }
if (!['Video', 'Audio', 'Subtitle', 'Any'].includes(pluginDetails.Type)) { if (!['Video', 'Audio', 'Subtitle', 'Any'].includes(pluginDetails.Type)) {
console.log(`Plugin does not have a valid Type'./Community/${files[i]}'`); console.log(`Plugin does not have a valid Type'${folder}/${files[i]}'`);
process.exit(1);
}
if (files[i].split('.js').join('') !== pluginDetails.id) {
console.log(`Plugin file name does not match details id'${folder}/${files[i]}'`);
process.exit(1); process.exit(1);
} }
if (!['Transcode', 'Filter'].includes(pluginDetails.Operation)) { if (!['Transcode', 'Filter'].includes(pluginDetails.Operation)) {
console.log(`Plugin does not have a valid Operation './Community/${files[i]}'`); console.log(`Plugin does not have a valid Operation '${folder}/${files[i]}'`);
process.exit(1); process.exit(1);
} else if (detailsKeys.length > detailsOrder.length) { } else if (detailsKeys.length > detailsOrder.length) {
console.log(`Plugin details are too many './Community/${files[i]}'`); console.log(`Plugin details are too many '${folder}/${files[i]}'`);
process.exit(1); process.exit(1);
} else if (pluginDetails.Inputs && !Array.isArray(pluginDetails.Inputs)) { } else if (pluginDetails.Inputs && !Array.isArray(pluginDetails.Inputs)) {
// Check default values are set; // Check default values are set;
@ -121,7 +130,7 @@ module.exports.plugin = plugin;`;
for (let j = 0; j < inputs.length; j += 1) { for (let j = 0; j < inputs.length; j += 1) {
// Prevent duplicate plugin inputs // Prevent duplicate plugin inputs
if (savedInputs[inputs[j].name] === true) { if (savedInputs[inputs[j].name] === true) {
console.log(`Plugin Input already exists: './Community/${files[i]}' : ${inputs[j].name}`); console.log(`Plugin Input already exists: '${folder}/${files[i]}' : ${inputs[j].name}`);
process.exit(1); process.exit(1);
} else { } else {
savedInputs[inputs[j].name] = true; savedInputs[inputs[j].name] = true;
@ -135,10 +144,10 @@ module.exports.plugin = plugin;`;
|| inputKeys[3] !== 'inputUI' || inputKeys[3] !== 'inputUI'
|| inputKeys[4] !== 'tooltip' || inputKeys[4] !== 'tooltip'
) { ) {
console.log(`Plugin Input keys are not in correct order: './Community/${files[i]}' : ${inputs[j].name}`); console.log(`Plugin Input keys are not in correct order: '${folder}/${files[i]}' : ${inputs[j].name}`);
process.exit(1); process.exit(1);
} else if (inputs[j].type === undefined || !pluginInputTypes.includes(inputs[j].type)) { } 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}`); console.log(`Plugin Input does not have a type: '${folder}/${files[i]}' : ${inputs[j].name}`);
process.exit(1); process.exit(1);
} else if ( } else if (
(inputs[j].type === 'string' && typeof inputs[j].defaultValue !== 'string') (inputs[j].type === 'string' && typeof inputs[j].defaultValue !== 'string')
@ -146,14 +155,17 @@ module.exports.plugin = plugin;`;
|| (inputs[j].type === 'boolean' && typeof inputs[j].defaultValue !== 'boolean') || (inputs[j].type === 'boolean' && typeof inputs[j].defaultValue !== 'boolean')
) { ) {
console.log(`Plugin Input type does not match defaultValue type: console.log(`Plugin Input type does not match defaultValue type:
'./Community/${files[i]}' : ${inputs[j].name}`); '${folder}/${files[i]}' : ${inputs[j].name}`);
process.exit(1); process.exit(1);
} else if (inputs[j].defaultValue === undefined) { } else if (inputs[j].defaultValue === undefined) {
console.log(`Plugin Input does not have a default value: './Community/${files[i]}' : ${inputs[j].name}`); console.log(`Plugin Input does not have a default value: '${folder}/${files[i]}' : ${inputs[j].name}`);
process.exit(1); process.exit(1);
} }
} }
} }
}
console.log(`[✓]${folder}/${files[i]}`);
}
});
console.log('Done!'); console.log('Done!');

Loading…
Cancel
Save