Add ability to disable storyboards

This commit is contained in:
Chocobozzz 2023-12-27 10:39:09 +01:00
parent 482223cc23
commit b9077c83fc
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
23 changed files with 131 additions and 60 deletions

View File

@ -357,6 +357,20 @@
</ng-container>
</ng-container>
<ng-container formGroupName="storyboards">
<div class="form-group">
<my-peertube-checkbox
inputName="storyboardsEnabled" formControlName="enabled"
i18n-labelText labelText="Enable video storyboards"
>
<ng-container ngProjectAs="description">
<span i18n>Generate storyboards of local videos using ffmpeg so users can see the video preview in the player while scrubbing the video</span>
</ng-container>
</my-peertube-checkbox>
</div>
</ng-container>
</div>
</div>

View File

@ -274,6 +274,10 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
instanceCustomHomepage: {
content: null
},
storyboards: {
enabled: null
}
}

View File

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

View File

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

View File

@ -257,4 +257,8 @@ export interface CustomConfig {
}
}
storyboards: {
enabled: boolean
}
}

View File

@ -321,6 +321,10 @@ export interface ServerConfig {
}
}
}
storyboards: {
enabled: boolean
}
}
export type HTMLServerConfig = Omit<ServerConfig, 'signup'>

View File

@ -567,6 +567,9 @@ export class ConfigCommand extends AbstractCommand {
disableLocalSearch: true,
isDefaultSearch: true
}
},
storyboards: {
enabled: true
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = [

View File

@ -610,8 +610,10 @@ const CONFIG = {
get DISABLE_LOCAL_SEARCH () { return config.get<boolean>('search.search_index.disable_local_search') },
get IS_DEFAULT_SEARCH () { return config.get<boolean>('search.search_index.is_default_search') }
}
},
STORYBOARDS: {
get ENABLED () { return config.get<boolean>('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 () {

View File

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

View File

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

View File

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

View File

@ -290,6 +290,10 @@ class ServerConfigManager {
users: CONFIG.VIEWS.VIDEOS.WATCHING_INTERVAL.USERS
}
}
},
storyboards: {
enabled: CONFIG.STORYBOARDS.ENABLED
}
}
}

View File

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

View File

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

View File

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

View File

@ -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}.`)
}