aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/transcoding
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/transcoding')
-rw-r--r--server/lib/transcoding/transcoding.ts386
1 files changed, 236 insertions, 150 deletions
diff --git a/server/lib/transcoding/transcoding.ts b/server/lib/transcoding/transcoding.ts
index 44e26754d..c7b61e9ba 100644
--- a/server/lib/transcoding/transcoding.ts
+++ b/server/lib/transcoding/transcoding.ts
@@ -1,3 +1,4 @@
1import { MutexInterface } from 'async-mutex'
1import { Job } from 'bullmq' 2import { Job } from 'bullmq'
2import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' 3import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
3import { basename, extname as extnameUtil, join } from 'path' 4import { basename, extname as extnameUtil, join } from 'path'
@@ -6,11 +7,13 @@ import { retryTransactionWrapper } from '@server/helpers/database-utils'
6import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 7import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
7import { sequelizeTypescript } from '@server/initializers/database' 8import { sequelizeTypescript } from '@server/initializers/database'
8import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 9import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
10import { pick } from '@shared/core-utils'
9import { VideoResolution, VideoStorage } from '../../../shared/models/videos' 11import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
10import { 12import {
11 buildFileMetadata, 13 buildFileMetadata,
12 canDoQuickTranscode, 14 canDoQuickTranscode,
13 computeResolutionsToTranscode, 15 computeResolutionsToTranscode,
16 ffprobePromise,
14 getVideoStreamDuration, 17 getVideoStreamDuration,
15 getVideoStreamFPS, 18 getVideoStreamFPS,
16 transcodeVOD, 19 transcodeVOD,
@@ -33,7 +36,7 @@ import { VideoTranscodingProfilesManager } from './default-transcoding-profiles'
33 */ 36 */
34 37
35// Optimize the original video file and replace it. The resolution is not changed. 38// Optimize the original video file and replace it. The resolution is not changed.
36function optimizeOriginalVideofile (options: { 39async function optimizeOriginalVideofile (options: {
37 video: MVideoFullLight 40 video: MVideoFullLight
38 inputVideoFile: MVideoFile 41 inputVideoFile: MVideoFile
39 job: Job 42 job: Job
@@ -43,49 +46,62 @@ function optimizeOriginalVideofile (options: {
43 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 46 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
44 const newExtname = '.mp4' 47 const newExtname = '.mp4'
45 48
46 return VideoPathManager.Instance.makeAvailableVideoFile(inputVideoFile.withVideoOrPlaylist(video), async videoInputPath => { 49 // Will be released by our transcodeVOD function once ffmpeg is ran
47 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) 50 const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
48 51
49 const transcodeType: TranscodeVODOptionsType = await canDoQuickTranscode(videoInputPath) 52 try {
50 ? 'quick-transcode' 53 await video.reload()
51 : 'video'
52 54
53 const resolution = buildOriginalFileResolution(inputVideoFile.resolution) 55 const fileWithVideoOrPlaylist = inputVideoFile.withVideoOrPlaylist(video)
54 56
55 const transcodeOptions: TranscodeVODOptions = { 57 const result = await VideoPathManager.Instance.makeAvailableVideoFile(fileWithVideoOrPlaylist, async videoInputPath => {
56 type: transcodeType, 58 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
57 59
58 inputPath: videoInputPath, 60 const transcodeType: TranscodeVODOptionsType = await canDoQuickTranscode(videoInputPath)
59 outputPath: videoTranscodedPath, 61 ? 'quick-transcode'
62 : 'video'
60 63
61 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 64 const resolution = buildOriginalFileResolution(inputVideoFile.resolution)
62 profile: CONFIG.TRANSCODING.PROFILE,
63 65
64 resolution, 66 const transcodeOptions: TranscodeVODOptions = {
67 type: transcodeType,
65 68
66 job 69 inputPath: videoInputPath,
67 } 70 outputPath: videoTranscodedPath,
68 71
69 // Could be very long! 72 inputFileMutexReleaser,
70 await transcodeVOD(transcodeOptions)
71 73
72 // Important to do this before getVideoFilename() to take in account the new filename 74 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
73 inputVideoFile.resolution = resolution 75 profile: CONFIG.TRANSCODING.PROFILE,
74 inputVideoFile.extname = newExtname
75 inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
76 inputVideoFile.storage = VideoStorage.FILE_SYSTEM
77 76
78 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile) 77 resolution,
79 78
80 const { videoFile } = await onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 79 job
81 await remove(videoInputPath) 80 }
82 81
83 return { transcodeType, videoFile } 82 // Could be very long!
84 }) 83 await transcodeVOD(transcodeOptions)
84
85 // Important to do this before getVideoFilename() to take in account the new filename
86 inputVideoFile.resolution = resolution
87 inputVideoFile.extname = newExtname
88 inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
89 inputVideoFile.storage = VideoStorage.FILE_SYSTEM
90
91 const { videoFile } = await onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, inputVideoFile)
92 await remove(videoInputPath)
93
94 return { transcodeType, videoFile }
95 })
96
97 return result
98 } finally {
99 inputFileMutexReleaser()
100 }
85} 101}
86 102
87// Transcode the original video file to a lower resolution compatible with WebTorrent 103// Transcode the original video file to a lower resolution compatible with WebTorrent
88function transcodeNewWebTorrentResolution (options: { 104async function transcodeNewWebTorrentResolution (options: {
89 video: MVideoFullLight 105 video: MVideoFullLight
90 resolution: VideoResolution 106 resolution: VideoResolution
91 job: Job 107 job: Job
@@ -95,53 +111,68 @@ function transcodeNewWebTorrentResolution (options: {
95 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 111 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
96 const newExtname = '.mp4' 112 const newExtname = '.mp4'
97 113
98 return VideoPathManager.Instance.makeAvailableVideoFile(video.getMaxQualityFile().withVideoOrPlaylist(video), async videoInputPath => { 114 const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
99 const newVideoFile = new VideoFileModel({
100 resolution,
101 extname: newExtname,
102 filename: generateWebTorrentVideoFilename(resolution, newExtname),
103 size: 0,
104 videoId: video.id
105 })
106 115
107 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newVideoFile) 116 try {
108 const videoTranscodedPath = join(transcodeDirectory, newVideoFile.filename) 117 await video.reload()
109 118
110 const transcodeOptions = resolution === VideoResolution.H_NOVIDEO 119 const file = video.getMaxQualityFile().withVideoOrPlaylist(video)
111 ? {
112 type: 'only-audio' as 'only-audio',
113 120
114 inputPath: videoInputPath, 121 const result = await VideoPathManager.Instance.makeAvailableVideoFile(file, async videoInputPath => {
115 outputPath: videoTranscodedPath, 122 const newVideoFile = new VideoFileModel({
123 resolution,
124 extname: newExtname,
125 filename: generateWebTorrentVideoFilename(resolution, newExtname),
126 size: 0,
127 videoId: video.id
128 })
116 129
117 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 130 const videoTranscodedPath = join(transcodeDirectory, newVideoFile.filename)
118 profile: CONFIG.TRANSCODING.PROFILE,
119 131
120 resolution, 132 const transcodeOptions = resolution === VideoResolution.H_NOVIDEO
133 ? {
134 type: 'only-audio' as 'only-audio',
121 135
122 job 136 inputPath: videoInputPath,
123 } 137 outputPath: videoTranscodedPath,
124 : {
125 type: 'video' as 'video',
126 inputPath: videoInputPath,
127 outputPath: videoTranscodedPath,
128 138
129 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 139 inputFileMutexReleaser,
130 profile: CONFIG.TRANSCODING.PROFILE,
131 140
132 resolution, 141 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
142 profile: CONFIG.TRANSCODING.PROFILE,
133 143
134 job 144 resolution,
135 }
136 145
137 await transcodeVOD(transcodeOptions) 146 job
147 }
148 : {
149 type: 'video' as 'video',
150 inputPath: videoInputPath,
151 outputPath: videoTranscodedPath,
138 152
139 return onWebTorrentVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) 153 inputFileMutexReleaser,
140 }) 154
155 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
156 profile: CONFIG.TRANSCODING.PROFILE,
157
158 resolution,
159
160 job
161 }
162
163 await transcodeVOD(transcodeOptions)
164
165 return onWebTorrentVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, newVideoFile)
166 })
167
168 return result
169 } finally {
170 inputFileMutexReleaser()
171 }
141} 172}
142 173
143// Merge an image with an audio file to create a video 174// Merge an image with an audio file to create a video
144function mergeAudioVideofile (options: { 175async function mergeAudioVideofile (options: {
145 video: MVideoFullLight 176 video: MVideoFullLight
146 resolution: VideoResolution 177 resolution: VideoResolution
147 job: Job 178 job: Job
@@ -151,54 +182,67 @@ function mergeAudioVideofile (options: {
151 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 182 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
152 const newExtname = '.mp4' 183 const newExtname = '.mp4'
153 184
154 const inputVideoFile = video.getMinQualityFile() 185 const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
155 186
156 return VideoPathManager.Instance.makeAvailableVideoFile(inputVideoFile.withVideoOrPlaylist(video), async audioInputPath => { 187 try {
157 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) 188 await video.reload()
158 189
159 // If the user updates the video preview during transcoding 190 const inputVideoFile = video.getMinQualityFile()
160 const previewPath = video.getPreview().getPath()
161 const tmpPreviewPath = join(CONFIG.STORAGE.TMP_DIR, basename(previewPath))
162 await copyFile(previewPath, tmpPreviewPath)
163 191
164 const transcodeOptions = { 192 const fileWithVideoOrPlaylist = inputVideoFile.withVideoOrPlaylist(video)
165 type: 'merge-audio' as 'merge-audio',
166 193
167 inputPath: tmpPreviewPath, 194 const result = await VideoPathManager.Instance.makeAvailableVideoFile(fileWithVideoOrPlaylist, async audioInputPath => {
168 outputPath: videoTranscodedPath, 195 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
169 196
170 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), 197 // If the user updates the video preview during transcoding
171 profile: CONFIG.TRANSCODING.PROFILE, 198 const previewPath = video.getPreview().getPath()
199 const tmpPreviewPath = join(CONFIG.STORAGE.TMP_DIR, basename(previewPath))
200 await copyFile(previewPath, tmpPreviewPath)
172 201
173 audioPath: audioInputPath, 202 const transcodeOptions = {
174 resolution, 203 type: 'merge-audio' as 'merge-audio',
175 204
176 job 205 inputPath: tmpPreviewPath,
177 } 206 outputPath: videoTranscodedPath,
178 207
179 try { 208 inputFileMutexReleaser,
180 await transcodeVOD(transcodeOptions)
181 209
182 await remove(audioInputPath) 210 availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
183 await remove(tmpPreviewPath) 211 profile: CONFIG.TRANSCODING.PROFILE,
184 } catch (err) {
185 await remove(tmpPreviewPath)
186 throw err
187 }
188 212
189 // Important to do this before getVideoFilename() to take in account the new file extension 213 audioPath: audioInputPath,
190 inputVideoFile.extname = newExtname 214 resolution,
191 inputVideoFile.resolution = resolution
192 inputVideoFile.filename = generateWebTorrentVideoFilename(inputVideoFile.resolution, newExtname)
193 215
194 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile) 216 job
195 // ffmpeg generated a new video file, so update the video duration 217 }
196 // See https://trac.ffmpeg.org/ticket/5456
197 video.duration = await getVideoStreamDuration(videoTranscodedPath)
198 await video.save()
199 218
200 return onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 219 try {
201 }) 220 await transcodeVOD(transcodeOptions)
221
222 await remove(audioInputPath)
223 await remove(tmpPreviewPath)
224 } catch (err) {
225 await remove(tmpPreviewPath)
226 throw err
227 }
228
229 // Important to do this before getVideoFilename() to take in account the new file extension
230 inputVideoFile.extname = newExtname
231 inputVideoFile.resolution = resolution
232 inputVideoFile.filename = generateWebTorrentVideoFilename(inputVideoFile.resolution, newExtname)
233
234 // ffmpeg generated a new video file, so update the video duration
235 // See https://trac.ffmpeg.org/ticket/5456
236 video.duration = await getVideoStreamDuration(videoTranscodedPath)
237 await video.save()
238
239 return onWebTorrentVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, inputVideoFile)
240 })
241
242 return result
243 } finally {
244 inputFileMutexReleaser()
245 }
202} 246}
203 247
204// Concat TS segments from a live video to a fragmented mp4 HLS playlist 248// Concat TS segments from a live video to a fragmented mp4 HLS playlist
@@ -207,13 +251,13 @@ async function generateHlsPlaylistResolutionFromTS (options: {
207 concatenatedTsFilePath: string 251 concatenatedTsFilePath: string
208 resolution: VideoResolution 252 resolution: VideoResolution
209 isAAC: boolean 253 isAAC: boolean
254 inputFileMutexReleaser: MutexInterface.Releaser
210}) { 255}) {
211 return generateHlsPlaylistCommon({ 256 return generateHlsPlaylistCommon({
212 video: options.video,
213 resolution: options.resolution,
214 inputPath: options.concatenatedTsFilePath,
215 type: 'hls-from-ts' as 'hls-from-ts', 257 type: 'hls-from-ts' as 'hls-from-ts',
216 isAAC: options.isAAC 258 inputPath: options.concatenatedTsFilePath,
259
260 ...pick(options, [ 'video', 'resolution', 'inputFileMutexReleaser', 'isAAC' ])
217 }) 261 })
218} 262}
219 263
@@ -223,15 +267,14 @@ function generateHlsPlaylistResolution (options: {
223 videoInputPath: string 267 videoInputPath: string
224 resolution: VideoResolution 268 resolution: VideoResolution
225 copyCodecs: boolean 269 copyCodecs: boolean
270 inputFileMutexReleaser: MutexInterface.Releaser
226 job?: Job 271 job?: Job
227}) { 272}) {
228 return generateHlsPlaylistCommon({ 273 return generateHlsPlaylistCommon({
229 video: options.video,
230 resolution: options.resolution,
231 copyCodecs: options.copyCodecs,
232 inputPath: options.videoInputPath,
233 type: 'hls' as 'hls', 274 type: 'hls' as 'hls',
234 job: options.job 275 inputPath: options.videoInputPath,
276
277 ...pick(options, [ 'video', 'resolution', 'copyCodecs', 'inputFileMutexReleaser', 'job' ])
235 }) 278 })
236} 279}
237 280
@@ -251,27 +294,39 @@ async function onWebTorrentVideoFileTranscoding (
251 video: MVideoFullLight, 294 video: MVideoFullLight,
252 videoFile: MVideoFile, 295 videoFile: MVideoFile,
253 transcodingPath: string, 296 transcodingPath: string,
254 outputPath: string 297 newVideoFile: MVideoFile
255) { 298) {
256 const stats = await stat(transcodingPath) 299 const mutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
257 const fps = await getVideoStreamFPS(transcodingPath)
258 const metadata = await buildFileMetadata(transcodingPath)
259 300
260 await move(transcodingPath, outputPath, { overwrite: true }) 301 try {
302 await video.reload()
261 303
262 videoFile.size = stats.size 304 const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newVideoFile)
263 videoFile.fps = fps
264 videoFile.metadata = metadata
265 305
266 await createTorrentAndSetInfoHash(video, videoFile) 306 const stats = await stat(transcodingPath)
267 307
268 const oldFile = await VideoFileModel.loadWebTorrentFile({ videoId: video.id, fps: videoFile.fps, resolution: videoFile.resolution }) 308 const probe = await ffprobePromise(transcodingPath)
269 if (oldFile) await video.removeWebTorrentFile(oldFile) 309 const fps = await getVideoStreamFPS(transcodingPath, probe)
310 const metadata = await buildFileMetadata(transcodingPath, probe)
270 311
271 await VideoFileModel.customUpsert(videoFile, 'video', undefined) 312 await move(transcodingPath, outputPath, { overwrite: true })
272 video.VideoFiles = await video.$get('VideoFiles')
273 313
274 return { video, videoFile } 314 videoFile.size = stats.size
315 videoFile.fps = fps
316 videoFile.metadata = metadata
317
318 await createTorrentAndSetInfoHash(video, videoFile)
319
320 const oldFile = await VideoFileModel.loadWebTorrentFile({ videoId: video.id, fps: videoFile.fps, resolution: videoFile.resolution })
321 if (oldFile) await video.removeWebTorrentFile(oldFile)
322
323 await VideoFileModel.customUpsert(videoFile, 'video', undefined)
324 video.VideoFiles = await video.$get('VideoFiles')
325
326 return { video, videoFile }
327 } finally {
328 mutexReleaser()
329 }
275} 330}
276 331
277async function generateHlsPlaylistCommon (options: { 332async function generateHlsPlaylistCommon (options: {
@@ -279,12 +334,15 @@ async function generateHlsPlaylistCommon (options: {
279 video: MVideo 334 video: MVideo
280 inputPath: string 335 inputPath: string
281 resolution: VideoResolution 336 resolution: VideoResolution
337
338 inputFileMutexReleaser: MutexInterface.Releaser
339
282 copyCodecs?: boolean 340 copyCodecs?: boolean
283 isAAC?: boolean 341 isAAC?: boolean
284 342
285 job?: Job 343 job?: Job
286}) { 344}) {
287 const { type, video, inputPath, resolution, copyCodecs, isAAC, job } = options 345 const { type, video, inputPath, resolution, copyCodecs, isAAC, job, inputFileMutexReleaser } = options
288 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR 346 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
289 347
290 const videoTranscodedBasePath = join(transcodeDirectory, type) 348 const videoTranscodedBasePath = join(transcodeDirectory, type)
@@ -308,6 +366,8 @@ async function generateHlsPlaylistCommon (options: {
308 366
309 isAAC, 367 isAAC,
310 368
369 inputFileMutexReleaser,
370
311 hlsPlaylist: { 371 hlsPlaylist: {
312 videoFilename 372 videoFilename
313 }, 373 },
@@ -333,47 +393,73 @@ async function generateHlsPlaylistCommon (options: {
333 videoStreamingPlaylistId: playlist.id 393 videoStreamingPlaylistId: playlist.id
334 }) 394 })
335 395
336 const videoFilePath = VideoPathManager.Instance.getFSVideoFileOutputPath(playlist, newVideoFile) 396 const mutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
337 await ensureDir(VideoPathManager.Instance.getFSHLSOutputPath(video))
338 397
339 // Move playlist file 398 try {
340 const resolutionPlaylistPath = VideoPathManager.Instance.getFSHLSOutputPath(video, resolutionPlaylistFilename) 399 // VOD transcoding is a long task, refresh video attributes
341 await move(resolutionPlaylistFileTranscodePath, resolutionPlaylistPath, { overwrite: true }) 400 await video.reload()
342 // Move video file
343 await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
344 401
345 // Update video duration if it was not set (in case of a live for example) 402 const videoFilePath = VideoPathManager.Instance.getFSVideoFileOutputPath(playlist, newVideoFile)
346 if (!video.duration) { 403 await ensureDir(VideoPathManager.Instance.getFSHLSOutputPath(video))
347 video.duration = await getVideoStreamDuration(videoFilePath)
348 await video.save()
349 }
350 404
351 const stats = await stat(videoFilePath) 405 // Move playlist file
406 const resolutionPlaylistPath = VideoPathManager.Instance.getFSHLSOutputPath(video, resolutionPlaylistFilename)
407 await move(resolutionPlaylistFileTranscodePath, resolutionPlaylistPath, { overwrite: true })
408 // Move video file
409 await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
352 410
353 newVideoFile.size = stats.size 411 // Update video duration if it was not set (in case of a live for example)
354 newVideoFile.fps = await getVideoStreamFPS(videoFilePath) 412 if (!video.duration) {
355 newVideoFile.metadata = await buildFileMetadata(videoFilePath) 413 video.duration = await getVideoStreamDuration(videoFilePath)
414 await video.save()
415 }
356 416
357 await createTorrentAndSetInfoHash(playlist, newVideoFile) 417 const stats = await stat(videoFilePath)
358 418
359 const oldFile = await VideoFileModel.loadHLSFile({ playlistId: playlist.id, fps: newVideoFile.fps, resolution: newVideoFile.resolution }) 419 newVideoFile.size = stats.size
360 if (oldFile) { 420 newVideoFile.fps = await getVideoStreamFPS(videoFilePath)
361 await video.removeStreamingPlaylistVideoFile(playlist, oldFile) 421 newVideoFile.metadata = await buildFileMetadata(videoFilePath)
362 await oldFile.destroy()
363 }
364 422
365 const savedVideoFile = await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) 423 await createTorrentAndSetInfoHash(playlist, newVideoFile)
366 424
367 await updatePlaylistAfterFileChange(video, playlist) 425 const oldFile = await VideoFileModel.loadHLSFile({
426 playlistId: playlist.id,
427 fps: newVideoFile.fps,
428 resolution: newVideoFile.resolution
429 })
430
431 if (oldFile) {
432 await video.removeStreamingPlaylistVideoFile(playlist, oldFile)
433 await oldFile.destroy()
434 }
435
436 const savedVideoFile = await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined)
437
438 await updatePlaylistAfterFileChange(video, playlist)
368 439
369 return { resolutionPlaylistPath, videoFile: savedVideoFile } 440 return { resolutionPlaylistPath, videoFile: savedVideoFile }
441 } finally {
442 mutexReleaser()
443 }
370} 444}
371 445
372function buildOriginalFileResolution (inputResolution: number) { 446function buildOriginalFileResolution (inputResolution: number) {
373 if (CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION === true) return toEven(inputResolution) 447 if (CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION === true) {
448 return toEven(inputResolution)
449 }
374 450
375 const resolutions = computeResolutionsToTranscode({ input: inputResolution, type: 'vod', includeInput: false, strictLower: false }) 451 const resolutions = computeResolutionsToTranscode({
376 if (resolutions.length === 0) return toEven(inputResolution) 452 input: inputResolution,
453 type: 'vod',
454 includeInput: false,
455 strictLower: false,
456 // We don't really care about the audio resolution in this context
457 hasAudio: true
458 })
459
460 if (resolutions.length === 0) {
461 return toEven(inputResolution)
462 }
377 463
378 return Math.max(...resolutions) 464 return Math.max(...resolutions)
379} 465}