aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/files-cache/videos-preview-cache.ts2
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts4
-rw-r--r--server/lib/job-queue/handlers/video-import.ts1
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts56
-rw-r--r--server/lib/thumbnail.ts8
-rw-r--r--server/lib/video-transcoding.ts88
6 files changed, 114 insertions, 45 deletions
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts
index 14be7f24a..a68619d07 100644
--- a/server/lib/files-cache/videos-preview-cache.ts
+++ b/server/lib/files-cache/videos-preview-cache.ts
@@ -21,7 +21,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
21 const video = await VideoModel.loadByUUIDWithFile(videoUUID) 21 const video = await VideoModel.loadByUUIDWithFile(videoUUID)
22 if (!video) return undefined 22 if (!video) return undefined
23 23
24 if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } 24 if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() }
25 25
26 return this.loadRemoteFile(videoUUID) 26 return this.loadRemoteFile(videoUUID)
27 } 27 }
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 921d9a083..8cacb0ef3 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -1,7 +1,7 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
3import { VideoModel } from '../../../models/video/video' 3import { VideoModel } from '../../../models/video/video'
4import { publishVideoIfNeeded } from './video-transcoding' 4import { publishNewResolutionIfNeeded } from './video-transcoding'
5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
6import { copy, stat } from 'fs-extra' 6import { copy, stat } from 'fs-extra'
7import { VideoFileModel } from '../../../models/video/video-file' 7import { VideoFileModel } from '../../../models/video/video-file'
@@ -25,7 +25,7 @@ async function processVideoFileImport (job: Bull.Job) {
25 25
26 await updateVideoFile(video, payload.filePath) 26 await updateVideoFile(video, payload.filePath)
27 27
28 await publishVideoIfNeeded(video) 28 await publishNewResolutionIfNeeded(video)
29 return video 29 return video
30} 30}
31 31
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 1650916a6..50e159245 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -209,6 +209,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { 209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
210 // Put uuid because we don't have id auto incremented for now 210 // Put uuid because we don't have id auto incremented for now
211 const dataInput = { 211 const dataInput = {
212 type: 'optimize' as 'optimize',
212 videoUUID: videoImportUpdated.Video.uuid, 213 videoUUID: videoImportUpdated.Video.uuid,
213 isNewVideo: true 214 isNewVideo: true
214 } 215 }
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 48cac517e..e9b84ecd6 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -8,18 +8,39 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { sequelizeTypescript } from '../../../initializers' 8import { sequelizeTypescript } from '../../../initializers'
9import * as Bluebird from 'bluebird' 9import * as Bluebird from 'bluebird'
10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' 10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' 11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 12import { Notifier } from '../../notifier'
13import { CONFIG } from '../../../initializers/config' 13import { CONFIG } from '../../../initializers/config'
14 14
15export type VideoTranscodingPayload = { 15interface BaseTranscodingPayload {
16 videoUUID: string 16 videoUUID: string
17 resolution?: VideoResolution
18 isNewVideo?: boolean 17 isNewVideo?: boolean
18}
19
20interface HLSTranscodingPayload extends BaseTranscodingPayload {
21 type: 'hls'
22 isPortraitMode?: boolean
23 resolution: VideoResolution
24}
25
26interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
27 type: 'new-resolution'
19 isPortraitMode?: boolean 28 isPortraitMode?: boolean
20 generateHlsPlaylist?: boolean 29 resolution: VideoResolution
30}
31
32interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
33 type: 'merge-audio'
34 resolution: VideoResolution
35}
36
37interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
38 type: 'optimize'
21} 39}
22 40
41export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload
42 | OptimizeTranscodingPayload | MergeAudioTranscodingPayload
43
23async function processVideoTranscoding (job: Bull.Job) { 44async function processVideoTranscoding (job: Bull.Job) {
24 const payload = job.data as VideoTranscodingPayload 45 const payload = job.data as VideoTranscodingPayload
25 logger.info('Processing video file in job %d.', job.id) 46 logger.info('Processing video file in job %d.', job.id)
@@ -31,14 +52,18 @@ async function processVideoTranscoding (job: Bull.Job) {
31 return undefined 52 return undefined
32 } 53 }
33 54
34 if (payload.generateHlsPlaylist) { 55 if (payload.type === 'hls') {
35 await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) 56 await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false)
36 57
37 await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) 58 await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video)
38 } else if (payload.resolution) { // Transcoding in other resolution 59 } else if (payload.type === 'new-resolution') {
39 await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) 60 await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false)
40 61
41 await retryTransactionWrapper(publishVideoIfNeeded, video, payload) 62 await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
63 } else if (payload.type === 'merge-audio') {
64 await mergeAudioVideofile(video, payload.resolution)
65
66 await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
42 } else { 67 } else {
43 await optimizeVideofile(video) 68 await optimizeVideofile(video)
44 69
@@ -62,7 +87,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
62 }) 87 })
63} 88}
64 89
65async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) { 90async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
66 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 91 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
67 // Maybe the video changed in database, refresh it 92 // Maybe the video changed in database, refresh it
68 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) 93 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -94,7 +119,7 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi
94 await createHlsJobIfEnabled(payload) 119 await createHlsJobIfEnabled(payload)
95} 120}
96 121
97async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) { 122async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) {
98 if (videoArg === undefined) return undefined 123 if (videoArg === undefined) return undefined
99 124
100 // Outside the transaction (IO on disk) 125 // Outside the transaction (IO on disk)
@@ -120,6 +145,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
120 145
121 for (const resolution of resolutionsEnabled) { 146 for (const resolution of resolutionsEnabled) {
122 const dataInput = { 147 const dataInput = {
148 type: 'new-resolution' as 'new-resolution',
123 videoUUID: videoDatabase.uuid, 149 videoUUID: videoDatabase.uuid,
124 resolution 150 resolution
125 } 151 }
@@ -149,27 +175,27 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
149 if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) 175 if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
150 if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) 176 if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
151 177
152 await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) 178 const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })
179 await createHlsJobIfEnabled(hlsPayload)
153} 180}
154 181
155// --------------------------------------------------------------------------- 182// ---------------------------------------------------------------------------
156 183
157export { 184export {
158 processVideoTranscoding, 185 processVideoTranscoding,
159 publishVideoIfNeeded 186 publishNewResolutionIfNeeded
160} 187}
161 188
162// --------------------------------------------------------------------------- 189// ---------------------------------------------------------------------------
163 190
164function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) { 191function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: number, isPortraitMode?: boolean }) {
165 // Generate HLS playlist? 192 // Generate HLS playlist?
166 if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { 193 if (payload && CONFIG.TRANSCODING.HLS.ENABLED) {
167 const hlsTranscodingPayload = { 194 const hlsTranscodingPayload = {
195 type: 'hls' as 'hls',
168 videoUUID: payload.videoUUID, 196 videoUUID: payload.videoUUID,
169 resolution: payload.resolution, 197 resolution: payload.resolution,
170 isPortraitMode: payload.isPortraitMode, 198 isPortraitMode: payload.isPortraitMode
171
172 generateHlsPlaylist: true
173 } 199 }
174 200
175 return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) 201 return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload })
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index 950b14c3b..18bdcded4 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,7 +1,7 @@
1import { VideoFileModel } from '../models/video/video-file' 1import { VideoFileModel } from '../models/video/video-file'
2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
3import { CONFIG } from '../initializers/config' 3import { CONFIG } from '../initializers/config'
4import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' 4import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants'
5import { VideoModel } from '../models/video/video' 5import { VideoModel } from '../models/video/video'
6import { ThumbnailModel } from '../models/video/thumbnail' 6import { ThumbnailModel } from '../models/video/thumbnail'
7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
@@ -45,8 +45,10 @@ function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel,
45function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { 45function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
46 const input = video.getVideoFilePath(videoFile) 46 const input = video.getVideoFilePath(videoFile)
47 47
48 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) 48 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
49 const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width }) 49 const thumbnailCreator = videoFile.isAudio()
50 ? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
51 : () => generateImageFromVideoFile(input, basePath, filename, { height, width })
50 52
51 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) 53 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
52} 54}
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index d6b6b251a..8d786e0ef 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -1,6 +1,6 @@
1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' 1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
2import { join } from 'path' 2import { join } from 'path'
3import { getVideoFileFPS, transcode, canDoQuickTranscode } from '../helpers/ffmpeg-utils' 3import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils'
4import { ensureDir, move, remove, stat } from 'fs-extra' 4import { ensureDir, move, remove, stat } from 'fs-extra'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
@@ -23,13 +23,15 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
23 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) 23 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
24 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) 24 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
25 25
26 const doQuickTranscode = await(canDoQuickTranscode(videoInputPath)) 26 const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath)
27 ? 'quick-transcode'
28 : 'video'
27 29
28 const transcodeOptions = { 30 const transcodeOptions: TranscodeOptions = {
31 type: transcodeType as any, // FIXME: typing issue
29 inputPath: videoInputPath, 32 inputPath: videoInputPath,
30 outputPath: videoTranscodedPath, 33 outputPath: videoTranscodedPath,
31 resolution: inputVideoFile.resolution, 34 resolution: inputVideoFile.resolution
32 doQuickTranscode
33 } 35 }
34 36
35 // Could be very long! 37 // Could be very long!
@@ -39,19 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
39 await remove(videoInputPath) 41 await remove(videoInputPath)
40 42
41 // Important to do this before getVideoFilename() to take in account the new file extension 43 // Important to do this before getVideoFilename() to take in account the new file extension
42 inputVideoFile.set('extname', newExtname) 44 inputVideoFile.extname = newExtname
43
44 const stats = await stat(videoTranscodedPath)
45 const fps = await getVideoFileFPS(videoTranscodedPath)
46 45
47 const videoOutputPath = video.getVideoFilePath(inputVideoFile) 46 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
48 await move(videoTranscodedPath, videoOutputPath)
49 47
50 inputVideoFile.set('size', stats.size) 48 await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
51 inputVideoFile.set('fps', fps)
52
53 await video.createTorrentAndSetInfoHash(inputVideoFile)
54 await inputVideoFile.save()
55 } catch (err) { 49 } catch (err) {
56 // Auto destruction... 50 // Auto destruction...
57 video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) 51 video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err }))
@@ -81,6 +75,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
81 const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) 75 const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))
82 76
83 const transcodeOptions = { 77 const transcodeOptions = {
78 type: 'video' as 'video',
84 inputPath: videoInputPath, 79 inputPath: videoInputPath,
85 outputPath: videoTranscodedPath, 80 outputPath: videoTranscodedPath,
86 resolution, 81 resolution,
@@ -89,19 +84,37 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
89 84
90 await transcode(transcodeOptions) 85 await transcode(transcodeOptions)
91 86
92 const stats = await stat(videoTranscodedPath) 87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
93 const fps = await getVideoFileFPS(videoTranscodedPath) 88}
89
90async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
93 const newExtname = '.mp4'
94
95 const inputVideoFile = video.getOriginalFile()
94 96
95 await move(videoTranscodedPath, videoOutputPath) 97 const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
98 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
96 99
97 newVideoFile.set('size', stats.size) 100 const transcodeOptions = {
98 newVideoFile.set('fps', fps) 101 type: 'merge-audio' as 'merge-audio',
102 inputPath: video.getPreview().getPath(),
103 outputPath: videoTranscodedPath,
104 audioPath: audioInputPath,
105 resolution
106 }
99 107
100 await video.createTorrentAndSetInfoHash(newVideoFile) 108 await transcode(transcodeOptions)
101 109
102 await newVideoFile.save() 110 await remove(audioInputPath)
103 111
104 video.VideoFiles.push(newVideoFile) 112 // Important to do this before getVideoFilename() to take in account the new file extension
113 inputVideoFile.extname = newExtname
114
115 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
116
117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
105} 118}
106 119
107async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { 120async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
@@ -112,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
112 const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) 125 const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
113 126
114 const transcodeOptions = { 127 const transcodeOptions = {
128 type: 'hls' as 'hls',
115 inputPath: videoInputPath, 129 inputPath: videoInputPath,
116 outputPath, 130 outputPath,
117 resolution, 131 resolution,
@@ -140,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
140 }) 154 })
141} 155}
142 156
157// ---------------------------------------------------------------------------
158
143export { 159export {
144 generateHlsPlaylist, 160 generateHlsPlaylist,
145 optimizeVideofile, 161 optimizeVideofile,
146 transcodeOriginalVideofile 162 transcodeOriginalVideofile,
163 mergeAudioVideofile
164}
165
166// ---------------------------------------------------------------------------
167
168async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
169 const stats = await stat(transcodingPath)
170 const fps = await getVideoFileFPS(transcodingPath)
171
172 await move(transcodingPath, outputPath)
173
174 videoFile.set('size', stats.size)
175 videoFile.set('fps', fps)
176
177 await video.createTorrentAndSetInfoHash(videoFile)
178
179 const updatedVideoFile = await videoFile.save()
180
181 // Add it if this is a new created file
182 if (video.VideoFiles.some(f => f.id === videoFile.id) === false) {
183 video.VideoFiles.push(updatedVideoFile)
184 }
185
186 return video
147} 187}