1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
import { move, remove } from 'fs-extra'
import { join } from 'path'
import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent'
import { CONFIG } from '@server/initializers/config'
import { UserModel } from '@server/models/user/user'
import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models'
import { getVideoStreamDuration } from '@shared/ffmpeg'
import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@shared/models'
import { federateVideoIfNeeded } from './activitypub/videos'
import { JobQueue } from './job-queue'
import { VideoStudioTranscodingJobHandler } from './runners'
import { createOptimizeOrMergeAudioJobs } from './transcoding/create-transcoding-job'
import { getTranscodingJobPriority } from './transcoding/transcoding-priority'
import { buildNewFile, removeHLSPlaylist, removeWebTorrentFile } from './video-file'
import { VideoPathManager } from './video-path-manager'
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 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.getMaxQualityFile().size / video.duration) * additionalDuration
}
// ---------------------------------------------------------------------------
export async function createVideoStudioJob (options: {
video: MVideo
user: MUser
payload: VideoStudioEditionPayload
}) {
const { video, user, payload } = options
const priority = await getTranscodingJobPriority({ user, type: 'studio', fallback: 0 })
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)
await video.save()
await federateVideoIfNeeded(video, false, undefined)
const user = await UserModel.loadByVideoId(video.id)
await createOptimizeOrMergeAudioJobs({ video, videoFile: newFile, isNewVideo: false, user, videoFileAlreadyLocked: false })
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) {
await removeHLSPlaylist(video)
for (const file of video.VideoFiles) {
if (file.id === webTorrentFileException.id) continue
await removeWebTorrentFile(video, file.id)
}
}
|