aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-02-11 10:51:33 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-02-28 10:42:19 +0100
commitc729caf6cc34630877a0e5a1bda1719384cd0c8a (patch)
tree1d2e13722e518c73d2c9e6f0969615e29d51cf8c /server/lib
parenta24bf4dc659cebb65d887862bf21d7a35e9ec791 (diff)
downloadPeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.gz
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.zst
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.zip
Add basic video editor support
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/hls.ts6
-rw-r--r--server/lib/job-queue/handlers/video-edition.ts229
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts12
-rw-r--r--server/lib/job-queue/handlers/video-import.ts8
-rw-r--r--server/lib/job-queue/handlers/video-live-ending.ts8
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts10
-rw-r--r--server/lib/job-queue/job-queue.ts9
-rw-r--r--server/lib/live/live-manager.ts14
-rw-r--r--server/lib/live/shared/muxing-session.ts4
-rw-r--r--server/lib/plugins/plugin-helpers-builder.ts2
-rw-r--r--server/lib/plugins/register-helpers.ts2
-rw-r--r--server/lib/server-config-manager.ts5
-rw-r--r--server/lib/thumbnail.ts5
-rw-r--r--server/lib/transcoding/default-transcoding-profiles.ts (renamed from server/lib/transcoding/video-transcoding-profiles.ts)25
-rw-r--r--server/lib/transcoding/transcoding.ts (renamed from server/lib/transcoding/video-transcoding.ts)35
-rw-r--r--server/lib/user.ts6
-rw-r--r--server/lib/video-editor.ts32
-rw-r--r--server/lib/video.ts6
18 files changed, 357 insertions, 61 deletions
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index 985f50587..43043315b 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -4,7 +4,7 @@ import { basename, dirname, join } from 'path'
4import { MStreamingPlaylistFilesVideo, MVideo, MVideoUUID } from '@server/types/models' 4import { MStreamingPlaylistFilesVideo, MVideo, MVideoUUID } from '@server/types/models'
5import { sha256 } from '@shared/extra-utils' 5import { sha256 } from '@shared/extra-utils'
6import { VideoStorage } from '@shared/models' 6import { VideoStorage } from '@shared/models'
7import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamSize } from '../helpers/ffprobe-utils' 7import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamDimensionsInfo } from '../helpers/ffmpeg'
8import { logger } from '../helpers/logger' 8import { logger } from '../helpers/logger'
9import { doRequest, doRequestAndSaveToFile } from '../helpers/requests' 9import { doRequest, doRequestAndSaveToFile } from '../helpers/requests'
10import { generateRandomString } from '../helpers/utils' 10import { generateRandomString } from '../helpers/utils'
@@ -40,10 +40,10 @@ async function updateMasterHLSPlaylist (video: MVideo, playlist: MStreamingPlayl
40 const playlistFilename = getHlsResolutionPlaylistFilename(file.filename) 40 const playlistFilename = getHlsResolutionPlaylistFilename(file.filename)
41 41
42 await VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(playlist), async videoFilePath => { 42 await VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(playlist), async videoFilePath => {
43 const size = await getVideoStreamSize(videoFilePath) 43 const size = await getVideoStreamDimensionsInfo(videoFilePath)
44 44
45 const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file) 45 const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file)
46 const resolution = `RESOLUTION=${size.width}x${size.height}` 46 const resolution = `RESOLUTION=${size?.width || 0}x${size?.height || 0}`
47 47
48 let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}` 48 let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}`
49 if (file.fps) line += ',FRAME-RATE=' + file.fps 49 if (file.fps) line += ',FRAME-RATE=' + file.fps
diff --git a/server/lib/job-queue/handlers/video-edition.ts b/server/lib/job-queue/handlers/video-edition.ts
new file mode 100644
index 000000000..c5ba0452f
--- /dev/null
+++ b/server/lib/job-queue/handlers/video-edition.ts
@@ -0,0 +1,229 @@
1import { Job } from 'bull'
2import { move, remove } from 'fs-extra'
3import { join } from 'path'
4import { addIntroOutro, addWatermark, cutVideo } from '@server/helpers/ffmpeg'
5import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent'
6import { CONFIG } from '@server/initializers/config'
7import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
8import { generateWebTorrentVideoFilename } from '@server/lib/paths'
9import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles'
10import { isAbleToUploadVideo } from '@server/lib/user'
11import { addMoveToObjectStorageJob, addOptimizeOrMergeAudioJob } from '@server/lib/video'
12import { approximateIntroOutroAdditionalSize } from '@server/lib/video-editor'
13import { VideoPathManager } from '@server/lib/video-path-manager'
14import { buildNextVideoState } from '@server/lib/video-state'
15import { UserModel } from '@server/models/user/user'
16import { VideoModel } from '@server/models/video/video'
17import { VideoFileModel } from '@server/models/video/video-file'
18import { MVideo, MVideoFile, MVideoFullLight, MVideoId, MVideoWithAllFiles } from '@server/types/models'
19import { getLowercaseExtension, pick } from '@shared/core-utils'
20import {
21 buildFileMetadata,
22 buildUUID,
23 ffprobePromise,
24 getFileSize,
25 getVideoStreamDimensionsInfo,
26 getVideoStreamDuration,
27 getVideoStreamFPS
28} from '@shared/extra-utils'
29import {
30 VideoEditionPayload,
31 VideoEditionTaskPayload,
32 VideoEditorTask,
33 VideoEditorTaskCutPayload,
34 VideoEditorTaskIntroPayload,
35 VideoEditorTaskOutroPayload,
36 VideoEditorTaskWatermarkPayload,
37 VideoState
38} from '@shared/models'
39import { logger, loggerTagsFactory } from '../../../helpers/logger'
40
41const lTagsBase = loggerTagsFactory('video-edition')
42
43async function processVideoEdition (job: Job) {
44 const payload = job.data as VideoEditionPayload
45
46 logger.info('Process video edition of %s in job %d.', payload.videoUUID, job.id)
47
48 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID)
49
50 // No video, maybe deleted?
51 if (!video) {
52 logger.info('Can\'t process job %d, video does not exist.', job.id, lTagsBase(payload.videoUUID))
53 return undefined
54 }
55
56 await checkUserQuotaOrThrow(video, payload)
57
58 const inputFile = video.getMaxQualityFile()
59
60 const editionResultPath = await VideoPathManager.Instance.makeAvailableVideoFile(inputFile, async originalFilePath => {
61 let tmpInputFilePath: string
62 let outputPath: string
63
64 for (const task of payload.tasks) {
65 const outputFilename = buildUUID() + inputFile.extname
66 outputPath = join(CONFIG.STORAGE.TMP_DIR, outputFilename)
67
68 await processTask({
69 inputPath: tmpInputFilePath ?? originalFilePath,
70 video,
71 outputPath,
72 task
73 })
74
75 if (tmpInputFilePath) await remove(tmpInputFilePath)
76
77 // For the next iteration
78 tmpInputFilePath = outputPath
79 }
80
81 return outputPath
82 })
83
84 logger.info('Video edition ended for video %s.', video.uuid)
85
86 const newFile = await buildNewFile(video, editionResultPath)
87
88 const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile)
89 await move(editionResultPath, outputPath)
90
91 await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath)
92
93 await removeAllFiles(video, newFile)
94
95 await newFile.save()
96
97 video.state = buildNextVideoState()
98 video.duration = await getVideoStreamDuration(outputPath)
99 await video.save()
100
101 await federateVideoIfNeeded(video, false, undefined)
102
103 if (video.state === VideoState.TO_TRANSCODE) {
104 const user = await UserModel.loadByVideoId(video.id)
105
106 await addOptimizeOrMergeAudioJob(video, newFile, user, false)
107 } else if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE) {
108 await addMoveToObjectStorageJob(video, false)
109 }
110}
111
112// ---------------------------------------------------------------------------
113
114export {
115 processVideoEdition
116}
117
118// ---------------------------------------------------------------------------
119
120type TaskProcessorOptions <T extends VideoEditionTaskPayload = VideoEditionTaskPayload> = {
121 inputPath: string
122 outputPath: string
123 video: MVideo
124 task: T
125}
126
127const taskProcessors: { [id in VideoEditorTask['name']]: (options: TaskProcessorOptions) => Promise<any> } = {
128 'add-intro': processAddIntroOutro,
129 'add-outro': processAddIntroOutro,
130 'cut': processCut,
131 'add-watermark': processAddWatermark
132}
133
134async function processTask (options: TaskProcessorOptions) {
135 const { video, task } = options
136
137 logger.info('Processing %s task for video %s.', task.name, video.uuid, { task })
138
139 const processor = taskProcessors[options.task.name]
140 if (!process) throw new Error('Unknown task ' + task.name)
141
142 return processor(options)
143}
144
145function processAddIntroOutro (options: TaskProcessorOptions<VideoEditorTaskIntroPayload | VideoEditorTaskOutroPayload>) {
146 const { task } = options
147
148 return addIntroOutro({
149 ...pick(options, [ 'inputPath', 'outputPath' ]),
150
151 introOutroPath: task.options.file,
152 type: task.name === 'add-intro'
153 ? 'intro'
154 : 'outro',
155
156 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
157 profile: CONFIG.TRANSCODING.PROFILE
158 })
159}
160
161function processCut (options: TaskProcessorOptions<VideoEditorTaskCutPayload>) {
162 const { task } = options
163
164 return cutVideo({
165 ...pick(options, [ 'inputPath', 'outputPath' ]),
166
167 start: task.options.start,
168 end: task.options.end
169 })
170}
171
172function processAddWatermark (options: TaskProcessorOptions<VideoEditorTaskWatermarkPayload>) {
173 const { task } = options
174
175 return addWatermark({
176 ...pick(options, [ 'inputPath', 'outputPath' ]),
177
178 watermarkPath: task.options.file,
179
180 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
181 profile: CONFIG.TRANSCODING.PROFILE
182 })
183}
184
185async function buildNewFile (video: MVideoId, path: string) {
186 const videoFile = new VideoFileModel({
187 extname: getLowercaseExtension(path),
188 size: await getFileSize(path),
189 metadata: await buildFileMetadata(path),
190 videoStreamingPlaylistId: null,
191 videoId: video.id
192 })
193
194 const probe = await ffprobePromise(path)
195
196 videoFile.fps = await getVideoStreamFPS(path, probe)
197 videoFile.resolution = (await getVideoStreamDimensionsInfo(path, probe)).resolution
198
199 videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname)
200
201 return videoFile
202}
203
204async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) {
205 const hls = video.getHLSPlaylist()
206
207 if (hls) {
208 await video.removeStreamingPlaylistFiles(hls)
209 await hls.destroy()
210 }
211
212 for (const file of video.VideoFiles) {
213 if (file.id === webTorrentFileException.id) continue
214
215 await video.removeWebTorrentFileAndTorrent(file)
216 await file.destroy()
217 }
218}
219
220async function checkUserQuotaOrThrow (video: MVideoFullLight, payload: VideoEditionPayload) {
221 const user = await UserModel.loadByVideoId(video.id)
222
223 const filePathFinder = (i: number) => (payload.tasks[i] as VideoEditorTaskIntroPayload | VideoEditorTaskOutroPayload).options.file
224
225 const additionalBytes = await approximateIntroOutroAdditionalSize(video, payload.tasks, filePathFinder)
226 if (await isAbleToUploadVideo(user.id, additionalBytes) === false) {
227 throw new Error('Quota exceeded for this user to edit the video')
228 }
229}
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 0d9e80cb8..6b2d60317 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -1,18 +1,18 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { copy, stat } from 'fs-extra' 2import { copy, stat } from 'fs-extra'
3import { getLowercaseExtension } from '@shared/core-utils'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 3import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
6import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' 5import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
7import { generateWebTorrentVideoFilename } from '@server/lib/paths' 6import { generateWebTorrentVideoFilename } from '@server/lib/paths'
8import { addMoveToObjectStorageJob } from '@server/lib/video' 7import { addMoveToObjectStorageJob } from '@server/lib/video'
9import { VideoPathManager } from '@server/lib/video-path-manager' 8import { VideoPathManager } from '@server/lib/video-path-manager'
9import { VideoModel } from '@server/models/video/video'
10import { VideoFileModel } from '@server/models/video/video-file'
10import { MVideoFullLight } from '@server/types/models' 11import { MVideoFullLight } from '@server/types/models'
12import { getLowercaseExtension } from '@shared/core-utils'
11import { VideoFileImportPayload, VideoStorage } from '@shared/models' 13import { VideoFileImportPayload, VideoStorage } from '@shared/models'
12import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 14import { getVideoStreamFPS, getVideoStreamDimensionsInfo } from '../../../helpers/ffmpeg'
13import { logger } from '../../../helpers/logger' 15import { logger } from '../../../helpers/logger'
14import { VideoModel } from '../../../models/video/video'
15import { VideoFileModel } from '../../../models/video/video-file'
16 16
17async function processVideoFileImport (job: Job) { 17async function processVideoFileImport (job: Job) {
18 const payload = job.data as VideoFileImportPayload 18 const payload = job.data as VideoFileImportPayload
@@ -45,9 +45,9 @@ export {
45// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
46 46
47async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) { 47async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) {
48 const { resolution } = await getVideoFileResolution(inputFilePath) 48 const { resolution } = await getVideoStreamDimensionsInfo(inputFilePath)
49 const { size } = await stat(inputFilePath) 49 const { size } = await stat(inputFilePath)
50 const fps = await getVideoFileFPS(inputFilePath) 50 const fps = await getVideoStreamFPS(inputFilePath)
51 51
52 const fileExt = getLowercaseExtension(inputFilePath) 52 const fileExt = getLowercaseExtension(inputFilePath)
53 53
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index b6e05d8f5..b3ca28c2f 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -25,7 +25,7 @@ import {
25 VideoResolution, 25 VideoResolution,
26 VideoState 26 VideoState
27} from '@shared/models' 27} from '@shared/models'
28import { ffprobePromise, getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 28import { ffprobePromise, getVideoStreamDuration, getVideoStreamFPS, getVideoStreamDimensionsInfo } from '../../../helpers/ffmpeg'
29import { logger } from '../../../helpers/logger' 29import { logger } from '../../../helpers/logger'
30import { getSecureTorrentName } from '../../../helpers/utils' 30import { getSecureTorrentName } from '../../../helpers/utils'
31import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' 31import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent'
@@ -121,10 +121,10 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
121 121
122 const { resolution } = await isAudioFile(tempVideoPath, probe) 122 const { resolution } = await isAudioFile(tempVideoPath, probe)
123 ? { resolution: VideoResolution.H_NOVIDEO } 123 ? { resolution: VideoResolution.H_NOVIDEO }
124 : await getVideoFileResolution(tempVideoPath) 124 : await getVideoStreamDimensionsInfo(tempVideoPath)
125 125
126 const fps = await getVideoFileFPS(tempVideoPath, probe) 126 const fps = await getVideoStreamFPS(tempVideoPath, probe)
127 const duration = await getDurationFromVideoFile(tempVideoPath, probe) 127 const duration = await getVideoStreamDuration(tempVideoPath, probe)
128 128
129 // Prepare video file object for creation in database 129 // Prepare video file object for creation in database
130 const fileExt = getLowercaseExtension(tempVideoPath) 130 const fileExt = getLowercaseExtension(tempVideoPath)
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index a04cfa2c9..497f6612a 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -1,12 +1,12 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { pathExists, readdir, remove } from 'fs-extra' 2import { pathExists, readdir, remove } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import { ffprobePromise, getAudioStream, getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils' 4import { ffprobePromise, getAudioStream, getVideoStreamDuration, getVideoStreamDimensionsInfo } from '@server/helpers/ffmpeg'
5import { VIDEO_LIVE } from '@server/initializers/constants' 5import { VIDEO_LIVE } from '@server/initializers/constants'
6import { buildConcatenatedName, cleanupLive, LiveSegmentShaStore } from '@server/lib/live' 6import { buildConcatenatedName, cleanupLive, LiveSegmentShaStore } from '@server/lib/live'
7import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveDirectory } from '@server/lib/paths' 7import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveDirectory } from '@server/lib/paths'
8import { generateVideoMiniature } from '@server/lib/thumbnail' 8import { generateVideoMiniature } from '@server/lib/thumbnail'
9import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding' 9import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/transcoding'
10import { VideoPathManager } from '@server/lib/video-path-manager' 10import { VideoPathManager } from '@server/lib/video-path-manager'
11import { moveToNextState } from '@server/lib/video-state' 11import { moveToNextState } from '@server/lib/video-state'
12import { VideoModel } from '@server/models/video/video' 12import { VideoModel } from '@server/models/video/video'
@@ -96,7 +96,7 @@ async function saveLive (video: MVideo, live: MVideoLive, streamingPlaylist: MSt
96 const probe = await ffprobePromise(concatenatedTsFilePath) 96 const probe = await ffprobePromise(concatenatedTsFilePath)
97 const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe) 97 const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe)
98 98
99 const { resolution, isPortraitMode } = await getVideoFileResolution(concatenatedTsFilePath, probe) 99 const { resolution, isPortraitMode } = await getVideoStreamDimensionsInfo(concatenatedTsFilePath, probe)
100 100
101 const { resolutionPlaylistPath: outputPath } = await generateHlsPlaylistResolutionFromTS({ 101 const { resolutionPlaylistPath: outputPath } = await generateHlsPlaylistResolutionFromTS({
102 video: videoWithFiles, 102 video: videoWithFiles,
@@ -107,7 +107,7 @@ async function saveLive (video: MVideo, live: MVideoLive, streamingPlaylist: MSt
107 }) 107 })
108 108
109 if (!durationDone) { 109 if (!durationDone) {
110 videoWithFiles.duration = await getDurationFromVideoFile(outputPath) 110 videoWithFiles.duration = await getVideoStreamDuration(outputPath)
111 await videoWithFiles.save() 111 await videoWithFiles.save()
112 112
113 durationDone = true 113 durationDone = true
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 5540b791d..512979734 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -1,5 +1,5 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' 2import { TranscodeVODOptionsType } from '@server/helpers/ffmpeg'
3import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video' 3import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video'
4import { VideoPathManager } from '@server/lib/video-path-manager' 4import { VideoPathManager } from '@server/lib/video-path-manager'
5import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state' 5import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state'
@@ -16,7 +16,7 @@ import {
16 VideoTranscodingPayload 16 VideoTranscodingPayload
17} from '@shared/models' 17} from '@shared/models'
18import { retryTransactionWrapper } from '../../../helpers/database-utils' 18import { retryTransactionWrapper } from '../../../helpers/database-utils'
19import { computeLowerResolutionsToTranscode } from '../../../helpers/ffprobe-utils' 19import { computeLowerResolutionsToTranscode } from '../../../helpers/ffmpeg'
20import { logger, loggerTagsFactory } from '../../../helpers/logger' 20import { logger, loggerTagsFactory } from '../../../helpers/logger'
21import { CONFIG } from '../../../initializers/config' 21import { CONFIG } from '../../../initializers/config'
22import { VideoModel } from '../../../models/video/video' 22import { VideoModel } from '../../../models/video/video'
@@ -25,7 +25,7 @@ import {
25 mergeAudioVideofile, 25 mergeAudioVideofile,
26 optimizeOriginalVideofile, 26 optimizeOriginalVideofile,
27 transcodeNewWebTorrentResolution 27 transcodeNewWebTorrentResolution
28} from '../../transcoding/video-transcoding' 28} from '../../transcoding/transcoding'
29 29
30type HandlerFunction = (job: Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<void> 30type HandlerFunction = (job: Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<void>
31 31
@@ -174,10 +174,10 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
174async function onVideoFirstWebTorrentTranscoding ( 174async function onVideoFirstWebTorrentTranscoding (
175 videoArg: MVideoWithFile, 175 videoArg: MVideoWithFile,
176 payload: OptimizeTranscodingPayload | MergeAudioTranscodingPayload, 176 payload: OptimizeTranscodingPayload | MergeAudioTranscodingPayload,
177 transcodeType: TranscodeOptionsType, 177 transcodeType: TranscodeVODOptionsType,
178 user: MUserId 178 user: MUserId
179) { 179) {
180 const { resolution, isPortraitMode, audioStream } = await videoArg.getMaxQualityFileInfo() 180 const { resolution, isPortraitMode, audioStream } = await videoArg.probeMaxQualityFile()
181 181
182 // Maybe the video changed in database, refresh it 182 // Maybe the video changed in database, refresh it
183 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) 183 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid)
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index 22bd1f5d2..e10a3bab5 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -14,6 +14,7 @@ import {
14 JobType, 14 JobType,
15 MoveObjectStoragePayload, 15 MoveObjectStoragePayload,
16 RefreshPayload, 16 RefreshPayload,
17 VideoEditionPayload,
17 VideoFileImportPayload, 18 VideoFileImportPayload,
18 VideoImportPayload, 19 VideoImportPayload,
19 VideoLiveEndingPayload, 20 VideoLiveEndingPayload,
@@ -31,6 +32,7 @@ import { refreshAPObject } from './handlers/activitypub-refresher'
31import { processActorKeys } from './handlers/actor-keys' 32import { processActorKeys } from './handlers/actor-keys'
32import { processEmail } from './handlers/email' 33import { processEmail } from './handlers/email'
33import { processMoveToObjectStorage } from './handlers/move-to-object-storage' 34import { processMoveToObjectStorage } from './handlers/move-to-object-storage'
35import { processVideoEdition } from './handlers/video-edition'
34import { processVideoFileImport } from './handlers/video-file-import' 36import { processVideoFileImport } from './handlers/video-file-import'
35import { processVideoImport } from './handlers/video-import' 37import { processVideoImport } from './handlers/video-import'
36import { processVideoLiveEnding } from './handlers/video-live-ending' 38import { processVideoLiveEnding } from './handlers/video-live-ending'
@@ -53,6 +55,7 @@ type CreateJobArgument =
53 { type: 'actor-keys', payload: ActorKeysPayload } | 55 { type: 'actor-keys', payload: ActorKeysPayload } |
54 { type: 'video-redundancy', payload: VideoRedundancyPayload } | 56 { type: 'video-redundancy', payload: VideoRedundancyPayload } |
55 { type: 'delete-resumable-upload-meta-file', payload: DeleteResumableUploadMetaFilePayload } | 57 { type: 'delete-resumable-upload-meta-file', payload: DeleteResumableUploadMetaFilePayload } |
58 { type: 'video-edition', payload: VideoEditionPayload } |
56 { type: 'move-to-object-storage', payload: MoveObjectStoragePayload } 59 { type: 'move-to-object-storage', payload: MoveObjectStoragePayload }
57 60
58export type CreateJobOptions = { 61export type CreateJobOptions = {
@@ -75,7 +78,8 @@ const handlers: { [id in JobType]: (job: Job) => Promise<any> } = {
75 'video-live-ending': processVideoLiveEnding, 78 'video-live-ending': processVideoLiveEnding,
76 'actor-keys': processActorKeys, 79 'actor-keys': processActorKeys,
77 'video-redundancy': processVideoRedundancy, 80 'video-redundancy': processVideoRedundancy,
78 'move-to-object-storage': processMoveToObjectStorage 81 'move-to-object-storage': processMoveToObjectStorage,
82 'video-edition': processVideoEdition
79} 83}
80 84
81const jobTypes: JobType[] = [ 85const jobTypes: JobType[] = [
@@ -93,7 +97,8 @@ const jobTypes: JobType[] = [
93 'video-redundancy', 97 'video-redundancy',
94 'actor-keys', 98 'actor-keys',
95 'video-live-ending', 99 'video-live-ending',
96 'move-to-object-storage' 100 'move-to-object-storage',
101 'video-edition'
97] 102]
98 103
99class JobQueue { 104class JobQueue {
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts
index 33e49acc1..21c34a9a4 100644
--- a/server/lib/live/live-manager.ts
+++ b/server/lib/live/live-manager.ts
@@ -5,10 +5,10 @@ import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
5import { 5import {
6 computeLowerResolutionsToTranscode, 6 computeLowerResolutionsToTranscode,
7 ffprobePromise, 7 ffprobePromise,
8 getVideoFileBitrate, 8 getVideoStreamBitrate,
9 getVideoFileFPS, 9 getVideoStreamFPS,
10 getVideoFileResolution 10 getVideoStreamDimensionsInfo
11} from '@server/helpers/ffprobe-utils' 11} from '@server/helpers/ffmpeg'
12import { logger, loggerTagsFactory } from '@server/helpers/logger' 12import { logger, loggerTagsFactory } from '@server/helpers/logger'
13import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' 13import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
14import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants' 14import { P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE } from '@server/initializers/constants'
@@ -226,9 +226,9 @@ class LiveManager {
226 const probe = await ffprobePromise(inputUrl) 226 const probe = await ffprobePromise(inputUrl)
227 227
228 const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([ 228 const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([
229 getVideoFileResolution(inputUrl, probe), 229 getVideoStreamDimensionsInfo(inputUrl, probe),
230 getVideoFileFPS(inputUrl, probe), 230 getVideoStreamFPS(inputUrl, probe),
231 getVideoFileBitrate(inputUrl, probe) 231 getVideoStreamBitrate(inputUrl, probe)
232 ]) 232 ])
233 233
234 logger.info( 234 logger.info(
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts
index 22a47942a..f5f473039 100644
--- a/server/lib/live/shared/muxing-session.ts
+++ b/server/lib/live/shared/muxing-session.ts
@@ -5,14 +5,14 @@ import { FfmpegCommand } from 'fluent-ffmpeg'
5import { appendFile, ensureDir, readFile, stat } from 'fs-extra' 5import { appendFile, ensureDir, readFile, stat } from 'fs-extra'
6import { basename, join } from 'path' 6import { basename, join } from 'path'
7import { EventEmitter } from 'stream' 7import { EventEmitter } from 'stream'
8import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg-utils' 8import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg'
9import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger' 9import { logger, loggerTagsFactory, LoggerTagsFn } from '@server/helpers/logger'
10import { CONFIG } from '@server/initializers/config' 10import { CONFIG } from '@server/initializers/config'
11import { MEMOIZE_TTL, VIDEO_LIVE } from '@server/initializers/constants' 11import { MEMOIZE_TTL, VIDEO_LIVE } from '@server/initializers/constants'
12import { VideoFileModel } from '@server/models/video/video-file' 12import { VideoFileModel } from '@server/models/video/video-file'
13import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models' 13import { MStreamingPlaylistVideo, MUserId, MVideoLiveVideo } from '@server/types/models'
14import { getLiveDirectory } from '../../paths' 14import { getLiveDirectory } from '../../paths'
15import { VideoTranscodingProfilesManager } from '../../transcoding/video-transcoding-profiles' 15import { VideoTranscodingProfilesManager } from '../../transcoding/default-transcoding-profiles'
16import { isAbleToUploadVideo } from '../../user' 16import { isAbleToUploadVideo } from '../../user'
17import { LiveQuotaStore } from '../live-quota-store' 17import { LiveQuotaStore } from '../live-quota-store'
18import { LiveSegmentShaStore } from '../live-segment-sha-store' 18import { LiveSegmentShaStore } from '../live-segment-sha-store'
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts
index 78e4a28ad..897271c0b 100644
--- a/server/lib/plugins/plugin-helpers-builder.ts
+++ b/server/lib/plugins/plugin-helpers-builder.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { ffprobePromise } from '@server/helpers/ffprobe-utils' 3import { ffprobePromise } from '@server/helpers/ffmpeg/ffprobe-utils'
4import { buildLogger } from '@server/helpers/logger' 4import { buildLogger } from '@server/helpers/logger'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
diff --git a/server/lib/plugins/register-helpers.ts b/server/lib/plugins/register-helpers.ts
index d1756040a..f4d405676 100644
--- a/server/lib/plugins/register-helpers.ts
+++ b/server/lib/plugins/register-helpers.ts
@@ -21,7 +21,7 @@ import {
21 VideoPlaylistPrivacy, 21 VideoPlaylistPrivacy,
22 VideoPrivacy 22 VideoPrivacy
23} from '@shared/models' 23} from '@shared/models'
24import { VideoTranscodingProfilesManager } from '../transcoding/video-transcoding-profiles' 24import { VideoTranscodingProfilesManager } from '../transcoding/default-transcoding-profiles'
25import { buildPluginHelpers } from './plugin-helpers-builder' 25import { buildPluginHelpers } from './plugin-helpers-builder'
26 26
27export class RegisterHelpers { 27export class RegisterHelpers {
diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts
index d97f21eb7..38512f384 100644
--- a/server/lib/server-config-manager.ts
+++ b/server/lib/server-config-manager.ts
@@ -8,7 +8,7 @@ import { HTMLServerConfig, RegisteredExternalAuthConfig, RegisteredIdAndPassAuth
8import { Hooks } from './plugins/hooks' 8import { Hooks } from './plugins/hooks'
9import { PluginManager } from './plugins/plugin-manager' 9import { PluginManager } from './plugins/plugin-manager'
10import { getThemeOrDefault } from './plugins/theme-utils' 10import { getThemeOrDefault } from './plugins/theme-utils'
11import { VideoTranscodingProfilesManager } from './transcoding/video-transcoding-profiles' 11import { VideoTranscodingProfilesManager } from './transcoding/default-transcoding-profiles'
12 12
13/** 13/**
14 * 14 *
@@ -151,6 +151,9 @@ class ServerConfigManager {
151 port: CONFIG.LIVE.RTMP.PORT 151 port: CONFIG.LIVE.RTMP.PORT
152 } 152 }
153 }, 153 },
154 videoEditor: {
155 enabled: CONFIG.VIDEO_EDITOR.ENABLED
156 },
154 import: { 157 import: {
155 videos: { 158 videos: {
156 http: { 159 http: {
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index 36270e5c1..aa2d7a813 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,7 +1,6 @@
1import { join } from 'path' 1import { join } from 'path'
2import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 2import { ThumbnailType } from '@shared/models'
3import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 3import { generateImageFilename, generateImageFromVideoFile, processImage } from '../helpers/image-utils'
4import { generateImageFilename, processImage } from '../helpers/image-utils'
5import { downloadImage } from '../helpers/requests' 4import { downloadImage } from '../helpers/requests'
6import { CONFIG } from '../initializers/config' 5import { CONFIG } from '../initializers/config'
7import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' 6import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
diff --git a/server/lib/transcoding/video-transcoding-profiles.ts b/server/lib/transcoding/default-transcoding-profiles.ts
index dcc8d4c5c..ba98a11ca 100644
--- a/server/lib/transcoding/video-transcoding-profiles.ts
+++ b/server/lib/transcoding/default-transcoding-profiles.ts
@@ -2,8 +2,14 @@
2import { logger } from '@server/helpers/logger' 2import { logger } from '@server/helpers/logger'
3import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' 3import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils'
4import { AvailableEncoders, EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '../../../shared/models/videos' 4import { AvailableEncoders, EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '../../../shared/models/videos'
5import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils' 5import {
6import { canDoQuickAudioTranscode, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '../../helpers/ffprobe-utils' 6 buildStreamSuffix,
7 canDoQuickAudioTranscode,
8 ffprobePromise,
9 getAudioStream,
10 getMaxAudioBitrate,
11 resetSupportedEncoders
12} from '../../helpers/ffmpeg'
7 13
8/** 14/**
9 * 15 *
@@ -15,8 +21,14 @@ import { canDoQuickAudioTranscode, ffprobePromise, getAudioStream, getMaxAudioBi
15 * * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate 21 * * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
16 */ 22 */
17 23
24// ---------------------------------------------------------------------------
25// Default builders
26// ---------------------------------------------------------------------------
27
18const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { 28const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => {
19 const { fps, inputRatio, inputBitrate, resolution } = options 29 const { fps, inputRatio, inputBitrate, resolution } = options
30
31 // TODO: remove in 4.2, fps is not optional anymore
20 if (!fps) return { outputOptions: [ ] } 32 if (!fps) return { outputOptions: [ ] }
21 33
22 const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) 34 const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution })
@@ -45,10 +57,10 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
45 } 57 }
46} 58}
47 59
48const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum }) => { 60const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => {
49 const probe = await ffprobePromise(input) 61 const probe = await ffprobePromise(input)
50 62
51 if (await canDoQuickAudioTranscode(input, probe)) { 63 if (canCopyAudio && await canDoQuickAudioTranscode(input, probe)) {
52 logger.debug('Copy audio stream %s by AAC encoder.', input) 64 logger.debug('Copy audio stream %s by AAC encoder.', input)
53 return { copy: true, outputOptions: [ ] } 65 return { copy: true, outputOptions: [ ] }
54 } 66 }
@@ -75,7 +87,10 @@ const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum })
75 return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } 87 return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] }
76} 88}
77 89
78// Used to get and update available encoders 90// ---------------------------------------------------------------------------
91// Profile manager to get and change default profiles
92// ---------------------------------------------------------------------------
93
79class VideoTranscodingProfilesManager { 94class VideoTranscodingProfilesManager {
80 private static instance: VideoTranscodingProfilesManager 95 private static instance: VideoTranscodingProfilesManager
81 96
diff --git a/server/lib/transcoding/video-transcoding.ts b/server/lib/transcoding/transcoding.ts
index 9942a067b..d55364e25 100644
--- a/server/lib/transcoding/video-transcoding.ts
+++ b/server/lib/transcoding/transcoding.ts
@@ -6,8 +6,15 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
6import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 6import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
7import { VideoResolution, VideoStorage } from '../../../shared/models/videos' 7import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
8import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 8import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
9import { transcode, TranscodeOptions, TranscodeOptionsType } from '../../helpers/ffmpeg-utils' 9import {
10import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../../helpers/ffprobe-utils' 10 canDoQuickTranscode,
11 getVideoStreamDuration,
12 buildFileMetadata,
13 getVideoStreamFPS,
14 transcodeVOD,
15 TranscodeVODOptions,
16 TranscodeVODOptionsType
17} from '../../helpers/ffmpeg'
11import { CONFIG } from '../../initializers/config' 18import { CONFIG } from '../../initializers/config'
12import { P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' 19import { P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants'
13import { VideoFileModel } from '../../models/video/video-file' 20import { VideoFileModel } from '../../models/video/video-file'
@@ -21,7 +28,7 @@ import {
21 getHlsResolutionPlaylistFilename 28 getHlsResolutionPlaylistFilename
22} from '../paths' 29} from '../paths'
23import { VideoPathManager } from '../video-path-manager' 30import { VideoPathManager } from '../video-path-manager'
24import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' 31import { VideoTranscodingProfilesManager } from './default-transcoding-profiles'
25 32
26/** 33/**
27 * 34 *
@@ -38,13 +45,13 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
38 return VideoPathManager.Instance.makeAvailableVideoFile(inputVideoFile.withVideoOrPlaylist(video), async videoInputPath => { 45 return VideoPathManager.Instance.makeAvailableVideoFile(inputVideoFile.withVideoOrPlaylist(video), async videoInputPath => {
39 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) 46 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
40 47
41 const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) 48 const transcodeType: TranscodeVODOptionsType = await canDoQuickTranscode(videoInputPath)
42 ? 'quick-transcode' 49 ? 'quick-transcode'
43 : 'video' 50 : 'video'
44 51
45 const resolution = toEven(inputVideoFile.resolution) 52 const resolution = toEven(inputVideoFile.resolution)
46 53
47 const transcodeOptions: TranscodeOptions = { 54 const transcodeOptions: TranscodeVODOptions = {
48 type: transcodeType, 55 type: transcodeType,
49 56
50 inputPath: videoInputPath, 57 inputPath: videoInputPath,
@@ -59,7 +66,7 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
59 } 66 }
60 67
61 // Could be very long! 68 // Could be very long!
62 await transcode(transcodeOptions) 69 await transcodeVOD(transcodeOptions)
63 70
64 // Important to do this before getVideoFilename() to take in account the new filename 71 // Important to do this before getVideoFilename() to take in account the new filename
65 inputVideoFile.extname = newExtname 72 inputVideoFile.extname = newExtname
@@ -121,7 +128,7 @@ function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: V
121 job 128 job
122 } 129 }
123 130
124 await transcode(transcodeOptions) 131 await transcodeVOD(transcodeOptions)
125 132
126 return onWebTorrentVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) 133 return onWebTorrentVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
127 }) 134 })
@@ -158,7 +165,7 @@ function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoResolutio
158 } 165 }
159 166
160 try { 167 try {
161 await transcode(transcodeOptions) 168 await transcodeVOD(transcodeOptions)
162 169
163 await remove(audioInputPath) 170 await remove(audioInputPath)
164 await remove(tmpPreviewPath) 171 await remove(tmpPreviewPath)
@@ -175,7 +182,7 @@ function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoResolutio
175 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile) 182 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile)
176 // ffmpeg generated a new video file, so update the video duration 183 // ffmpeg generated a new video file, so update the video duration
177 // See https://trac.ffmpeg.org/ticket/5456 184 // See https://trac.ffmpeg.org/ticket/5456
178 video.duration = await getDurationFromVideoFile(videoTranscodedPath) 185 video.duration = await getVideoStreamDuration(videoTranscodedPath)
179 await video.save() 186 await video.save()
180 187
181 return onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 188 return onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
@@ -239,8 +246,8 @@ async function onWebTorrentVideoFileTranscoding (
239 outputPath: string 246 outputPath: string
240) { 247) {
241 const stats = await stat(transcodingPath) 248 const stats = await stat(transcodingPath)
242 const fps = await getVideoFileFPS(transcodingPath) 249 const fps = await getVideoStreamFPS(transcodingPath)
243 const metadata = await getMetadataFromFile(transcodingPath) 250 const metadata = await buildFileMetadata(transcodingPath)
244 251
245 await move(transcodingPath, outputPath, { overwrite: true }) 252 await move(transcodingPath, outputPath, { overwrite: true })
246 253
@@ -299,7 +306,7 @@ async function generateHlsPlaylistCommon (options: {
299 job 306 job
300 } 307 }
301 308
302 await transcode(transcodeOptions) 309 await transcodeVOD(transcodeOptions)
303 310
304 // Create or update the playlist 311 // Create or update the playlist
305 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video) 312 const playlist = await VideoStreamingPlaylistModel.loadOrGenerate(video)
@@ -344,8 +351,8 @@ async function generateHlsPlaylistCommon (options: {
344 const stats = await stat(videoFilePath) 351 const stats = await stat(videoFilePath)
345 352
346 newVideoFile.size = stats.size 353 newVideoFile.size = stats.size
347 newVideoFile.fps = await getVideoFileFPS(videoFilePath) 354 newVideoFile.fps = await getVideoStreamFPS(videoFilePath)
348 newVideoFile.metadata = await getMetadataFromFile(videoFilePath) 355 newVideoFile.metadata = await buildFileMetadata(videoFilePath)
349 356
350 await createTorrentAndSetInfoHash(playlist, newVideoFile) 357 await createTorrentAndSetInfoHash(playlist, newVideoFile)
351 358
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 0d292ac90..3f7499296 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -19,6 +19,7 @@ import { buildActorInstance } from './local-actor'
19import { Redis } from './redis' 19import { Redis } from './redis'
20import { createLocalVideoChannel } from './video-channel' 20import { createLocalVideoChannel } from './video-channel'
21import { createWatchLaterPlaylist } from './video-playlist' 21import { createWatchLaterPlaylist } from './video-playlist'
22import { logger } from '@server/helpers/logger'
22 23
23type ChannelNames = { name: string, displayName: string } 24type ChannelNames = { name: string, displayName: string }
24 25
@@ -159,6 +160,11 @@ async function isAbleToUploadVideo (userId: number, newVideoSize: number) {
159 const uploadedTotal = newVideoSize + totalBytes 160 const uploadedTotal = newVideoSize + totalBytes
160 const uploadedDaily = newVideoSize + totalBytesDaily 161 const uploadedDaily = newVideoSize + totalBytesDaily
161 162
163 logger.debug(
164 'Check user %d quota to upload another video.', userId,
165 { totalBytes, totalBytesDaily, videoQuota: user.videoQuota, videoQuotaDaily: user.videoQuotaDaily, newVideoSize }
166 )
167
162 if (user.videoQuotaDaily === -1) return uploadedTotal < user.videoQuota 168 if (user.videoQuotaDaily === -1) return uploadedTotal < user.videoQuota
163 if (user.videoQuota === -1) return uploadedDaily < user.videoQuotaDaily 169 if (user.videoQuota === -1) return uploadedDaily < user.videoQuotaDaily
164 170
diff --git a/server/lib/video-editor.ts b/server/lib/video-editor.ts
new file mode 100644
index 000000000..99b0bd949
--- /dev/null
+++ b/server/lib/video-editor.ts
@@ -0,0 +1,32 @@
1import { MVideoFullLight } from "@server/types/models"
2import { getVideoStreamDuration } from "@shared/extra-utils"
3import { VideoEditorTask } from "@shared/models"
4
5function buildTaskFileFieldname (indice: number, fieldName = 'file') {
6 return `tasks[${indice}][options][${fieldName}]`
7}
8
9function getTaskFile (files: Express.Multer.File[], indice: number, fieldName = 'file') {
10 return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName))
11}
12
13async function approximateIntroOutroAdditionalSize (video: MVideoFullLight, tasks: VideoEditorTask[], fileFinder: (i: number) => string) {
14 let additionalDuration = 0
15
16 for (let i = 0; i < tasks.length; i++) {
17 const task = tasks[i]
18
19 if (task.name !== 'add-intro' && task.name !== 'add-outro') continue
20
21 const filePath = fileFinder(i)
22 additionalDuration += await getVideoStreamDuration(filePath)
23 }
24
25 return (video.getMaxQualityFile().size / video.duration) * additionalDuration
26}
27
28export {
29 approximateIntroOutroAdditionalSize,
30 buildTaskFileFieldname,
31 getTaskFile
32}
diff --git a/server/lib/video.ts b/server/lib/video.ts
index 2690f953d..ec4256c1a 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -81,7 +81,7 @@ async function setVideoTags (options: {
81 video.Tags = tagInstances 81 video.Tags = tagInstances
82} 82}
83 83
84async function addOptimizeOrMergeAudioJob (video: MVideoUUID, videoFile: MVideoFile, user: MUserId) { 84async function addOptimizeOrMergeAudioJob (video: MVideoUUID, videoFile: MVideoFile, user: MUserId, isNewVideo = true) {
85 let dataInput: VideoTranscodingPayload 85 let dataInput: VideoTranscodingPayload
86 86
87 if (videoFile.isAudio()) { 87 if (videoFile.isAudio()) {
@@ -90,13 +90,13 @@ async function addOptimizeOrMergeAudioJob (video: MVideoUUID, videoFile: MVideoF
90 resolution: DEFAULT_AUDIO_RESOLUTION, 90 resolution: DEFAULT_AUDIO_RESOLUTION,
91 videoUUID: video.uuid, 91 videoUUID: video.uuid,
92 createHLSIfNeeded: true, 92 createHLSIfNeeded: true,
93 isNewVideo: true 93 isNewVideo
94 } 94 }
95 } else { 95 } else {
96 dataInput = { 96 dataInput = {
97 type: 'optimize-to-webtorrent', 97 type: 'optimize-to-webtorrent',
98 videoUUID: video.uuid, 98 videoUUID: video.uuid,
99 isNewVideo: true 99 isNewVideo
100 } 100 }
101 } 101 }
102 102