Init commit
This commit is contained in:
148
server/core/lib/video-studio.ts
Normal file
148
server/core/lib/video-studio.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { buildAspectRatio } from '@peertube/peertube-core-utils'
|
||||
import { getVideoStreamDuration } from '@peertube/peertube-ffmpeg'
|
||||
import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@peertube/peertube-models'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
|
||||
import { createTorrentAndSetInfoHashFromPath } from '@server/lib/webtorrent.js'
|
||||
import { CONFIG } from '@server/initializers/config.js'
|
||||
import { MUser, MVideoFile, MVideoFullLight, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models/index.js'
|
||||
import { move, remove } from 'fs-extra/esm'
|
||||
import { join } from 'path'
|
||||
import { JobQueue } from './job-queue/index.js'
|
||||
import { VideoStudioTranscodingJobHandler } from './runners/index.js'
|
||||
import { getTranscodingJobPriority } from './transcoding/transcoding-priority.js'
|
||||
import { regenerateTranscriptionTaskIfNeeded } from './video-captions.js'
|
||||
import { buildNewFile, removeHLSPlaylist, removeWebVideoFile } from './video-file.js'
|
||||
import { addRemoteStoryboardJobIfNeeded, buildLocalStoryboardJobIfNeeded } from './video-jobs.js'
|
||||
import { VideoPathManager } from './video-path-manager.js'
|
||||
|
||||
const lTags = loggerTagsFactory('video-studio')
|
||||
|
||||
export function buildTaskFileFieldname (indice: number, fieldName = 'file') {
|
||||
return `tasks[${indice}][options][${fieldName}]`
|
||||
}
|
||||
|
||||
export function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') {
|
||||
return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName))
|
||||
}
|
||||
|
||||
export function getStudioTaskFilePath (filename: string) {
|
||||
return join(CONFIG.STORAGE.TMP_PERSISTENT_DIR, filename)
|
||||
}
|
||||
|
||||
export async function safeCleanupStudioTMPFiles (tasks: VideoStudioTaskPayload[]) {
|
||||
logger.info('Removing TMP studio task files', { tasks, ...lTags() })
|
||||
|
||||
for (const task of tasks) {
|
||||
try {
|
||||
if (task.name === 'add-intro' || task.name === 'add-outro') {
|
||||
await remove(task.options.file)
|
||||
} else if (task.name === 'add-watermark') {
|
||||
await remove(task.options.file)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Cannot remove studio file', { err })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function approximateIntroOutroAdditionalSize (
|
||||
video: MVideoFullLight,
|
||||
tasks: VideoStudioTask[],
|
||||
fileFinder: (i: number) => string
|
||||
) {
|
||||
let additionalDuration = 0
|
||||
|
||||
for (let i = 0; i < tasks.length; i++) {
|
||||
const task = tasks[i]
|
||||
|
||||
if (task.name !== 'add-intro' && task.name !== 'add-outro') continue
|
||||
|
||||
const filePath = fileFinder(i)
|
||||
additionalDuration += await getVideoStreamDuration(filePath)
|
||||
}
|
||||
|
||||
return (video.getMaxQualityBytes() / video.duration) * additionalDuration
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function createVideoStudioJob (options: {
|
||||
video: MVideoWithFile
|
||||
user: MUser
|
||||
payload: VideoStudioEditionPayload
|
||||
}) {
|
||||
const { video, user, payload } = options
|
||||
|
||||
const priority = await getTranscodingJobPriority({ user, type: 'studio' })
|
||||
|
||||
if (CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED) {
|
||||
await new VideoStudioTranscodingJobHandler().create({ video, tasks: payload.tasks, priority })
|
||||
return
|
||||
}
|
||||
|
||||
await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority })
|
||||
}
|
||||
|
||||
export async function onVideoStudioEnded (options: {
|
||||
editionResultPath: string
|
||||
tasks: VideoStudioTaskPayload[]
|
||||
video: MVideoFullLight
|
||||
}) {
|
||||
const { video, tasks, editionResultPath } = options
|
||||
|
||||
const newFile = await buildNewFile({ path: editionResultPath, mode: 'web-video' })
|
||||
newFile.videoId = video.id
|
||||
|
||||
const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile)
|
||||
await move(editionResultPath, outputPath)
|
||||
|
||||
await safeCleanupStudioTMPFiles(tasks)
|
||||
|
||||
await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath)
|
||||
await removeAllFiles(video, newFile)
|
||||
|
||||
await newFile.save()
|
||||
|
||||
video.duration = await getVideoStreamDuration(outputPath)
|
||||
video.aspectRatio = buildAspectRatio({ width: newFile.width, height: newFile.height })
|
||||
await video.save()
|
||||
|
||||
await JobQueue.Instance.createSequentialJobFlow(
|
||||
await buildLocalStoryboardJobIfNeeded({ video, federate: false }),
|
||||
{
|
||||
type: 'federate-video' as 'federate-video',
|
||||
payload: {
|
||||
videoUUID: video.uuid,
|
||||
isNewVideoForFederation: false
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'transcoding-job-builder' as 'transcoding-job-builder',
|
||||
payload: {
|
||||
videoUUID: video.uuid,
|
||||
optimizeJob: {
|
||||
isNewVideo: false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
await addRemoteStoryboardJobIfNeeded(video)
|
||||
await regenerateTranscriptionTaskIfNeeded(video)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function removeAllFiles (video: MVideoWithAllFiles, webVideoFileException: MVideoFile) {
|
||||
await removeHLSPlaylist(video)
|
||||
|
||||
for (const file of video.VideoFiles) {
|
||||
if (file.id === webVideoFileException.id) continue
|
||||
|
||||
await removeWebVideoFile(video, file.id)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user