diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/client-html.ts | 3 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-file.ts | 7 | ||||
-rw-r--r-- | server/lib/video-transcoding.ts | 130 |
3 files changed, 136 insertions, 4 deletions
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index a69e09c32..b1088c096 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -8,6 +8,7 @@ import { VideoModel } from '../models/video/video' | |||
8 | import * as validator from 'validator' | 8 | import * as validator from 'validator' |
9 | import { VideoPrivacy } from '../../shared/models/videos' | 9 | import { VideoPrivacy } from '../../shared/models/videos' |
10 | import { readFile } from 'fs-extra' | 10 | import { readFile } from 'fs-extra' |
11 | import { getActivityStreamDuration } from '../models/video/video-format-utils' | ||
11 | 12 | ||
12 | export class ClientHtml { | 13 | export class ClientHtml { |
13 | 14 | ||
@@ -150,7 +151,7 @@ export class ClientHtml { | |||
150 | description: videoDescriptionEscaped, | 151 | description: videoDescriptionEscaped, |
151 | thumbnailUrl: previewUrl, | 152 | thumbnailUrl: previewUrl, |
152 | uploadDate: video.createdAt.toISOString(), | 153 | uploadDate: video.createdAt.toISOString(), |
153 | duration: video.getActivityStreamDuration(), | 154 | duration: getActivityStreamDuration(video.duration), |
154 | contentUrl: videoUrl, | 155 | contentUrl: videoUrl, |
155 | embedUrl: embedUrl, | 156 | embedUrl: embedUrl, |
156 | interactionCount: video.views | 157 | interactionCount: video.views |
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts index c6308f7a6..2c9ca8e12 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-file.ts | |||
@@ -8,6 +8,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
8 | import { sequelizeTypescript } from '../../../initializers' | 8 | import { sequelizeTypescript } from '../../../initializers' |
9 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { importVideoFile, transcodeOriginalVideofile, optimizeOriginalVideofile } from '../../video-transcoding' | ||
11 | 12 | ||
12 | export type VideoFilePayload = { | 13 | export type VideoFilePayload = { |
13 | videoUUID: string | 14 | videoUUID: string |
@@ -32,7 +33,7 @@ async function processVideoFileImport (job: Bull.Job) { | |||
32 | return undefined | 33 | return undefined |
33 | } | 34 | } |
34 | 35 | ||
35 | await video.importVideoFile(payload.filePath) | 36 | await importVideoFile(video, payload.filePath) |
36 | 37 | ||
37 | await onVideoFileTranscoderOrImportSuccess(video) | 38 | await onVideoFileTranscoderOrImportSuccess(video) |
38 | return video | 39 | return video |
@@ -51,11 +52,11 @@ async function processVideoFile (job: Bull.Job) { | |||
51 | 52 | ||
52 | // Transcoding in other resolution | 53 | // Transcoding in other resolution |
53 | if (payload.resolution) { | 54 | if (payload.resolution) { |
54 | await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode || false) | 55 | await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) |
55 | 56 | ||
56 | await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, video) | 57 | await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, video) |
57 | } else { | 58 | } else { |
58 | await video.optimizeOriginalVideofile() | 59 | await optimizeOriginalVideofile(video) |
59 | 60 | ||
60 | await retryTransactionWrapper(onVideoFileOptimizerSuccess, video, payload.isNewVideo) | 61 | await retryTransactionWrapper(onVideoFileOptimizerSuccess, video, payload.isNewVideo) |
61 | } | 62 | } |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts new file mode 100644 index 000000000..bf3ff78c2 --- /dev/null +++ b/server/lib/video-transcoding.ts | |||
@@ -0,0 +1,130 @@ | |||
1 | import { CONFIG } from '../initializers' | ||
2 | import { join, extname } from 'path' | ||
3 | import { getVideoFileFPS, getVideoFileResolution, transcode } from '../helpers/ffmpeg-utils' | ||
4 | import { copy, remove, rename, stat } from 'fs-extra' | ||
5 | import { logger } from '../helpers/logger' | ||
6 | import { VideoResolution } from '../../shared/models/videos' | ||
7 | import { VideoFileModel } from '../models/video/video-file' | ||
8 | import { VideoModel } from '../models/video/video' | ||
9 | |||
10 | async function optimizeOriginalVideofile (video: VideoModel) { | ||
11 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
12 | const newExtname = '.mp4' | ||
13 | const inputVideoFile = video.getOriginalFile() | ||
14 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) | ||
15 | const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) | ||
16 | |||
17 | const transcodeOptions = { | ||
18 | inputPath: videoInputPath, | ||
19 | outputPath: videoTranscodedPath | ||
20 | } | ||
21 | |||
22 | // Could be very long! | ||
23 | await transcode(transcodeOptions) | ||
24 | |||
25 | try { | ||
26 | await remove(videoInputPath) | ||
27 | |||
28 | // Important to do this before getVideoFilename() to take in account the new file extension | ||
29 | inputVideoFile.set('extname', newExtname) | ||
30 | |||
31 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | ||
32 | await rename(videoTranscodedPath, videoOutputPath) | ||
33 | const stats = await stat(videoOutputPath) | ||
34 | const fps = await getVideoFileFPS(videoOutputPath) | ||
35 | |||
36 | inputVideoFile.set('size', stats.size) | ||
37 | inputVideoFile.set('fps', fps) | ||
38 | |||
39 | await video.createTorrentAndSetInfoHash(inputVideoFile) | ||
40 | await inputVideoFile.save() | ||
41 | } catch (err) { | ||
42 | // Auto destruction... | ||
43 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) | ||
44 | |||
45 | throw err | ||
46 | } | ||
47 | } | ||
48 | |||
49 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | ||
50 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
51 | const extname = '.mp4' | ||
52 | |||
53 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | ||
54 | const videoInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) | ||
55 | |||
56 | const newVideoFile = new VideoFileModel({ | ||
57 | resolution, | ||
58 | extname, | ||
59 | size: 0, | ||
60 | videoId: video.id | ||
61 | }) | ||
62 | const videoOutputPath = join(videosDirectory, video.getVideoFilename(newVideoFile)) | ||
63 | |||
64 | const transcodeOptions = { | ||
65 | inputPath: videoInputPath, | ||
66 | outputPath: videoOutputPath, | ||
67 | resolution, | ||
68 | isPortraitMode | ||
69 | } | ||
70 | |||
71 | await transcode(transcodeOptions) | ||
72 | |||
73 | const stats = await stat(videoOutputPath) | ||
74 | const fps = await getVideoFileFPS(videoOutputPath) | ||
75 | |||
76 | newVideoFile.set('size', stats.size) | ||
77 | newVideoFile.set('fps', fps) | ||
78 | |||
79 | await video.createTorrentAndSetInfoHash(newVideoFile) | ||
80 | |||
81 | await newVideoFile.save() | ||
82 | |||
83 | video.VideoFiles.push(newVideoFile) | ||
84 | } | ||
85 | |||
86 | async function importVideoFile (video: VideoModel, inputFilePath: string) { | ||
87 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | ||
88 | const { size } = await stat(inputFilePath) | ||
89 | const fps = await getVideoFileFPS(inputFilePath) | ||
90 | |||
91 | let updatedVideoFile = new VideoFileModel({ | ||
92 | resolution: videoFileResolution, | ||
93 | extname: extname(inputFilePath), | ||
94 | size, | ||
95 | fps, | ||
96 | videoId: video.id | ||
97 | }) | ||
98 | |||
99 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | ||
100 | |||
101 | if (currentVideoFile) { | ||
102 | // Remove old file and old torrent | ||
103 | await video.removeFile(currentVideoFile) | ||
104 | await video.removeTorrent(currentVideoFile) | ||
105 | // Remove the old video file from the array | ||
106 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | ||
107 | |||
108 | // Update the database | ||
109 | currentVideoFile.set('extname', updatedVideoFile.extname) | ||
110 | currentVideoFile.set('size', updatedVideoFile.size) | ||
111 | currentVideoFile.set('fps', updatedVideoFile.fps) | ||
112 | |||
113 | updatedVideoFile = currentVideoFile | ||
114 | } | ||
115 | |||
116 | const outputPath = video.getVideoFilePath(updatedVideoFile) | ||
117 | await copy(inputFilePath, outputPath) | ||
118 | |||
119 | await video.createTorrentAndSetInfoHash(updatedVideoFile) | ||
120 | |||
121 | await updatedVideoFile.save() | ||
122 | |||
123 | video.VideoFiles.push(updatedVideoFile) | ||
124 | } | ||
125 | |||
126 | export { | ||
127 | optimizeOriginalVideofile, | ||
128 | transcodeOriginalVideofile, | ||
129 | importVideoFile | ||
130 | } | ||