From b9077c83fcce0fab5bfcee0527be9256f6e517d0 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 27 Dec 2023 10:39:09 +0100 Subject: [PATCH] Add ability to disable storyboards --- .../edit-basic-configuration.component.html | 14 ++++++++ .../edit-custom-config.component.ts | 4 +++ config/default.yaml | 4 +++ config/production.yaml.example | 4 +++ .../models/src/server/custom-config.model.ts | 4 +++ .../models/src/server/server-config.model.ts | 4 +++ .../src/server/config-command.ts | 3 ++ packages/tests/src/api/check-params/config.ts | 4 +++ packages/tests/src/api/server/config.ts | 7 ++++ .../tests/src/api/videos/video-storyboard.ts | 21 ++++++++++++ server/core/controllers/api/config.ts | 3 ++ server/core/controllers/api/videos/source.ts | 11 ++----- server/core/controllers/api/videos/upload.ts | 11 ++----- .../core/initializers/checker-before-init.ts | 3 +- server/core/initializers/config.ts | 6 ++-- .../lib/job-queue/handlers/video-import.ts | 10 ++---- .../job-queue/handlers/video-live-ending.ts | 9 ++--- server/core/lib/job-queue/job-queue.ts | 4 ++- server/core/lib/server-config-manager.ts | 4 +++ .../core/lib/transcoding/web-transcoding.ts | 10 ++---- server/core/lib/video-studio.ts | 9 ++--- server/core/lib/video.ts | 33 ++++++++++++++++++- .../scripts/create-generate-storyboard-job.ts | 9 ++--- 23 files changed, 131 insertions(+), 60 deletions(-) diff --git a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html index 54f6a2e8f..c08bf97d5 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-basic-configuration.component.html @@ -357,6 +357,20 @@ + + +
+ + + Generate storyboards of local videos using ffmpeg so users can see the video preview in the player while scrubbing the video + + +
+ +
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 2c0cc0a16..c1cbe1b7d 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -274,6 +274,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { instanceCustomHomepage: { content: null + }, + + storyboards: { + enabled: null } } diff --git a/config/default.yaml b/config/default.yaml index 7801f8d1b..98af02d1a 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -873,3 +873,7 @@ client: # If you enable only one external auth plugin # You can automatically redirect your users on this external platform when they click on the login button redirect_on_single_external_auth: false + +storyboards: + # Generate storyboards of local videos using ffmpeg so users can see the video preview in the player while scrubbing the video + enabled: true diff --git a/config/production.yaml.example b/config/production.yaml.example index a1e0400c7..83a4fe7c8 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -883,3 +883,7 @@ client: # If you enable only one external auth plugin # You can automatically redirect your users on this external platform when they click on the login button redirect_on_single_external_auth: false + +storyboards: + # Generate storyboards of local videos using ffmpeg so users can see the video preview in the player while scrubbing the video + enabled: true diff --git a/packages/models/src/server/custom-config.model.ts b/packages/models/src/server/custom-config.model.ts index 138ccd8f9..0fce36d5c 100644 --- a/packages/models/src/server/custom-config.model.ts +++ b/packages/models/src/server/custom-config.model.ts @@ -257,4 +257,8 @@ export interface CustomConfig { } } + storyboards: { + enabled: boolean + } + } diff --git a/packages/models/src/server/server-config.model.ts b/packages/models/src/server/server-config.model.ts index a5012a2b2..6fff58647 100644 --- a/packages/models/src/server/server-config.model.ts +++ b/packages/models/src/server/server-config.model.ts @@ -321,6 +321,10 @@ export interface ServerConfig { } } } + + storyboards: { + enabled: boolean + } } export type HTMLServerConfig = Omit diff --git a/packages/server-commands/src/server/config-command.ts b/packages/server-commands/src/server/config-command.ts index 527a77610..88ae662c4 100644 --- a/packages/server-commands/src/server/config-command.ts +++ b/packages/server-commands/src/server/config-command.ts @@ -567,6 +567,9 @@ export class ConfigCommand extends AbstractCommand { disableLocalSearch: true, isDefaultSearch: true } + }, + storyboards: { + enabled: true } } diff --git a/packages/tests/src/api/check-params/config.ts b/packages/tests/src/api/check-params/config.ts index 15b09791d..7a2225eaa 100644 --- a/packages/tests/src/api/check-params/config.ts +++ b/packages/tests/src/api/check-params/config.ts @@ -16,6 +16,7 @@ describe('Test config API validators', function () { const path = '/api/v1/config/custom' let server: PeerTubeServer let userAccessToken: string + const updateParams: CustomConfig = { instance: { name: 'PeerTube updated', @@ -240,6 +241,9 @@ describe('Test config API validators', function () { disableLocalSearch: true, isDefaultSearch: true } + }, + storyboards: { + enabled: false } } diff --git a/packages/tests/src/api/server/config.ts b/packages/tests/src/api/server/config.ts index 58882488b..c96804751 100644 --- a/packages/tests/src/api/server/config.ts +++ b/packages/tests/src/api/server/config.ts @@ -123,6 +123,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) { expect(data.broadcastMessage.level).to.equal('info') expect(data.broadcastMessage.message).to.equal('') expect(data.broadcastMessage.dismissable).to.be.false + + expect(data.storyboards.enabled).to.be.true } function checkUpdatedConfig (data: CustomConfig) { @@ -236,6 +238,8 @@ function checkUpdatedConfig (data: CustomConfig) { expect(data.broadcastMessage.level).to.equal('error') expect(data.broadcastMessage.message).to.equal('super bad message') expect(data.broadcastMessage.dismissable).to.be.true + + expect(data.storyboards.enabled).to.be.false } const newCustomConfig: CustomConfig = { @@ -460,6 +464,9 @@ const newCustomConfig: CustomConfig = { disableLocalSearch: true, isDefaultSearch: true } + }, + storyboards: { + enabled: false } } diff --git a/packages/tests/src/api/videos/video-storyboard.ts b/packages/tests/src/api/videos/video-storyboard.ts index 8ec1a0999..32314d005 100644 --- a/packages/tests/src/api/videos/video-storyboard.ts +++ b/packages/tests/src/api/videos/video-storyboard.ts @@ -209,6 +209,27 @@ describe('Test video storyboard', function () { } }) + it('Should not generate storyboards if disabled by the admin', async function () { + this.timeout(60000) + + await servers[0].config.updateExistingSubConfig({ + newConfig: { + storyboards: { + enabled: false + } + } + }) + + const { uuid } = await servers[0].videos.quickUpload({ name: 'upload', fixture: 'video_short.webm' }) + await waitJobs(servers) + + for (const server of servers) { + const { storyboards } = await server.storyboard.list({ id: uuid }) + + expect(storyboards).to.have.lengthOf(0) + } + }) + after(async function () { await cleanupTests(servers) }) diff --git a/server/core/controllers/api/config.ts b/server/core/controllers/api/config.ts index 8c28bc08c..fa6553597 100644 --- a/server/core/controllers/api/config.ts +++ b/server/core/controllers/api/config.ts @@ -355,6 +355,9 @@ function customConfig (): CustomConfig { disableLocalSearch: CONFIG.SEARCH.SEARCH_INDEX.DISABLE_LOCAL_SEARCH, isDefaultSearch: CONFIG.SEARCH.SEARCH_INDEX.IS_DEFAULT_SEARCH } + }, + storyboards: { + enabled: CONFIG.STORYBOARDS.ENABLED } } } diff --git a/server/core/controllers/api/videos/source.ts b/server/core/controllers/api/videos/source.ts index b4bfc3725..d56e9610f 100644 --- a/server/core/controllers/api/videos/source.ts +++ b/server/core/controllers/api/videos/source.ts @@ -5,7 +5,7 @@ import { CreateJobArgument, CreateJobOptions, JobQueue } from '@server/lib/job-q import { Hooks } from '@server/lib/plugins/hooks.js' import { regenerateMiniaturesIfNeeded } from '@server/lib/thumbnail.js' import { uploadx } from '@server/lib/uploadx.js' -import { buildMoveJob } from '@server/lib/video.js' +import { buildMoveJob, buildStoryboardJobIfNeeded } from '@server/lib/video.js' import { autoBlacklistVideoIfNeeded } from '@server/lib/video-blacklist.js' import { buildNewFile } from '@server/lib/video-file.js' import { VideoPathManager } from '@server/lib/video-path-manager.js' @@ -152,14 +152,7 @@ async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVide } }, - { - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - // No need to federate, we process these jobs sequentially - federate: false - } - }, + buildStoryboardJobIfNeeded({ video, federate: false }), { type: 'federate-video' as 'federate-video', diff --git a/server/core/controllers/api/videos/upload.ts b/server/core/controllers/api/videos/upload.ts index a1a0d5058..1d51094c8 100644 --- a/server/core/controllers/api/videos/upload.ts +++ b/server/core/controllers/api/videos/upload.ts @@ -6,7 +6,7 @@ import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js' import { CreateJobArgument, CreateJobOptions, JobQueue } from '@server/lib/job-queue/index.js' import { Redis } from '@server/lib/redis.js' import { uploadx } from '@server/lib/uploadx.js' -import { buildLocalVideoFromReq, buildMoveJob, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video.js' +import { buildLocalVideoFromReq, buildMoveJob, buildStoryboardJobIfNeeded, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video.js' import { buildNewFile } from '@server/lib/video-file.js' import { VideoPathManager } from '@server/lib/video-path-manager.js' import { buildNextVideoState } from '@server/lib/video-state.js' @@ -248,14 +248,7 @@ async function addVideoJobsAfterUpload (video: MVideoFullLight, videoFile: MVide } }, - { - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - // No need to federate, we process these jobs sequentially - federate: false - } - }, + buildStoryboardJobIfNeeded({ video, federate: false }), { type: 'notify', diff --git a/server/core/initializers/checker-before-init.ts b/server/core/initializers/checker-before-init.ts index 6a7706eaa..096d2ee02 100644 --- a/server/core/initializers/checker-before-init.ts +++ b/server/core/initializers/checker-before-init.ts @@ -84,7 +84,8 @@ function checkMissedConfig () { 'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p', 'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p', 'live.transcoding.resolutions.1440p', 'live.transcoding.resolutions.2160p', 'live.transcoding.always_transcode_original_resolution', - 'live.transcoding.remote_runners.enabled' + 'live.transcoding.remote_runners.enabled', + 'storyboards.enabled' ] const requiredAlternatives = [ diff --git a/server/core/initializers/config.ts b/server/core/initializers/config.ts index 3c64162a3..1780c1971 100644 --- a/server/core/initializers/config.ts +++ b/server/core/initializers/config.ts @@ -610,8 +610,10 @@ const CONFIG = { get DISABLE_LOCAL_SEARCH () { return config.get('search.search_index.disable_local_search') }, get IS_DEFAULT_SEARCH () { return config.get('search.search_index.is_default_search') } } + }, + STORYBOARDS: { + get ENABLED () { return config.get('storyboards.enabled') } } - } function registerConfigChangedHandler (fun: Function) { @@ -682,7 +684,7 @@ export function reloadConfig () { return process.env.NODE_CONFIG_DIR.split(':') } - return [ join(root(), 'config') ] + return [join(root(), 'config')] } function purge () { diff --git a/server/core/lib/job-queue/handlers/video-import.ts b/server/core/lib/job-queue/handlers/video-import.ts index 5d71d99a1..4984f02eb 100644 --- a/server/core/lib/job-queue/handlers/video-import.ts +++ b/server/core/lib/job-queue/handlers/video-import.ts @@ -25,7 +25,7 @@ import { createOptimizeOrMergeAudioJobs } from '@server/lib/transcoding/create-t import { isAbleToUploadVideo } from '@server/lib/user.js' import { VideoPathManager } from '@server/lib/video-path-manager.js' import { buildNextVideoState } from '@server/lib/video-state.js' -import { buildMoveJob } from '@server/lib/video.js' +import { buildMoveJob, buildStoryboardJobIfNeeded } from '@server/lib/video.js' import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models/index.js' import { MVideoImport, MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import.js' import { getLowercaseExtension } from '@peertube/peertube-node-utils' @@ -307,13 +307,7 @@ async function afterImportSuccess (options: { } // Generate the storyboard in the job queue, and don't forget to federate an update after - await JobQueue.Instance.createJob({ - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - federate: true - } - }) + await JobQueue.Instance.createJob(buildStoryboardJobIfNeeded({ video, federate: true })) if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) { await JobQueue.Instance.createJob( diff --git a/server/core/lib/job-queue/handlers/video-live-ending.ts b/server/core/lib/job-queue/handlers/video-live-ending.ts index 1837f9d33..ee1a3b2c4 100644 --- a/server/core/lib/job-queue/handlers/video-live-ending.ts +++ b/server/core/lib/job-queue/handlers/video-live-ending.ts @@ -30,6 +30,7 @@ import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoS import { logger, loggerTagsFactory } from '../../../helpers/logger.js' import { JobQueue } from '../job-queue.js' import { isVideoInPublicDirectory } from '@server/lib/video-privacy.js' +import { buildStoryboardJobIfNeeded } from '@server/lib/video.js' const lTags = loggerTagsFactory('live', 'job') @@ -302,11 +303,5 @@ async function cleanupLiveAndFederate (options: { } function createStoryboardJob (video: MVideo) { - return JobQueue.Instance.createJob({ - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - federate: true - } - }) + return JobQueue.Instance.createJob(buildStoryboardJobIfNeeded({ video, federate: true })) } diff --git a/server/core/lib/job-queue/job-queue.ts b/server/core/lib/job-queue/job-queue.ts index 8e1ff3be9..accd45cea 100644 --- a/server/core/lib/job-queue/job-queue.ts +++ b/server/core/lib/job-queue/job-queue.ts @@ -336,7 +336,9 @@ class JobQueue { .catch(err => logger.error('Cannot create job.', { err, options })) } - createJob (options: CreateJobArgument & CreateJobOptions) { + createJob (options: CreateJobArgument & CreateJobOptions | undefined) { + if (!options) return + const queue: Queue = this.queues[options.type] if (queue === undefined) { logger.error('Unknown queue %s: cannot create job.', options.type) diff --git a/server/core/lib/server-config-manager.ts b/server/core/lib/server-config-manager.ts index e1969f720..3b874edfd 100644 --- a/server/core/lib/server-config-manager.ts +++ b/server/core/lib/server-config-manager.ts @@ -290,6 +290,10 @@ class ServerConfigManager { users: CONFIG.VIEWS.VIDEOS.WATCHING_INTERVAL.USERS } } + }, + + storyboards: { + enabled: CONFIG.STORYBOARDS.ENABLED } } } diff --git a/server/core/lib/transcoding/web-transcoding.ts b/server/core/lib/transcoding/web-transcoding.ts index e8247e958..a5b934048 100644 --- a/server/core/lib/transcoding/web-transcoding.ts +++ b/server/core/lib/transcoding/web-transcoding.ts @@ -16,6 +16,7 @@ import { buildFileMetadata } from '../video-file.js' import { VideoPathManager } from '../video-path-manager.js' import { buildFFmpegVOD } from './shared/index.js' import { buildOriginalFileResolution } from './transcoding-resolutions.js' +import { buildStoryboardJobIfNeeded } from '../video.js' // Optimize the original video file and replace it. The resolution is not changed. export async function optimizeOriginalVideofile (options: { @@ -247,14 +248,7 @@ export async function onWebVideoFileTranscoding (options: { video.VideoFiles = await video.$get('VideoFiles') if (wasAudioFile) { - await JobQueue.Instance.createJob({ - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - // No need to federate, we process these jobs sequentially - federate: false - } - }) + await JobQueue.Instance.createJob(buildStoryboardJobIfNeeded({ video, federate: false })) } return { video, videoFile } diff --git a/server/core/lib/video-studio.ts b/server/core/lib/video-studio.ts index d248a0f23..fbf74103d 100644 --- a/server/core/lib/video-studio.ts +++ b/server/core/lib/video-studio.ts @@ -11,6 +11,7 @@ import { VideoStudioTranscodingJobHandler } from './runners/index.js' import { getTranscodingJobPriority } from './transcoding/transcoding-priority.js' import { buildNewFile, removeHLSPlaylist, removeWebVideoFile } from './video-file.js' import { VideoPathManager } from './video-path-manager.js' +import { buildStoryboardJobIfNeeded } from './video.js' const lTags = loggerTagsFactory('video-studio') @@ -106,13 +107,7 @@ export async function onVideoStudioEnded (options: { await video.save() return JobQueue.Instance.createSequentialJobFlow( - { - type: 'generate-video-storyboard' as 'generate-video-storyboard', - payload: { - videoUUID: video.uuid, - federate: false - } - }, + buildStoryboardJobIfNeeded({ video, federate: false }), { type: 'federate-video' as 'federate-video', diff --git a/server/core/lib/video.ts b/server/core/lib/video.ts index 7e88fa991..d9e55d712 100644 --- a/server/core/lib/video.ts +++ b/server/core/lib/video.ts @@ -16,7 +16,7 @@ import { TagModel } from '@server/models/video/tag.js' import { VideoJobInfoModel } from '@server/models/video/video-job-info.js' import { VideoModel } from '@server/models/video/video.js' import { FilteredModelAttributes } from '@server/types/index.js' -import { MThumbnail, MVideoFullLight, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models/index.js' +import { MThumbnail, MVideo, MVideoFullLight, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models/index.js' import { CreateJobArgument, JobQueue } from './job-queue/job-queue.js' import { updateLocalVideoMiniatureFromExisting } from './thumbnail.js' import { moveFilesIfPrivacyChanged } from './video-privacy.js' @@ -117,6 +117,37 @@ export async function buildMoveJob (options: { // --------------------------------------------------------------------------- +export function buildStoryboardJobIfNeeded (options: { + video: MVideo + federate: boolean +}) { + const { video, federate } = options + + if (CONFIG.STORYBOARDS.ENABLED) { + return { + type: 'generate-video-storyboard' as 'generate-video-storyboard', + payload: { + videoUUID: video.uuid, + federate + } + } + } + + if (federate === true) { + return { + type: 'federate-video' as 'federate-video', + payload: { + videoUUID: video.uuid, + isNewVideoForFederation: false + } + } + } + + return undefined +} + +// --------------------------------------------------------------------------- + export async function getVideoDuration (videoId: number | string) { const video = await VideoModel.load(videoId) diff --git a/server/scripts/create-generate-storyboard-job.ts b/server/scripts/create-generate-storyboard-job.ts index 1f70e4d15..5c964ea13 100644 --- a/server/scripts/create-generate-storyboard-job.ts +++ b/server/scripts/create-generate-storyboard-job.ts @@ -4,6 +4,7 @@ import { initDatabaseModels } from '@server/initializers/database.js' import { JobQueue } from '@server/lib/job-queue/index.js' import { StoryboardModel } from '@server/models/video/storyboard.js' import { VideoModel } from '@server/models/video/video.js' +import { buildStoryboardJobIfNeeded } from '@server/lib/video.js' program .description('Generate videos storyboard') @@ -60,13 +61,7 @@ async function run () { if (videoFull.isLive) continue - await JobQueue.Instance.createJob({ - type: 'generate-video-storyboard', - payload: { - videoUUID: videoFull.uuid, - federate: true - } - }) + await JobQueue.Instance.createJob(buildStoryboardJobIfNeeded({ video: videoFull, federate: true })) console.log(`Created generate-storyboard job for ${videoFull.name}.`) }