From 92e66e04f7f51d37b465cff442ce47f6d6d7cadd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 22 Mar 2022 16:58:49 +0100 Subject: Rename studio to editor --- server/lib/job-queue/handlers/video-edition.ts | 224 --------------------- .../lib/job-queue/handlers/video-studio-edition.ts | 224 +++++++++++++++++++++ server/lib/job-queue/job-queue.ts | 10 +- 3 files changed, 229 insertions(+), 229 deletions(-) delete mode 100644 server/lib/job-queue/handlers/video-edition.ts create mode 100644 server/lib/job-queue/handlers/video-studio-edition.ts (limited to 'server/lib/job-queue') diff --git a/server/lib/job-queue/handlers/video-edition.ts b/server/lib/job-queue/handlers/video-edition.ts deleted file mode 100644 index d2d2a4f65..000000000 --- a/server/lib/job-queue/handlers/video-edition.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { Job } from 'bull' -import { move, remove } from 'fs-extra' -import { join } from 'path' -import { addIntroOutro, addWatermark, cutVideo } from '@server/helpers/ffmpeg' -import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent' -import { CONFIG } from '@server/initializers/config' -import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' -import { generateWebTorrentVideoFilename } from '@server/lib/paths' -import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles' -import { isAbleToUploadVideo } from '@server/lib/user' -import { addOptimizeOrMergeAudioJob } from '@server/lib/video' -import { approximateIntroOutroAdditionalSize } from '@server/lib/video-editor' -import { VideoPathManager } from '@server/lib/video-path-manager' -import { UserModel } from '@server/models/user/user' -import { VideoModel } from '@server/models/video/video' -import { VideoFileModel } from '@server/models/video/video-file' -import { MVideo, MVideoFile, MVideoFullLight, MVideoId, MVideoWithAllFiles } from '@server/types/models' -import { getLowercaseExtension, pick } from '@shared/core-utils' -import { - buildFileMetadata, - buildUUID, - ffprobePromise, - getFileSize, - getVideoStreamDimensionsInfo, - getVideoStreamDuration, - getVideoStreamFPS -} from '@shared/extra-utils' -import { - VideoEditionPayload, - VideoEditionTaskPayload, - VideoEditorTask, - VideoEditorTaskCutPayload, - VideoEditorTaskIntroPayload, - VideoEditorTaskOutroPayload, - VideoEditorTaskWatermarkPayload -} from '@shared/models' -import { logger, loggerTagsFactory } from '../../../helpers/logger' - -const lTagsBase = loggerTagsFactory('video-edition') - -async function processVideoEdition (job: Job) { - const payload = job.data as VideoEditionPayload - const lTags = lTagsBase(payload.videoUUID) - - logger.info('Process video edition of %s in job %d.', payload.videoUUID, job.id, lTags) - - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) - - // No video, maybe deleted? - if (!video) { - logger.info('Can\'t process job %d, video does not exist.', job.id, lTags) - return undefined - } - - await checkUserQuotaOrThrow(video, payload) - - const inputFile = video.getMaxQualityFile() - - const editionResultPath = await VideoPathManager.Instance.makeAvailableVideoFile(inputFile, async originalFilePath => { - let tmpInputFilePath: string - let outputPath: string - - for (const task of payload.tasks) { - const outputFilename = buildUUID() + inputFile.extname - outputPath = join(CONFIG.STORAGE.TMP_DIR, outputFilename) - - await processTask({ - inputPath: tmpInputFilePath ?? originalFilePath, - video, - outputPath, - task, - lTags - }) - - if (tmpInputFilePath) await remove(tmpInputFilePath) - - // For the next iteration - tmpInputFilePath = outputPath - } - - return outputPath - }) - - logger.info('Video edition ended for video %s.', video.uuid, lTags) - - const newFile = await buildNewFile(video, editionResultPath) - - const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile) - await move(editionResultPath, outputPath) - - await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath) - - await removeAllFiles(video, newFile) - - await newFile.save() - - video.duration = await getVideoStreamDuration(outputPath) - await video.save() - - await federateVideoIfNeeded(video, false, undefined) - - const user = await UserModel.loadByVideoId(video.id) - await addOptimizeOrMergeAudioJob({ video, videoFile: newFile, user, isNewVideo: false }) -} - -// --------------------------------------------------------------------------- - -export { - processVideoEdition -} - -// --------------------------------------------------------------------------- - -type TaskProcessorOptions = { - inputPath: string - outputPath: string - video: MVideo - task: T - lTags: { tags: string[] } -} - -const taskProcessors: { [id in VideoEditorTask['name']]: (options: TaskProcessorOptions) => Promise } = { - 'add-intro': processAddIntroOutro, - 'add-outro': processAddIntroOutro, - 'cut': processCut, - 'add-watermark': processAddWatermark -} - -async function processTask (options: TaskProcessorOptions) { - const { video, task } = options - - logger.info('Processing %s task for video %s.', task.name, video.uuid, { task, ...options.lTags }) - - const processor = taskProcessors[options.task.name] - if (!process) throw new Error('Unknown task ' + task.name) - - return processor(options) -} - -function processAddIntroOutro (options: TaskProcessorOptions) { - const { task } = options - - return addIntroOutro({ - ...pick(options, [ 'inputPath', 'outputPath' ]), - - introOutroPath: task.options.file, - type: task.name === 'add-intro' - ? 'intro' - : 'outro', - - availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), - profile: CONFIG.TRANSCODING.PROFILE - }) -} - -function processCut (options: TaskProcessorOptions) { - const { task } = options - - return cutVideo({ - ...pick(options, [ 'inputPath', 'outputPath' ]), - - start: task.options.start, - end: task.options.end - }) -} - -function processAddWatermark (options: TaskProcessorOptions) { - const { task } = options - - return addWatermark({ - ...pick(options, [ 'inputPath', 'outputPath' ]), - - watermarkPath: task.options.file, - - availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), - profile: CONFIG.TRANSCODING.PROFILE - }) -} - -async function buildNewFile (video: MVideoId, path: string) { - const videoFile = new VideoFileModel({ - extname: getLowercaseExtension(path), - size: await getFileSize(path), - metadata: await buildFileMetadata(path), - videoStreamingPlaylistId: null, - videoId: video.id - }) - - const probe = await ffprobePromise(path) - - videoFile.fps = await getVideoStreamFPS(path, probe) - videoFile.resolution = (await getVideoStreamDimensionsInfo(path, probe)).resolution - - videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname) - - return videoFile -} - -async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) { - const hls = video.getHLSPlaylist() - - if (hls) { - await video.removeStreamingPlaylistFiles(hls) - await hls.destroy() - } - - for (const file of video.VideoFiles) { - if (file.id === webTorrentFileException.id) continue - - await video.removeWebTorrentFileAndTorrent(file) - await file.destroy() - } -} - -async function checkUserQuotaOrThrow (video: MVideoFullLight, payload: VideoEditionPayload) { - const user = await UserModel.loadByVideoId(video.id) - - const filePathFinder = (i: number) => (payload.tasks[i] as VideoEditorTaskIntroPayload | VideoEditorTaskOutroPayload).options.file - - const additionalBytes = await approximateIntroOutroAdditionalSize(video, payload.tasks, filePathFinder) - if (await isAbleToUploadVideo(user.id, additionalBytes) === false) { - throw new Error('Quota exceeded for this user to edit the video') - } -} diff --git a/server/lib/job-queue/handlers/video-studio-edition.ts b/server/lib/job-queue/handlers/video-studio-edition.ts new file mode 100644 index 000000000..cf3064a7a --- /dev/null +++ b/server/lib/job-queue/handlers/video-studio-edition.ts @@ -0,0 +1,224 @@ +import { Job } from 'bull' +import { move, remove } from 'fs-extra' +import { join } from 'path' +import { addIntroOutro, addWatermark, cutVideo } from '@server/helpers/ffmpeg' +import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent' +import { CONFIG } from '@server/initializers/config' +import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' +import { generateWebTorrentVideoFilename } from '@server/lib/paths' +import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles' +import { isAbleToUploadVideo } from '@server/lib/user' +import { addOptimizeOrMergeAudioJob } from '@server/lib/video' +import { VideoPathManager } from '@server/lib/video-path-manager' +import { approximateIntroOutroAdditionalSize } from '@server/lib/video-studio' +import { UserModel } from '@server/models/user/user' +import { VideoModel } from '@server/models/video/video' +import { VideoFileModel } from '@server/models/video/video-file' +import { MVideo, MVideoFile, MVideoFullLight, MVideoId, MVideoWithAllFiles } from '@server/types/models' +import { getLowercaseExtension, pick } from '@shared/core-utils' +import { + buildFileMetadata, + buildUUID, + ffprobePromise, + getFileSize, + getVideoStreamDimensionsInfo, + getVideoStreamDuration, + getVideoStreamFPS +} from '@shared/extra-utils' +import { + VideoStudioEditionPayload, + VideoStudioTaskPayload, + VideoStudioTaskCutPayload, + VideoStudioTaskIntroPayload, + VideoStudioTaskOutroPayload, + VideoStudioTaskWatermarkPayload, + VideoStudioTask +} from '@shared/models' +import { logger, loggerTagsFactory } from '../../../helpers/logger' + +const lTagsBase = loggerTagsFactory('video-edition') + +async function processVideoStudioEdition (job: Job) { + const payload = job.data as VideoStudioEditionPayload + const lTags = lTagsBase(payload.videoUUID) + + logger.info('Process video studio edition of %s in job %d.', payload.videoUUID, job.id, lTags) + + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) + + // No video, maybe deleted? + if (!video) { + logger.info('Can\'t process job %d, video does not exist.', job.id, lTags) + return undefined + } + + await checkUserQuotaOrThrow(video, payload) + + const inputFile = video.getMaxQualityFile() + + const editionResultPath = await VideoPathManager.Instance.makeAvailableVideoFile(inputFile, async originalFilePath => { + let tmpInputFilePath: string + let outputPath: string + + for (const task of payload.tasks) { + const outputFilename = buildUUID() + inputFile.extname + outputPath = join(CONFIG.STORAGE.TMP_DIR, outputFilename) + + await processTask({ + inputPath: tmpInputFilePath ?? originalFilePath, + video, + outputPath, + task, + lTags + }) + + if (tmpInputFilePath) await remove(tmpInputFilePath) + + // For the next iteration + tmpInputFilePath = outputPath + } + + return outputPath + }) + + logger.info('Video edition ended for video %s.', video.uuid, lTags) + + const newFile = await buildNewFile(video, editionResultPath) + + const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile) + await move(editionResultPath, outputPath) + + await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath) + + await removeAllFiles(video, newFile) + + await newFile.save() + + video.duration = await getVideoStreamDuration(outputPath) + await video.save() + + await federateVideoIfNeeded(video, false, undefined) + + const user = await UserModel.loadByVideoId(video.id) + await addOptimizeOrMergeAudioJob({ video, videoFile: newFile, user, isNewVideo: false }) +} + +// --------------------------------------------------------------------------- + +export { + processVideoStudioEdition +} + +// --------------------------------------------------------------------------- + +type TaskProcessorOptions = { + inputPath: string + outputPath: string + video: MVideo + task: T + lTags: { tags: string[] } +} + +const taskProcessors: { [id in VideoStudioTask['name']]: (options: TaskProcessorOptions) => Promise } = { + 'add-intro': processAddIntroOutro, + 'add-outro': processAddIntroOutro, + 'cut': processCut, + 'add-watermark': processAddWatermark +} + +async function processTask (options: TaskProcessorOptions) { + const { video, task } = options + + logger.info('Processing %s task for video %s.', task.name, video.uuid, { task, ...options.lTags }) + + const processor = taskProcessors[options.task.name] + if (!process) throw new Error('Unknown task ' + task.name) + + return processor(options) +} + +function processAddIntroOutro (options: TaskProcessorOptions) { + const { task } = options + + return addIntroOutro({ + ...pick(options, [ 'inputPath', 'outputPath' ]), + + introOutroPath: task.options.file, + type: task.name === 'add-intro' + ? 'intro' + : 'outro', + + availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), + profile: CONFIG.TRANSCODING.PROFILE + }) +} + +function processCut (options: TaskProcessorOptions) { + const { task } = options + + return cutVideo({ + ...pick(options, [ 'inputPath', 'outputPath' ]), + + start: task.options.start, + end: task.options.end + }) +} + +function processAddWatermark (options: TaskProcessorOptions) { + const { task } = options + + return addWatermark({ + ...pick(options, [ 'inputPath', 'outputPath' ]), + + watermarkPath: task.options.file, + + availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), + profile: CONFIG.TRANSCODING.PROFILE + }) +} + +async function buildNewFile (video: MVideoId, path: string) { + const videoFile = new VideoFileModel({ + extname: getLowercaseExtension(path), + size: await getFileSize(path), + metadata: await buildFileMetadata(path), + videoStreamingPlaylistId: null, + videoId: video.id + }) + + const probe = await ffprobePromise(path) + + videoFile.fps = await getVideoStreamFPS(path, probe) + videoFile.resolution = (await getVideoStreamDimensionsInfo(path, probe)).resolution + + videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname) + + return videoFile +} + +async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) { + const hls = video.getHLSPlaylist() + + if (hls) { + await video.removeStreamingPlaylistFiles(hls) + await hls.destroy() + } + + for (const file of video.VideoFiles) { + if (file.id === webTorrentFileException.id) continue + + await video.removeWebTorrentFileAndTorrent(file) + await file.destroy() + } +} + +async function checkUserQuotaOrThrow (video: MVideoFullLight, payload: VideoStudioEditionPayload) { + const user = await UserModel.loadByVideoId(video.id) + + const filePathFinder = (i: number) => (payload.tasks[i] as VideoStudioTaskIntroPayload | VideoStudioTaskOutroPayload).options.file + + const additionalBytes = await approximateIntroOutroAdditionalSize(video, payload.tasks, filePathFinder) + if (await isAbleToUploadVideo(user.id, additionalBytes) === false) { + throw new Error('Quota exceeded for this user to edit the video') + } +} diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index 3224abcc3..167b7b168 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts @@ -15,11 +15,11 @@ import { ManageVideoTorrentPayload, MoveObjectStoragePayload, RefreshPayload, - VideoEditionPayload, VideoFileImportPayload, VideoImportPayload, VideoLiveEndingPayload, VideoRedundancyPayload, + VideoStudioEditionPayload, VideoTranscodingPayload } from '../../../shared/models' import { logger } from '../../helpers/logger' @@ -34,10 +34,10 @@ import { processActorKeys } from './handlers/actor-keys' import { processEmail } from './handlers/email' import { processManageVideoTorrent } from './handlers/manage-video-torrent' import { processMoveToObjectStorage } from './handlers/move-to-object-storage' -import { processVideoEdition } from './handlers/video-edition' import { processVideoFileImport } from './handlers/video-file-import' import { processVideoImport } from './handlers/video-import' import { processVideoLiveEnding } from './handlers/video-live-ending' +import { processVideoStudioEdition } from './handlers/video-studio-edition' import { processVideoTranscoding } from './handlers/video-transcoding' import { processVideosViewsStats } from './handlers/video-views-stats' @@ -57,7 +57,7 @@ type CreateJobArgument = { type: 'actor-keys', payload: ActorKeysPayload } | { type: 'video-redundancy', payload: VideoRedundancyPayload } | { type: 'delete-resumable-upload-meta-file', payload: DeleteResumableUploadMetaFilePayload } | - { type: 'video-edition', payload: VideoEditionPayload } | + { type: 'video-studio-edition', payload: VideoStudioEditionPayload } | { type: 'manage-video-torrent', payload: ManageVideoTorrentPayload } | { type: 'move-to-object-storage', payload: MoveObjectStoragePayload } @@ -83,7 +83,7 @@ const handlers: { [id in JobType]: (job: Job) => Promise } = { 'video-redundancy': processVideoRedundancy, 'move-to-object-storage': processMoveToObjectStorage, 'manage-video-torrent': processManageVideoTorrent, - 'video-edition': processVideoEdition + 'video-studio-edition': processVideoStudioEdition } const jobTypes: JobType[] = [ @@ -103,7 +103,7 @@ const jobTypes: JobType[] = [ 'video-live-ending', 'move-to-object-storage', 'manage-video-torrent', - 'video-edition' + 'video-studio-edition' ] class JobQueue { -- cgit v1.2.3