diff options
author | Chocobozzz <me@florianbigard.com> | 2021-01-28 15:52:44 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-01-28 15:55:39 +0100 |
commit | 1896bca09e088b0da9d5e845407ecebae330618c (patch) | |
tree | 56041c445c0cd49aca536d0fd6b586730f4d341e /server/lib/plugins/register-helpers.ts | |
parent | 529b37527cff5203a0689a15ce73dcee6e1eece2 (diff) | |
download | PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.tar.gz PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.tar.zst PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.zip |
Support transcoding options/encoders by plugins
Diffstat (limited to 'server/lib/plugins/register-helpers.ts')
-rw-r--r-- | server/lib/plugins/register-helpers.ts | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts new file mode 100644 index 000000000..3a38a4835 --- /dev/null +++ b/server/lib/plugins/register-helpers.ts | |||
@@ -0,0 +1,444 @@ | |||
1 | import * as express from 'express' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { | ||
4 | VIDEO_CATEGORIES, | ||
5 | VIDEO_LANGUAGES, | ||
6 | VIDEO_LICENCES, | ||
7 | VIDEO_PLAYLIST_PRIVACIES, | ||
8 | VIDEO_PRIVACIES | ||
9 | } from '@server/initializers/constants' | ||
10 | import { onExternalUserAuthenticated } from '@server/lib/auth' | ||
11 | import { PluginModel } from '@server/models/server/plugin' | ||
12 | import { | ||
13 | RegisterServerAuthExternalOptions, | ||
14 | RegisterServerAuthExternalResult, | ||
15 | RegisterServerAuthPassOptions, | ||
16 | RegisterServerExternalAuthenticatedResult, | ||
17 | RegisterServerOptions | ||
18 | } from '@server/types/plugins' | ||
19 | import { | ||
20 | EncoderOptionsBuilder, | ||
21 | PluginPlaylistPrivacyManager, | ||
22 | PluginSettingsManager, | ||
23 | PluginStorageManager, | ||
24 | PluginVideoCategoryManager, | ||
25 | PluginVideoLanguageManager, | ||
26 | PluginVideoLicenceManager, | ||
27 | PluginVideoPrivacyManager, | ||
28 | RegisterServerHookOptions, | ||
29 | RegisterServerSettingOptions | ||
30 | } from '@shared/models' | ||
31 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | ||
32 | import { VideoTranscodingProfilesManager } from '../video-transcoding-profiles' | ||
33 | import { buildPluginHelpers } from './plugin-helpers-builder' | ||
34 | |||
35 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' | ||
36 | type VideoConstant = { [key in number | string]: string } | ||
37 | |||
38 | type UpdatedVideoConstant = { | ||
39 | [name in AlterableVideoConstant]: { | ||
40 | added: { key: number | string, label: string }[] | ||
41 | deleted: { key: number | string, label: string }[] | ||
42 | } | ||
43 | } | ||
44 | |||
45 | export class RegisterHelpers { | ||
46 | private readonly updatedVideoConstants: UpdatedVideoConstant = { | ||
47 | playlistPrivacy: { added: [], deleted: [] }, | ||
48 | privacy: { added: [], deleted: [] }, | ||
49 | language: { added: [], deleted: [] }, | ||
50 | licence: { added: [], deleted: [] }, | ||
51 | category: { added: [], deleted: [] } | ||
52 | } | ||
53 | |||
54 | private readonly transcodingProfiles: { | ||
55 | [ npmName: string ]: { | ||
56 | type: 'vod' | 'live' | ||
57 | encoder: string | ||
58 | profile: string | ||
59 | }[] | ||
60 | } = {} | ||
61 | |||
62 | private readonly transcodingEncoders: { | ||
63 | [ npmName: string ]: { | ||
64 | type: 'vod' | 'live' | ||
65 | streamType: 'audio' | 'video' | ||
66 | encoder: string | ||
67 | priority: number | ||
68 | }[] | ||
69 | } = {} | ||
70 | |||
71 | private readonly settings: RegisterServerSettingOptions[] = [] | ||
72 | |||
73 | private idAndPassAuths: RegisterServerAuthPassOptions[] = [] | ||
74 | private externalAuths: RegisterServerAuthExternalOptions[] = [] | ||
75 | |||
76 | private readonly onSettingsChangeCallbacks: ((settings: any) => void)[] = [] | ||
77 | |||
78 | private readonly router: express.Router | ||
79 | |||
80 | constructor ( | ||
81 | private readonly npmName: string, | ||
82 | private readonly plugin: PluginModel, | ||
83 | private readonly onHookAdded: (options: RegisterServerHookOptions) => void | ||
84 | ) { | ||
85 | this.router = express.Router() | ||
86 | } | ||
87 | |||
88 | buildRegisterHelpers (): RegisterServerOptions { | ||
89 | const registerHook = this.buildRegisterHook() | ||
90 | const registerSetting = this.buildRegisterSetting() | ||
91 | |||
92 | const getRouter = this.buildGetRouter() | ||
93 | |||
94 | const settingsManager = this.buildSettingsManager() | ||
95 | const storageManager = this.buildStorageManager() | ||
96 | |||
97 | const videoLanguageManager = this.buildVideoLanguageManager() | ||
98 | |||
99 | const videoLicenceManager = this.buildVideoLicenceManager() | ||
100 | const videoCategoryManager = this.buildVideoCategoryManager() | ||
101 | |||
102 | const videoPrivacyManager = this.buildVideoPrivacyManager() | ||
103 | const playlistPrivacyManager = this.buildPlaylistPrivacyManager() | ||
104 | |||
105 | const transcodingManager = this.buildTranscodingManager() | ||
106 | |||
107 | const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth() | ||
108 | const registerExternalAuth = this.buildRegisterExternalAuth() | ||
109 | const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() | ||
110 | const unregisterExternalAuth = this.buildUnregisterExternalAuth() | ||
111 | |||
112 | const peertubeHelpers = buildPluginHelpers(this.npmName) | ||
113 | |||
114 | return { | ||
115 | registerHook, | ||
116 | registerSetting, | ||
117 | |||
118 | getRouter, | ||
119 | |||
120 | settingsManager, | ||
121 | storageManager, | ||
122 | |||
123 | videoLanguageManager, | ||
124 | videoCategoryManager, | ||
125 | videoLicenceManager, | ||
126 | |||
127 | videoPrivacyManager, | ||
128 | playlistPrivacyManager, | ||
129 | |||
130 | transcodingManager, | ||
131 | |||
132 | registerIdAndPassAuth, | ||
133 | registerExternalAuth, | ||
134 | unregisterIdAndPassAuth, | ||
135 | unregisterExternalAuth, | ||
136 | |||
137 | peertubeHelpers | ||
138 | } | ||
139 | } | ||
140 | |||
141 | reinitVideoConstants (npmName: string) { | ||
142 | const hash = { | ||
143 | language: VIDEO_LANGUAGES, | ||
144 | licence: VIDEO_LICENCES, | ||
145 | category: VIDEO_CATEGORIES, | ||
146 | privacy: VIDEO_PRIVACIES, | ||
147 | playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES | ||
148 | } | ||
149 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ] | ||
150 | |||
151 | for (const type of types) { | ||
152 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
153 | if (!updatedConstants) continue | ||
154 | |||
155 | for (const added of updatedConstants.added) { | ||
156 | delete hash[type][added.key] | ||
157 | } | ||
158 | |||
159 | for (const deleted of updatedConstants.deleted) { | ||
160 | hash[type][deleted.key] = deleted.label | ||
161 | } | ||
162 | |||
163 | delete this.updatedVideoConstants[type][npmName] | ||
164 | } | ||
165 | } | ||
166 | |||
167 | reinitTranscodingProfilesAndEncoders (npmName: string) { | ||
168 | const profiles = this.transcodingProfiles[npmName] | ||
169 | if (Array.isArray(profiles)) { | ||
170 | for (const profile of profiles) { | ||
171 | VideoTranscodingProfilesManager.Instance.removeProfile(profile) | ||
172 | } | ||
173 | } | ||
174 | |||
175 | const encoders = this.transcodingEncoders[npmName] | ||
176 | if (Array.isArray(encoders)) { | ||
177 | for (const o of encoders) { | ||
178 | VideoTranscodingProfilesManager.Instance.removeEncoderPriority(o.type, o.streamType, o.encoder, o.priority) | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | |||
183 | getSettings () { | ||
184 | return this.settings | ||
185 | } | ||
186 | |||
187 | getRouter () { | ||
188 | return this.router | ||
189 | } | ||
190 | |||
191 | getIdAndPassAuths () { | ||
192 | return this.idAndPassAuths | ||
193 | } | ||
194 | |||
195 | getExternalAuths () { | ||
196 | return this.externalAuths | ||
197 | } | ||
198 | |||
199 | getOnSettingsChangedCallbacks () { | ||
200 | return this.onSettingsChangeCallbacks | ||
201 | } | ||
202 | |||
203 | private buildGetRouter () { | ||
204 | return () => this.router | ||
205 | } | ||
206 | |||
207 | private buildRegisterSetting () { | ||
208 | return (options: RegisterServerSettingOptions) => { | ||
209 | this.settings.push(options) | ||
210 | } | ||
211 | } | ||
212 | |||
213 | private buildRegisterHook () { | ||
214 | return (options: RegisterServerHookOptions) => { | ||
215 | if (serverHookObject[options.target] !== true) { | ||
216 | logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName) | ||
217 | return | ||
218 | } | ||
219 | |||
220 | return this.onHookAdded(options) | ||
221 | } | ||
222 | } | ||
223 | |||
224 | private buildRegisterIdAndPassAuth () { | ||
225 | return (options: RegisterServerAuthPassOptions) => { | ||
226 | if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') { | ||
227 | logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options }) | ||
228 | return | ||
229 | } | ||
230 | |||
231 | this.idAndPassAuths.push(options) | ||
232 | } | ||
233 | } | ||
234 | |||
235 | private buildRegisterExternalAuth () { | ||
236 | const self = this | ||
237 | |||
238 | return (options: RegisterServerAuthExternalOptions) => { | ||
239 | if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') { | ||
240 | logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options }) | ||
241 | return | ||
242 | } | ||
243 | |||
244 | this.externalAuths.push(options) | ||
245 | |||
246 | return { | ||
247 | userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { | ||
248 | onExternalUserAuthenticated({ | ||
249 | npmName: self.npmName, | ||
250 | authName: options.authName, | ||
251 | authResult: result | ||
252 | }).catch(err => { | ||
253 | logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) | ||
254 | }) | ||
255 | } | ||
256 | } as RegisterServerAuthExternalResult | ||
257 | } | ||
258 | } | ||
259 | |||
260 | private buildUnregisterExternalAuth () { | ||
261 | return (authName: string) => { | ||
262 | this.externalAuths = this.externalAuths.filter(a => a.authName !== authName) | ||
263 | } | ||
264 | } | ||
265 | |||
266 | private buildUnregisterIdAndPassAuth () { | ||
267 | return (authName: string) => { | ||
268 | this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName) | ||
269 | } | ||
270 | } | ||
271 | |||
272 | private buildSettingsManager (): PluginSettingsManager { | ||
273 | return { | ||
274 | getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings), | ||
275 | |||
276 | getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings), | ||
277 | |||
278 | setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value), | ||
279 | |||
280 | onSettingsChange: (cb: (settings: any) => void) => this.onSettingsChangeCallbacks.push(cb) | ||
281 | } | ||
282 | } | ||
283 | |||
284 | private buildStorageManager (): PluginStorageManager { | ||
285 | return { | ||
286 | getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key), | ||
287 | |||
288 | storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data) | ||
289 | } | ||
290 | } | ||
291 | |||
292 | private buildVideoLanguageManager (): PluginVideoLanguageManager { | ||
293 | return { | ||
294 | addLanguage: (key: string, label: string) => { | ||
295 | return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }) | ||
296 | }, | ||
297 | |||
298 | deleteLanguage: (key: string) => { | ||
299 | return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | |||
304 | private buildVideoCategoryManager (): PluginVideoCategoryManager { | ||
305 | return { | ||
306 | addCategory: (key: number, label: string) => { | ||
307 | return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }) | ||
308 | }, | ||
309 | |||
310 | deleteCategory: (key: number) => { | ||
311 | return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | |||
316 | private buildVideoPrivacyManager (): PluginVideoPrivacyManager { | ||
317 | return { | ||
318 | deletePrivacy: (key: number) => { | ||
319 | return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key }) | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | |||
324 | private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager { | ||
325 | return { | ||
326 | deletePlaylistPrivacy: (key: number) => { | ||
327 | return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key }) | ||
328 | } | ||
329 | } | ||
330 | } | ||
331 | |||
332 | private buildVideoLicenceManager (): PluginVideoLicenceManager { | ||
333 | return { | ||
334 | addLicence: (key: number, label: string) => { | ||
335 | return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }) | ||
336 | }, | ||
337 | |||
338 | deleteLicence: (key: number) => { | ||
339 | return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
340 | } | ||
341 | } | ||
342 | } | ||
343 | |||
344 | private addConstant<T extends string | number> (parameters: { | ||
345 | npmName: string | ||
346 | type: AlterableVideoConstant | ||
347 | obj: VideoConstant | ||
348 | key: T | ||
349 | label: string | ||
350 | }) { | ||
351 | const { npmName, type, obj, key, label } = parameters | ||
352 | |||
353 | if (obj[key]) { | ||
354 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
355 | return false | ||
356 | } | ||
357 | |||
358 | if (!this.updatedVideoConstants[type][npmName]) { | ||
359 | this.updatedVideoConstants[type][npmName] = { | ||
360 | added: [], | ||
361 | deleted: [] | ||
362 | } | ||
363 | } | ||
364 | |||
365 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
366 | obj[key] = label | ||
367 | |||
368 | return true | ||
369 | } | ||
370 | |||
371 | private deleteConstant<T extends string | number> (parameters: { | ||
372 | npmName: string | ||
373 | type: AlterableVideoConstant | ||
374 | obj: VideoConstant | ||
375 | key: T | ||
376 | }) { | ||
377 | const { npmName, type, obj, key } = parameters | ||
378 | |||
379 | if (!obj[key]) { | ||
380 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
381 | return false | ||
382 | } | ||
383 | |||
384 | if (!this.updatedVideoConstants[type][npmName]) { | ||
385 | this.updatedVideoConstants[type][npmName] = { | ||
386 | added: [], | ||
387 | deleted: [] | ||
388 | } | ||
389 | } | ||
390 | |||
391 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
392 | delete obj[key] | ||
393 | |||
394 | return true | ||
395 | } | ||
396 | |||
397 | private buildTranscodingManager () { | ||
398 | const self = this | ||
399 | |||
400 | function addProfile (type: 'live' | 'vod', encoder: string, profile: string, builder: EncoderOptionsBuilder) { | ||
401 | if (profile === 'default') { | ||
402 | logger.error('A plugin cannot add a default live transcoding profile') | ||
403 | return false | ||
404 | } | ||
405 | |||
406 | VideoTranscodingProfilesManager.Instance.addProfile({ | ||
407 | type, | ||
408 | encoder, | ||
409 | profile, | ||
410 | builder | ||
411 | }) | ||
412 | |||
413 | if (!self.transcodingProfiles[self.npmName]) self.transcodingProfiles[self.npmName] = [] | ||
414 | self.transcodingProfiles[self.npmName].push({ type, encoder, profile }) | ||
415 | |||
416 | return true | ||
417 | } | ||
418 | |||
419 | function addEncoderPriority (type: 'live' | 'vod', streamType: 'audio' | 'video', encoder: string, priority: number) { | ||
420 | VideoTranscodingProfilesManager.Instance.addEncoderPriority(type, streamType, encoder, priority) | ||
421 | |||
422 | if (!self.transcodingEncoders[self.npmName]) self.transcodingEncoders[self.npmName] = [] | ||
423 | self.transcodingEncoders[self.npmName].push({ type, streamType, encoder, priority }) | ||
424 | } | ||
425 | |||
426 | return { | ||
427 | addLiveProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) { | ||
428 | return addProfile('live', encoder, profile, builder) | ||
429 | }, | ||
430 | |||
431 | addVODProfile (encoder: string, profile: string, builder: EncoderOptionsBuilder) { | ||
432 | return addProfile('vod', encoder, profile, builder) | ||
433 | }, | ||
434 | |||
435 | addLiveEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) { | ||
436 | return addEncoderPriority('live', streamType, encoder, priority) | ||
437 | }, | ||
438 | |||
439 | addVODEncoderPriority (streamType: 'audio' | 'video', encoder: string, priority: number) { | ||
440 | return addEncoderPriority('vod', streamType, encoder, priority) | ||
441 | } | ||
442 | } | ||
443 | } | ||
444 | } | ||