diff options
Diffstat (limited to 'server/models/video')
-rw-r--r-- | server/models/video/video-interface.ts | 63 | ||||
-rw-r--r-- | server/models/video/video.ts | 105 |
2 files changed, 109 insertions, 59 deletions
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index fb31c6a8f..340426f45 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -7,60 +7,17 @@ import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | |||
7 | 7 | ||
8 | // Don't use barrel, import just what we need | 8 | // Don't use barrel, import just what we need |
9 | import { Video as FormattedVideo } from '../../../shared/models/videos/video.model' | 9 | import { Video as FormattedVideo } from '../../../shared/models/videos/video.model' |
10 | import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model' | ||
11 | import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' | ||
10 | import { ResultList } from '../../../shared/models/result-list.model' | 12 | import { ResultList } from '../../../shared/models/result-list.model' |
11 | 13 | ||
12 | export type FormattedRemoteVideoFile = { | ||
13 | infoHash: string | ||
14 | resolution: number | ||
15 | extname: string | ||
16 | size: number | ||
17 | } | ||
18 | |||
19 | export type FormattedAddRemoteVideo = { | ||
20 | uuid: string | ||
21 | name: string | ||
22 | category: number | ||
23 | licence: number | ||
24 | language: number | ||
25 | nsfw: boolean | ||
26 | description: string | ||
27 | author: string | ||
28 | duration: number | ||
29 | thumbnailData: string | ||
30 | tags: string[] | ||
31 | createdAt: Date | ||
32 | updatedAt: Date | ||
33 | views: number | ||
34 | likes: number | ||
35 | dislikes: number | ||
36 | files: FormattedRemoteVideoFile[] | ||
37 | } | ||
38 | |||
39 | export type FormattedUpdateRemoteVideo = { | ||
40 | uuid: string | ||
41 | name: string | ||
42 | category: number | ||
43 | licence: number | ||
44 | language: number | ||
45 | nsfw: boolean | ||
46 | description: string | ||
47 | author: string | ||
48 | duration: number | ||
49 | tags: string[] | ||
50 | createdAt: Date | ||
51 | updatedAt: Date | ||
52 | views: number | ||
53 | likes: number | ||
54 | dislikes: number | ||
55 | files: FormattedRemoteVideoFile[] | ||
56 | } | ||
57 | |||
58 | export namespace VideoMethods { | 14 | export namespace VideoMethods { |
59 | export type GetThumbnailName = (this: VideoInstance) => string | 15 | export type GetThumbnailName = (this: VideoInstance) => string |
60 | export type GetPreviewName = (this: VideoInstance) => string | 16 | export type GetPreviewName = (this: VideoInstance) => string |
61 | export type IsOwned = (this: VideoInstance) => boolean | 17 | export type IsOwned = (this: VideoInstance) => boolean |
62 | export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo | 18 | export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo |
63 | 19 | ||
20 | export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance | ||
64 | export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string | 21 | export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string |
65 | export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string | 22 | export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string |
66 | export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string | 23 | export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string |
@@ -69,10 +26,12 @@ export namespace VideoMethods { | |||
69 | export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string | 26 | export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string |
70 | export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> | 27 | export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> |
71 | 28 | ||
72 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormattedAddRemoteVideo> | 29 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<RemoteVideoCreateData> |
73 | export type ToUpdateRemoteJSON = (this: VideoInstance) => FormattedUpdateRemoteVideo | 30 | export type ToUpdateRemoteJSON = (this: VideoInstance) => RemoteVideoUpdateData |
74 | 31 | ||
75 | export type TranscodeVideofile = (this: VideoInstance, inputVideoFile: VideoFileInstance) => Promise<void> | 32 | export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void> |
33 | export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void> | ||
34 | export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number> | ||
76 | 35 | ||
77 | // Return thumbnail name | 36 | // Return thumbnail name |
78 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> | 37 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> |
@@ -147,6 +106,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
147 | createPreview: VideoMethods.CreatePreview | 106 | createPreview: VideoMethods.CreatePreview |
148 | createThumbnail: VideoMethods.CreateThumbnail | 107 | createThumbnail: VideoMethods.CreateThumbnail |
149 | createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | 108 | createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash |
109 | getOriginalFile: VideoMethods.GetOriginalFile | ||
150 | generateMagnetUri: VideoMethods.GenerateMagnetUri | 110 | generateMagnetUri: VideoMethods.GenerateMagnetUri |
151 | getPreviewName: VideoMethods.GetPreviewName | 111 | getPreviewName: VideoMethods.GetPreviewName |
152 | getThumbnailName: VideoMethods.GetThumbnailName | 112 | getThumbnailName: VideoMethods.GetThumbnailName |
@@ -161,9 +121,12 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
161 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 121 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
162 | toFormattedJSON: VideoMethods.ToFormattedJSON | 122 | toFormattedJSON: VideoMethods.ToFormattedJSON |
163 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 123 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
164 | transcodeVideofile: VideoMethods.TranscodeVideofile | 124 | optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile |
125 | transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | ||
126 | getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | ||
165 | 127 | ||
166 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> | 128 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> |
129 | addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> | ||
167 | setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string> | 130 | setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string> |
168 | } | 131 | } |
169 | 132 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e011c3b4d..28df91a7b 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -22,7 +22,8 @@ import { | |||
22 | unlinkPromise, | 22 | unlinkPromise, |
23 | renamePromise, | 23 | renamePromise, |
24 | writeFilePromise, | 24 | writeFilePromise, |
25 | createTorrentPromise | 25 | createTorrentPromise, |
26 | statPromise | ||
26 | } from '../../helpers' | 27 | } from '../../helpers' |
27 | import { | 28 | import { |
28 | CONFIG, | 29 | CONFIG, |
@@ -35,7 +36,8 @@ import { | |||
35 | VIDEO_FILE_RESOLUTIONS | 36 | VIDEO_FILE_RESOLUTIONS |
36 | } from '../../initializers' | 37 | } from '../../initializers' |
37 | import { removeVideoToFriends } from '../../lib' | 38 | import { removeVideoToFriends } from '../../lib' |
38 | import { VideoFileInstance } from './video-file-interface' | 39 | import { VideoResolution } from '../../../shared' |
40 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' | ||
39 | 41 | ||
40 | import { addMethodsToModel, getSort } from '../utils' | 42 | import { addMethodsToModel, getSort } from '../utils' |
41 | import { | 43 | import { |
@@ -46,6 +48,7 @@ import { | |||
46 | } from './video-interface' | 48 | } from './video-interface' |
47 | 49 | ||
48 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> | 50 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> |
51 | let getOriginalFile: VideoMethods.GetOriginalFile | ||
49 | let generateMagnetUri: VideoMethods.GenerateMagnetUri | 52 | let generateMagnetUri: VideoMethods.GenerateMagnetUri |
50 | let getVideoFilename: VideoMethods.GetVideoFilename | 53 | let getVideoFilename: VideoMethods.GetVideoFilename |
51 | let getThumbnailName: VideoMethods.GetThumbnailName | 54 | let getThumbnailName: VideoMethods.GetThumbnailName |
@@ -55,11 +58,13 @@ let isOwned: VideoMethods.IsOwned | |||
55 | let toFormattedJSON: VideoMethods.ToFormattedJSON | 58 | let toFormattedJSON: VideoMethods.ToFormattedJSON |
56 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 59 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
57 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 60 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
58 | let transcodeVideofile: VideoMethods.TranscodeVideofile | 61 | let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile |
62 | let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | ||
59 | let createPreview: VideoMethods.CreatePreview | 63 | let createPreview: VideoMethods.CreatePreview |
60 | let createThumbnail: VideoMethods.CreateThumbnail | 64 | let createThumbnail: VideoMethods.CreateThumbnail |
61 | let getVideoFilePath: VideoMethods.GetVideoFilePath | 65 | let getVideoFilePath: VideoMethods.GetVideoFilePath |
62 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | 66 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash |
67 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | ||
63 | 68 | ||
64 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 69 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
65 | let getDurationFromFile: VideoMethods.GetDurationFromFile | 70 | let getDurationFromFile: VideoMethods.GetDurationFromFile |
@@ -251,6 +256,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
251 | getTorrentFileName, | 256 | getTorrentFileName, |
252 | getVideoFilename, | 257 | getVideoFilename, |
253 | getVideoFilePath, | 258 | getVideoFilePath, |
259 | getOriginalFile, | ||
254 | isOwned, | 260 | isOwned, |
255 | removeFile, | 261 | removeFile, |
256 | removePreview, | 262 | removePreview, |
@@ -259,7 +265,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
259 | toAddRemoteJSON, | 265 | toAddRemoteJSON, |
260 | toFormattedJSON, | 266 | toFormattedJSON, |
261 | toUpdateRemoteJSON, | 267 | toUpdateRemoteJSON, |
262 | transcodeVideofile | 268 | optimizeOriginalVideofile, |
269 | transcodeOriginalVideofile, | ||
270 | getOriginalFileHeight | ||
263 | ] | 271 | ] |
264 | addMethodsToModel(Video, classMethods, instanceMethods) | 272 | addMethodsToModel(Video, classMethods, instanceMethods) |
265 | 273 | ||
@@ -327,9 +335,14 @@ function afterDestroy (video: VideoInstance, options: { transaction: Sequelize.T | |||
327 | return Promise.all(tasks) | 335 | return Promise.all(tasks) |
328 | } | 336 | } |
329 | 337 | ||
338 | getOriginalFile = function (this: VideoInstance) { | ||
339 | if (Array.isArray(this.VideoFiles) === false) return undefined | ||
340 | |||
341 | return this.VideoFiles.find(file => file.resolution === VideoResolution.ORIGINAL) | ||
342 | } | ||
343 | |||
330 | getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { | 344 | getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { |
331 | // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname | 345 | return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname |
332 | return this.uuid + videoFile.extname | ||
333 | } | 346 | } |
334 | 347 | ||
335 | getThumbnailName = function (this: VideoInstance) { | 348 | getThumbnailName = function (this: VideoInstance) { |
@@ -345,8 +358,7 @@ getPreviewName = function (this: VideoInstance) { | |||
345 | 358 | ||
346 | getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { | 359 | getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { |
347 | const extension = '.torrent' | 360 | const extension = '.torrent' |
348 | // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension | 361 | return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension |
349 | return this.uuid + extension | ||
350 | } | 362 | } |
351 | 363 | ||
352 | isOwned = function (this: VideoInstance) { | 364 | isOwned = function (this: VideoInstance) { |
@@ -552,9 +564,10 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
552 | return json | 564 | return json |
553 | } | 565 | } |
554 | 566 | ||
555 | transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) { | 567 | optimizeOriginalVideofile = function (this: VideoInstance) { |
556 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 568 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
557 | const newExtname = '.mp4' | 569 | const newExtname = '.mp4' |
570 | const inputVideoFile = this.getOriginalFile() | ||
558 | const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) | 571 | const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) |
559 | const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) | 572 | const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) |
560 | 573 | ||
@@ -575,6 +588,12 @@ transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileIns | |||
575 | return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) | 588 | return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) |
576 | }) | 589 | }) |
577 | .then(() => { | 590 | .then(() => { |
591 | return statPromise(this.getVideoFilePath(inputVideoFile)) | ||
592 | }) | ||
593 | .then(stats => { | ||
594 | return inputVideoFile.set('size', stats.size) | ||
595 | }) | ||
596 | .then(() => { | ||
578 | return this.createTorrentAndSetInfoHash(inputVideoFile) | 597 | return this.createTorrentAndSetInfoHash(inputVideoFile) |
579 | }) | 598 | }) |
580 | .then(() => { | 599 | .then(() => { |
@@ -594,6 +613,74 @@ transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileIns | |||
594 | }) | 613 | }) |
595 | } | 614 | } |
596 | 615 | ||
616 | transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoResolution) { | ||
617 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
618 | const extname = '.mp4' | ||
619 | |||
620 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | ||
621 | const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile())) | ||
622 | |||
623 | const newVideoFile = (Video['sequelize'].models.VideoFile as VideoFileModel).build({ | ||
624 | resolution, | ||
625 | extname, | ||
626 | size: 0, | ||
627 | videoId: this.id | ||
628 | }) | ||
629 | const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile)) | ||
630 | const resolutionWidthSizes = { | ||
631 | 1: '240x?', | ||
632 | 2: '360x?', | ||
633 | 3: '480x?', | ||
634 | 4: '720x?', | ||
635 | 5: '1080x?' | ||
636 | } | ||
637 | |||
638 | return new Promise<void>((res, rej) => { | ||
639 | ffmpeg(videoInputPath) | ||
640 | .output(videoOutputPath) | ||
641 | .videoCodec('libx264') | ||
642 | .size(resolutionWidthSizes[resolution]) | ||
643 | .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | ||
644 | .outputOption('-movflags faststart') | ||
645 | .on('error', rej) | ||
646 | .on('end', () => { | ||
647 | return statPromise(videoOutputPath) | ||
648 | .then(stats => { | ||
649 | newVideoFile.set('size', stats.size) | ||
650 | |||
651 | return undefined | ||
652 | }) | ||
653 | .then(() => { | ||
654 | return this.createTorrentAndSetInfoHash(newVideoFile) | ||
655 | }) | ||
656 | .then(() => { | ||
657 | return newVideoFile.save() | ||
658 | }) | ||
659 | .then(() => { | ||
660 | return this.VideoFiles.push(newVideoFile) | ||
661 | }) | ||
662 | .then(() => { | ||
663 | return res() | ||
664 | }) | ||
665 | .catch(rej) | ||
666 | }) | ||
667 | .run() | ||
668 | }) | ||
669 | } | ||
670 | |||
671 | getOriginalFileHeight = function (this: VideoInstance) { | ||
672 | const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) | ||
673 | |||
674 | return new Promise<number>((res, rej) => { | ||
675 | ffmpeg.ffprobe(originalFilePath, (err, metadata) => { | ||
676 | if (err) return rej(err) | ||
677 | |||
678 | const videoStream = metadata.streams.find(s => s.codec_type === 'video') | ||
679 | return res(videoStream.height) | ||
680 | }) | ||
681 | }) | ||
682 | } | ||
683 | |||
597 | removeThumbnail = function (this: VideoInstance) { | 684 | removeThumbnail = function (this: VideoInstance) { |
598 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | 685 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) |
599 | return unlinkPromise(thumbnailPath) | 686 | return unlinkPromise(thumbnailPath) |