diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-08-25 11:36:23 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-08-25 11:36:23 +0200 |
commit | 93e1258c7cbc0d1235ca6d2a1f7c1875985328b8 (patch) | |
tree | b0a1f77af7ab54dc5f58f569fcd1e9d84b04c533 /server/models/video | |
parent | 69f224587e99d56008e1fa129d0641840a486620 (diff) | |
download | PeerTube-93e1258c7cbc0d1235ca6d2a1f7c1875985328b8.tar.gz PeerTube-93e1258c7cbc0d1235ca6d2a1f7c1875985328b8.tar.zst PeerTube-93e1258c7cbc0d1235ca6d2a1f7c1875985328b8.zip |
Move video file metadata in their own table
Will be used for user video quotas and multiple video resolutions
Diffstat (limited to 'server/models/video')
-rw-r--r-- | server/models/video/index.ts | 1 | ||||
-rw-r--r-- | server/models/video/video-file-interface.ts | 24 | ||||
-rw-r--r-- | server/models/video/video-file.ts | 89 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 67 | ||||
-rw-r--r-- | server/models/video/video.ts | 434 |
5 files changed, 377 insertions, 238 deletions
diff --git a/server/models/video/index.ts b/server/models/video/index.ts index 84b801c72..08b360376 100644 --- a/server/models/video/index.ts +++ b/server/models/video/index.ts | |||
@@ -3,4 +3,5 @@ export * from './tag-interface' | |||
3 | export * from './video-abuse-interface' | 3 | export * from './video-abuse-interface' |
4 | export * from './video-blacklist-interface' | 4 | export * from './video-blacklist-interface' |
5 | export * from './video-tag-interface' | 5 | export * from './video-tag-interface' |
6 | export * from './video-file-interface' | ||
6 | export * from './video-interface' | 7 | export * from './video-interface' |
diff --git a/server/models/video/video-file-interface.ts b/server/models/video/video-file-interface.ts new file mode 100644 index 000000000..c9fb8b8ae --- /dev/null +++ b/server/models/video/video-file-interface.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | export namespace VideoFileMethods { | ||
4 | } | ||
5 | |||
6 | export interface VideoFileClass { | ||
7 | } | ||
8 | |||
9 | export interface VideoFileAttributes { | ||
10 | resolution: number | ||
11 | size: number | ||
12 | infoHash?: string | ||
13 | extname: string | ||
14 | |||
15 | videoId?: number | ||
16 | } | ||
17 | |||
18 | export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance<VideoFileAttributes> { | ||
19 | id: number | ||
20 | createdAt: Date | ||
21 | updatedAt: Date | ||
22 | } | ||
23 | |||
24 | export interface VideoFileModel extends VideoFileClass, Sequelize.Model<VideoFileInstance, VideoFileAttributes> {} | ||
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts new file mode 100644 index 000000000..09a30d7e0 --- /dev/null +++ b/server/models/video/video-file.ts | |||
@@ -0,0 +1,89 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { values } from 'lodash' | ||
3 | |||
4 | import { CONSTRAINTS_FIELDS } from '../../initializers' | ||
5 | import { | ||
6 | isVideoFileResolutionValid, | ||
7 | isVideoFileSizeValid, | ||
8 | isVideoFileInfoHashValid | ||
9 | } from '../../helpers' | ||
10 | |||
11 | import { addMethodsToModel } from '../utils' | ||
12 | import { | ||
13 | VideoFileInstance, | ||
14 | VideoFileAttributes | ||
15 | } from './video-file-interface' | ||
16 | |||
17 | let VideoFile: Sequelize.Model<VideoFileInstance, VideoFileAttributes> | ||
18 | |||
19 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | ||
20 | VideoFile = sequelize.define<VideoFileInstance, VideoFileAttributes>('VideoFile', | ||
21 | { | ||
22 | resolution: { | ||
23 | type: DataTypes.INTEGER, | ||
24 | allowNull: false, | ||
25 | validate: { | ||
26 | resolutionValid: value => { | ||
27 | const res = isVideoFileResolutionValid(value) | ||
28 | if (res === false) throw new Error('Video file resolution is not valid.') | ||
29 | } | ||
30 | } | ||
31 | }, | ||
32 | size: { | ||
33 | type: DataTypes.INTEGER, | ||
34 | allowNull: false, | ||
35 | validate: { | ||
36 | sizeValid: value => { | ||
37 | const res = isVideoFileSizeValid(value) | ||
38 | if (res === false) throw new Error('Video file size is not valid.') | ||
39 | } | ||
40 | } | ||
41 | }, | ||
42 | extname: { | ||
43 | type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)), | ||
44 | allowNull: false | ||
45 | }, | ||
46 | infoHash: { | ||
47 | type: DataTypes.STRING, | ||
48 | allowNull: false, | ||
49 | validate: { | ||
50 | infoHashValid: value => { | ||
51 | const res = isVideoFileInfoHashValid(value) | ||
52 | if (res === false) throw new Error('Video file info hash is not valid.') | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | }, | ||
57 | { | ||
58 | indexes: [ | ||
59 | { | ||
60 | fields: [ 'videoId' ] | ||
61 | }, | ||
62 | { | ||
63 | fields: [ 'infoHash' ] | ||
64 | } | ||
65 | ] | ||
66 | } | ||
67 | ) | ||
68 | |||
69 | const classMethods = [ | ||
70 | associate | ||
71 | ] | ||
72 | addMethodsToModel(VideoFile, classMethods) | ||
73 | |||
74 | return VideoFile | ||
75 | } | ||
76 | |||
77 | // ------------------------------ STATICS ------------------------------ | ||
78 | |||
79 | function associate (models) { | ||
80 | VideoFile.belongsTo(models.Video, { | ||
81 | foreignKey: { | ||
82 | name: 'videoId', | ||
83 | allowNull: false | ||
84 | }, | ||
85 | onDelete: 'CASCADE' | ||
86 | }) | ||
87 | } | ||
88 | |||
89 | // ------------------------------ METHODS ------------------------------ | ||
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 2fabcd906..976c70b5e 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -3,11 +3,19 @@ import * as Promise from 'bluebird' | |||
3 | 3 | ||
4 | import { AuthorInstance } from './author-interface' | 4 | import { AuthorInstance } from './author-interface' |
5 | import { TagAttributes, TagInstance } from './tag-interface' | 5 | import { TagAttributes, TagInstance } from './tag-interface' |
6 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | ||
6 | 7 | ||
7 | // Don't use barrel, import just what we need | 8 | // Don't use barrel, import just what we need |
8 | import { Video as FormatedVideo } from '../../../shared/models/videos/video.model' | 9 | import { Video as FormatedVideo } from '../../../shared/models/videos/video.model' |
9 | import { ResultList } from '../../../shared/models/result-list.model' | 10 | import { ResultList } from '../../../shared/models/result-list.model' |
10 | 11 | ||
12 | export type FormatedRemoteVideoFile = { | ||
13 | infoHash: string | ||
14 | resolution: number | ||
15 | extname: string | ||
16 | size: number | ||
17 | } | ||
18 | |||
11 | export type FormatedAddRemoteVideo = { | 19 | export type FormatedAddRemoteVideo = { |
12 | uuid: string | 20 | uuid: string |
13 | name: string | 21 | name: string |
@@ -16,17 +24,16 @@ export type FormatedAddRemoteVideo = { | |||
16 | language: number | 24 | language: number |
17 | nsfw: boolean | 25 | nsfw: boolean |
18 | description: string | 26 | description: string |
19 | infoHash: string | ||
20 | author: string | 27 | author: string |
21 | duration: number | 28 | duration: number |
22 | thumbnailData: string | 29 | thumbnailData: string |
23 | tags: string[] | 30 | tags: string[] |
24 | createdAt: Date | 31 | createdAt: Date |
25 | updatedAt: Date | 32 | updatedAt: Date |
26 | extname: string | ||
27 | views: number | 33 | views: number |
28 | likes: number | 34 | likes: number |
29 | dislikes: number | 35 | dislikes: number |
36 | files: FormatedRemoteVideoFile[] | ||
30 | } | 37 | } |
31 | 38 | ||
32 | export type FormatedUpdateRemoteVideo = { | 39 | export type FormatedUpdateRemoteVideo = { |
@@ -37,31 +44,35 @@ export type FormatedUpdateRemoteVideo = { | |||
37 | language: number | 44 | language: number |
38 | nsfw: boolean | 45 | nsfw: boolean |
39 | description: string | 46 | description: string |
40 | infoHash: string | ||
41 | author: string | 47 | author: string |
42 | duration: number | 48 | duration: number |
43 | tags: string[] | 49 | tags: string[] |
44 | createdAt: Date | 50 | createdAt: Date |
45 | updatedAt: Date | 51 | updatedAt: Date |
46 | extname: string | ||
47 | views: number | 52 | views: number |
48 | likes: number | 53 | likes: number |
49 | dislikes: number | 54 | dislikes: number |
55 | files: FormatedRemoteVideoFile[] | ||
50 | } | 56 | } |
51 | 57 | ||
52 | export namespace VideoMethods { | 58 | export namespace VideoMethods { |
53 | export type GenerateMagnetUri = (this: VideoInstance) => string | ||
54 | export type GetVideoFilename = (this: VideoInstance) => string | ||
55 | export type GetThumbnailName = (this: VideoInstance) => string | 59 | export type GetThumbnailName = (this: VideoInstance) => string |
56 | export type GetPreviewName = (this: VideoInstance) => string | 60 | export type GetPreviewName = (this: VideoInstance) => string |
57 | export type GetTorrentName = (this: VideoInstance) => string | ||
58 | export type IsOwned = (this: VideoInstance) => boolean | 61 | export type IsOwned = (this: VideoInstance) => boolean |
59 | export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo | 62 | export type ToFormatedJSON = (this: VideoInstance) => FormatedVideo |
60 | 63 | ||
64 | export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string | ||
65 | export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string | ||
66 | export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string | ||
67 | export type CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string> | ||
68 | export type CreateThumbnail = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string> | ||
69 | export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string | ||
70 | export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> | ||
71 | |||
61 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo> | 72 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<FormatedAddRemoteVideo> |
62 | export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo | 73 | export type ToUpdateRemoteJSON = (this: VideoInstance) => FormatedUpdateRemoteVideo |
63 | 74 | ||
64 | export type TranscodeVideofile = (this: VideoInstance) => Promise<void> | 75 | export type TranscodeVideofile = (this: VideoInstance, inputVideoFile: VideoFileInstance) => Promise<void> |
65 | 76 | ||
66 | // Return thumbnail name | 77 | // Return thumbnail name |
67 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> | 78 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> |
@@ -86,31 +97,25 @@ export namespace VideoMethods { | |||
86 | export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance> | 97 | export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance> |
87 | export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance> | 98 | export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance> |
88 | export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance> | 99 | export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance> |
100 | |||
101 | export type RemoveThumbnail = (this: VideoInstance) => Promise<void> | ||
102 | export type RemovePreview = (this: VideoInstance) => Promise<void> | ||
103 | export type RemoveFile = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> | ||
104 | export type RemoveTorrent = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> | ||
89 | } | 105 | } |
90 | 106 | ||
91 | export interface VideoClass { | 107 | export interface VideoClass { |
92 | generateMagnetUri: VideoMethods.GenerateMagnetUri | ||
93 | getVideoFilename: VideoMethods.GetVideoFilename | ||
94 | getThumbnailName: VideoMethods.GetThumbnailName | ||
95 | getPreviewName: VideoMethods.GetPreviewName | ||
96 | getTorrentName: VideoMethods.GetTorrentName | ||
97 | isOwned: VideoMethods.IsOwned | ||
98 | toFormatedJSON: VideoMethods.ToFormatedJSON | ||
99 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | ||
100 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | ||
101 | transcodeVideofile: VideoMethods.TranscodeVideofile | ||
102 | |||
103 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 108 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
104 | getDurationFromFile: VideoMethods.GetDurationFromFile | 109 | getDurationFromFile: VideoMethods.GetDurationFromFile |
105 | list: VideoMethods.List | 110 | list: VideoMethods.List |
106 | listForApi: VideoMethods.ListForApi | 111 | listForApi: VideoMethods.ListForApi |
107 | loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | ||
108 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 112 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags |
109 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 113 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor |
110 | load: VideoMethods.Load | 114 | load: VideoMethods.Load |
111 | loadByUUID: VideoMethods.LoadByUUID | ||
112 | loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor | 115 | loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor |
113 | loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags | 116 | loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags |
117 | loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | ||
118 | loadByUUID: VideoMethods.LoadByUUID | ||
114 | loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags | 119 | loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags |
115 | searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags | 120 | searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags |
116 | } | 121 | } |
@@ -118,13 +123,11 @@ export interface VideoClass { | |||
118 | export interface VideoAttributes { | 123 | export interface VideoAttributes { |
119 | uuid?: string | 124 | uuid?: string |
120 | name: string | 125 | name: string |
121 | extname: string | ||
122 | category: number | 126 | category: number |
123 | licence: number | 127 | licence: number |
124 | language: number | 128 | language: number |
125 | nsfw: boolean | 129 | nsfw: boolean |
126 | description: string | 130 | description: string |
127 | infoHash?: string | ||
128 | duration: number | 131 | duration: number |
129 | views?: number | 132 | views?: number |
130 | likes?: number | 133 | likes?: number |
@@ -133,6 +136,7 @@ export interface VideoAttributes { | |||
133 | 136 | ||
134 | Author?: AuthorInstance | 137 | Author?: AuthorInstance |
135 | Tags?: TagInstance[] | 138 | Tags?: TagInstance[] |
139 | VideoFiles?: VideoFileInstance[] | ||
136 | } | 140 | } |
137 | 141 | ||
138 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { | 142 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { |
@@ -140,18 +144,27 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
140 | createdAt: Date | 144 | createdAt: Date |
141 | updatedAt: Date | 145 | updatedAt: Date |
142 | 146 | ||
147 | createPreview: VideoMethods.CreatePreview | ||
148 | createThumbnail: VideoMethods.CreateThumbnail | ||
149 | createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | ||
143 | generateMagnetUri: VideoMethods.GenerateMagnetUri | 150 | generateMagnetUri: VideoMethods.GenerateMagnetUri |
144 | getVideoFilename: VideoMethods.GetVideoFilename | ||
145 | getThumbnailName: VideoMethods.GetThumbnailName | ||
146 | getPreviewName: VideoMethods.GetPreviewName | 151 | getPreviewName: VideoMethods.GetPreviewName |
147 | getTorrentName: VideoMethods.GetTorrentName | 152 | getThumbnailName: VideoMethods.GetThumbnailName |
153 | getTorrentFileName: VideoMethods.GetTorrentFileName | ||
154 | getVideoFilename: VideoMethods.GetVideoFilename | ||
155 | getVideoFilePath: VideoMethods.GetVideoFilePath | ||
148 | isOwned: VideoMethods.IsOwned | 156 | isOwned: VideoMethods.IsOwned |
149 | toFormatedJSON: VideoMethods.ToFormatedJSON | 157 | removeFile: VideoMethods.RemoveFile |
158 | removePreview: VideoMethods.RemovePreview | ||
159 | removeThumbnail: VideoMethods.RemoveThumbnail | ||
160 | removeTorrent: VideoMethods.RemoveTorrent | ||
150 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 161 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
162 | toFormatedJSON: VideoMethods.ToFormatedJSON | ||
151 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 163 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
152 | transcodeVideofile: VideoMethods.TranscodeVideofile | 164 | transcodeVideofile: VideoMethods.TranscodeVideofile |
153 | 165 | ||
154 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> | 166 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> |
167 | setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string> | ||
155 | } | 168 | } |
156 | 169 | ||
157 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} | 170 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b7eb24c4a..1e4bdf51c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -2,13 +2,12 @@ import * as safeBuffer from 'safe-buffer' | |||
2 | const Buffer = safeBuffer.Buffer | 2 | const Buffer = safeBuffer.Buffer |
3 | import * as ffmpeg from 'fluent-ffmpeg' | 3 | import * as ffmpeg from 'fluent-ffmpeg' |
4 | import * as magnetUtil from 'magnet-uri' | 4 | import * as magnetUtil from 'magnet-uri' |
5 | import { map, values } from 'lodash' | 5 | import { map } from 'lodash' |
6 | import * as parseTorrent from 'parse-torrent' | 6 | import * as parseTorrent from 'parse-torrent' |
7 | import { join } from 'path' | 7 | import { join } from 'path' |
8 | import * as Sequelize from 'sequelize' | 8 | import * as Sequelize from 'sequelize' |
9 | import * as Promise from 'bluebird' | 9 | import * as Promise from 'bluebird' |
10 | 10 | ||
11 | import { database as db } from '../../initializers/database' | ||
12 | import { TagInstance } from './tag-interface' | 11 | import { TagInstance } from './tag-interface' |
13 | import { | 12 | import { |
14 | logger, | 13 | logger, |
@@ -18,7 +17,6 @@ import { | |||
18 | isVideoLanguageValid, | 17 | isVideoLanguageValid, |
19 | isVideoNSFWValid, | 18 | isVideoNSFWValid, |
20 | isVideoDescriptionValid, | 19 | isVideoDescriptionValid, |
21 | isVideoInfoHashValid, | ||
22 | isVideoDurationValid, | 20 | isVideoDurationValid, |
23 | readFileBufferPromise, | 21 | readFileBufferPromise, |
24 | unlinkPromise, | 22 | unlinkPromise, |
@@ -27,16 +25,17 @@ import { | |||
27 | createTorrentPromise | 25 | createTorrentPromise |
28 | } from '../../helpers' | 26 | } from '../../helpers' |
29 | import { | 27 | import { |
30 | CONSTRAINTS_FIELDS, | ||
31 | CONFIG, | 28 | CONFIG, |
32 | REMOTE_SCHEME, | 29 | REMOTE_SCHEME, |
33 | STATIC_PATHS, | 30 | STATIC_PATHS, |
34 | VIDEO_CATEGORIES, | 31 | VIDEO_CATEGORIES, |
35 | VIDEO_LICENCES, | 32 | VIDEO_LICENCES, |
36 | VIDEO_LANGUAGES, | 33 | VIDEO_LANGUAGES, |
37 | THUMBNAILS_SIZE | 34 | THUMBNAILS_SIZE, |
35 | VIDEO_FILE_RESOLUTIONS | ||
38 | } from '../../initializers' | 36 | } from '../../initializers' |
39 | import { JobScheduler, removeVideoToFriends } from '../../lib' | 37 | import { removeVideoToFriends } from '../../lib' |
38 | import { VideoFileInstance } from './video-file-interface' | ||
40 | 39 | ||
41 | import { addMethodsToModel, getSort } from '../utils' | 40 | import { addMethodsToModel, getSort } from '../utils' |
42 | import { | 41 | import { |
@@ -51,12 +50,16 @@ let generateMagnetUri: VideoMethods.GenerateMagnetUri | |||
51 | let getVideoFilename: VideoMethods.GetVideoFilename | 50 | let getVideoFilename: VideoMethods.GetVideoFilename |
52 | let getThumbnailName: VideoMethods.GetThumbnailName | 51 | let getThumbnailName: VideoMethods.GetThumbnailName |
53 | let getPreviewName: VideoMethods.GetPreviewName | 52 | let getPreviewName: VideoMethods.GetPreviewName |
54 | let getTorrentName: VideoMethods.GetTorrentName | 53 | let getTorrentFileName: VideoMethods.GetTorrentFileName |
55 | let isOwned: VideoMethods.IsOwned | 54 | let isOwned: VideoMethods.IsOwned |
56 | let toFormatedJSON: VideoMethods.ToFormatedJSON | 55 | let toFormatedJSON: VideoMethods.ToFormatedJSON |
57 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 56 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON |
58 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | 57 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON |
59 | let transcodeVideofile: VideoMethods.TranscodeVideofile | 58 | let transcodeVideofile: VideoMethods.TranscodeVideofile |
59 | let createPreview: VideoMethods.CreatePreview | ||
60 | let createThumbnail: VideoMethods.CreateThumbnail | ||
61 | let getVideoFilePath: VideoMethods.GetVideoFilePath | ||
62 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | ||
60 | 63 | ||
61 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 64 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
62 | let getDurationFromFile: VideoMethods.GetDurationFromFile | 65 | let getDurationFromFile: VideoMethods.GetDurationFromFile |
@@ -71,6 +74,10 @@ let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor | |||
71 | let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags | 74 | let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags |
72 | let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags | 75 | let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags |
73 | let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags | 76 | let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags |
77 | let removeThumbnail: VideoMethods.RemoveThumbnail | ||
78 | let removePreview: VideoMethods.RemovePreview | ||
79 | let removeFile: VideoMethods.RemoveFile | ||
80 | let removeTorrent: VideoMethods.RemoveTorrent | ||
74 | 81 | ||
75 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 82 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
76 | Video = sequelize.define<VideoInstance, VideoAttributes>('Video', | 83 | Video = sequelize.define<VideoInstance, VideoAttributes>('Video', |
@@ -93,10 +100,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
93 | } | 100 | } |
94 | } | 101 | } |
95 | }, | 102 | }, |
96 | extname: { | ||
97 | type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)), | ||
98 | allowNull: false | ||
99 | }, | ||
100 | category: { | 103 | category: { |
101 | type: DataTypes.INTEGER, | 104 | type: DataTypes.INTEGER, |
102 | allowNull: false, | 105 | allowNull: false, |
@@ -148,16 +151,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
148 | } | 151 | } |
149 | } | 152 | } |
150 | }, | 153 | }, |
151 | infoHash: { | ||
152 | type: DataTypes.STRING, | ||
153 | allowNull: false, | ||
154 | validate: { | ||
155 | infoHashValid: value => { | ||
156 | const res = isVideoInfoHashValid(value) | ||
157 | if (res === false) throw new Error('Video info hash is not valid.') | ||
158 | } | ||
159 | } | ||
160 | }, | ||
161 | duration: { | 154 | duration: { |
162 | type: DataTypes.INTEGER, | 155 | type: DataTypes.INTEGER, |
163 | allowNull: false, | 156 | allowNull: false, |
@@ -216,9 +209,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
216 | fields: [ 'duration' ] | 209 | fields: [ 'duration' ] |
217 | }, | 210 | }, |
218 | { | 211 | { |
219 | fields: [ 'infoHash' ] | ||
220 | }, | ||
221 | { | ||
222 | fields: [ 'views' ] | 212 | fields: [ 'views' ] |
223 | }, | 213 | }, |
224 | { | 214 | { |
@@ -229,8 +219,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
229 | } | 219 | } |
230 | ], | 220 | ], |
231 | hooks: { | 221 | hooks: { |
232 | beforeValidate, | ||
233 | beforeCreate, | ||
234 | afterDestroy | 222 | afterDestroy |
235 | } | 223 | } |
236 | } | 224 | } |
@@ -246,23 +234,30 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
246 | listOwnedAndPopulateAuthorAndTags, | 234 | listOwnedAndPopulateAuthorAndTags, |
247 | listOwnedByAuthor, | 235 | listOwnedByAuthor, |
248 | load, | 236 | load, |
249 | loadByUUID, | ||
250 | loadByHostAndUUID, | ||
251 | loadAndPopulateAuthor, | 237 | loadAndPopulateAuthor, |
252 | loadAndPopulateAuthorAndPodAndTags, | 238 | loadAndPopulateAuthorAndPodAndTags, |
239 | loadByHostAndUUID, | ||
240 | loadByUUID, | ||
253 | loadByUUIDAndPopulateAuthorAndPodAndTags, | 241 | loadByUUIDAndPopulateAuthorAndPodAndTags, |
254 | searchAndPopulateAuthorAndPodAndTags, | 242 | searchAndPopulateAuthorAndPodAndTags |
255 | removeFromBlacklist | ||
256 | ] | 243 | ] |
257 | const instanceMethods = [ | 244 | const instanceMethods = [ |
245 | createPreview, | ||
246 | createThumbnail, | ||
247 | createTorrentAndSetInfoHash, | ||
258 | generateMagnetUri, | 248 | generateMagnetUri, |
259 | getVideoFilename, | ||
260 | getThumbnailName, | ||
261 | getPreviewName, | 249 | getPreviewName, |
262 | getTorrentName, | 250 | getThumbnailName, |
251 | getTorrentFileName, | ||
252 | getVideoFilename, | ||
253 | getVideoFilePath, | ||
263 | isOwned, | 254 | isOwned, |
264 | toFormatedJSON, | 255 | removeFile, |
256 | removePreview, | ||
257 | removeThumbnail, | ||
258 | removeTorrent, | ||
265 | toAddRemoteJSON, | 259 | toAddRemoteJSON, |
260 | toFormatedJSON, | ||
266 | toUpdateRemoteJSON, | 261 | toUpdateRemoteJSON, |
267 | transcodeVideofile | 262 | transcodeVideofile |
268 | ] | 263 | ] |
@@ -271,65 +266,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
271 | return Video | 266 | return Video |
272 | } | 267 | } |
273 | 268 | ||
274 | function beforeValidate (video: VideoInstance) { | ||
275 | // Put a fake infoHash if it does not exists yet | ||
276 | if (video.isOwned() && !video.infoHash) { | ||
277 | // 40 hexa length | ||
278 | video.infoHash = '0123456789abcdef0123456789abcdef01234567' | ||
279 | } | ||
280 | } | ||
281 | |||
282 | function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) { | ||
283 | if (video.isOwned()) { | ||
284 | const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | ||
285 | const tasks = [] | ||
286 | |||
287 | tasks.push( | ||
288 | createTorrentFromVideo(video, videoPath), | ||
289 | createThumbnail(video, videoPath), | ||
290 | createPreview(video, videoPath) | ||
291 | ) | ||
292 | |||
293 | if (CONFIG.TRANSCODING.ENABLED === true) { | ||
294 | // Put uuid because we don't have id auto incremented for now | ||
295 | const dataInput = { | ||
296 | videoUUID: video.uuid | ||
297 | } | ||
298 | |||
299 | tasks.push( | ||
300 | JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput) | ||
301 | ) | ||
302 | } | ||
303 | |||
304 | return Promise.all(tasks) | ||
305 | } | ||
306 | |||
307 | return Promise.resolve() | ||
308 | } | ||
309 | |||
310 | function afterDestroy (video: VideoInstance) { | ||
311 | const tasks = [] | ||
312 | |||
313 | tasks.push( | ||
314 | removeThumbnail(video) | ||
315 | ) | ||
316 | |||
317 | if (video.isOwned()) { | ||
318 | const removeVideoToFriendsParams = { | ||
319 | uuid: video.uuid | ||
320 | } | ||
321 | |||
322 | tasks.push( | ||
323 | removeFile(video), | ||
324 | removeTorrent(video), | ||
325 | removePreview(video), | ||
326 | removeVideoToFriends(removeVideoToFriendsParams) | ||
327 | ) | ||
328 | } | ||
329 | |||
330 | return Promise.all(tasks) | ||
331 | } | ||
332 | |||
333 | // ------------------------------ METHODS ------------------------------ | 269 | // ------------------------------ METHODS ------------------------------ |
334 | 270 | ||
335 | function associate (models) { | 271 | function associate (models) { |
@@ -354,37 +290,46 @@ function associate (models) { | |||
354 | }, | 290 | }, |
355 | onDelete: 'cascade' | 291 | onDelete: 'cascade' |
356 | }) | 292 | }) |
293 | |||
294 | Video.hasMany(models.VideoFile, { | ||
295 | foreignKey: { | ||
296 | name: 'videoId', | ||
297 | allowNull: false | ||
298 | }, | ||
299 | onDelete: 'cascade' | ||
300 | }) | ||
357 | } | 301 | } |
358 | 302 | ||
359 | generateMagnetUri = function (this: VideoInstance) { | 303 | function afterDestroy (video: VideoInstance) { |
360 | let baseUrlHttp | 304 | const tasks = [] |
361 | let baseUrlWs | ||
362 | 305 | ||
363 | if (this.isOwned()) { | 306 | tasks.push( |
364 | baseUrlHttp = CONFIG.WEBSERVER.URL | 307 | video.removeThumbnail() |
365 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 308 | ) |
366 | } else { | ||
367 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host | ||
368 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host | ||
369 | } | ||
370 | 309 | ||
371 | const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentName() | 310 | if (video.isOwned()) { |
372 | const announce = [ baseUrlWs + '/tracker/socket' ] | 311 | const removeVideoToFriendsParams = { |
373 | const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename() ] | 312 | uuid: video.uuid |
313 | } | ||
374 | 314 | ||
375 | const magnetHash = { | 315 | tasks.push( |
376 | xs, | 316 | video.removePreview(), |
377 | announce, | 317 | removeVideoToFriends(removeVideoToFriendsParams) |
378 | urlList, | 318 | ) |
379 | infoHash: this.infoHash, | 319 | |
380 | name: this.name | 320 | // TODO: check files is populated |
321 | video.VideoFiles.forEach(file => { | ||
322 | video.removeFile(file), | ||
323 | video.removeTorrent(file) | ||
324 | }) | ||
381 | } | 325 | } |
382 | 326 | ||
383 | return magnetUtil.encode(magnetHash) | 327 | return Promise.all(tasks) |
384 | } | 328 | } |
385 | 329 | ||
386 | getVideoFilename = function (this: VideoInstance) { | 330 | getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { |
387 | return this.uuid + this.extname | 331 | // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + videoFile.extname |
332 | return this.uuid + videoFile.extname | ||
388 | } | 333 | } |
389 | 334 | ||
390 | getThumbnailName = function (this: VideoInstance) { | 335 | getThumbnailName = function (this: VideoInstance) { |
@@ -398,8 +343,9 @@ getPreviewName = function (this: VideoInstance) { | |||
398 | return this.uuid + extension | 343 | return this.uuid + extension |
399 | } | 344 | } |
400 | 345 | ||
401 | getTorrentName = function (this: VideoInstance) { | 346 | getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { |
402 | const extension = '.torrent' | 347 | const extension = '.torrent' |
348 | // return this.uuid + '-' + VIDEO_FILE_RESOLUTIONS[videoFile.resolution] + extension | ||
403 | return this.uuid + extension | 349 | return this.uuid + extension |
404 | } | 350 | } |
405 | 351 | ||
@@ -407,6 +353,67 @@ isOwned = function (this: VideoInstance) { | |||
407 | return this.remote === false | 353 | return this.remote === false |
408 | } | 354 | } |
409 | 355 | ||
356 | createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
357 | return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.PREVIEWS_DIR, this.getPreviewName(), null) | ||
358 | } | ||
359 | |||
360 | createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
361 | return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), THUMBNAILS_SIZE) | ||
362 | } | ||
363 | |||
364 | getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
365 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | ||
366 | } | ||
367 | |||
368 | createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
369 | const options = { | ||
370 | announceList: [ | ||
371 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] | ||
372 | ], | ||
373 | urlList: [ | ||
374 | CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | ||
375 | ] | ||
376 | } | ||
377 | |||
378 | return createTorrentPromise(this.getVideoFilePath(videoFile), options) | ||
379 | .then(torrent => { | ||
380 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | ||
381 | return writeFilePromise(filePath, torrent).then(() => torrent) | ||
382 | }) | ||
383 | .then(torrent => { | ||
384 | const parsedTorrent = parseTorrent(torrent) | ||
385 | |||
386 | videoFile.infoHash = parsedTorrent.infoHash | ||
387 | }) | ||
388 | } | ||
389 | |||
390 | generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
391 | let baseUrlHttp | ||
392 | let baseUrlWs | ||
393 | |||
394 | if (this.isOwned()) { | ||
395 | baseUrlHttp = CONFIG.WEBSERVER.URL | ||
396 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | ||
397 | } else { | ||
398 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host | ||
399 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host | ||
400 | } | ||
401 | |||
402 | const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | ||
403 | const announce = [ baseUrlWs + '/tracker/socket' ] | ||
404 | const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ] | ||
405 | |||
406 | const magnetHash = { | ||
407 | xs, | ||
408 | announce, | ||
409 | urlList, | ||
410 | infoHash: videoFile.infoHash, | ||
411 | name: this.name | ||
412 | } | ||
413 | |||
414 | return magnetUtil.encode(magnetHash) | ||
415 | } | ||
416 | |||
410 | toFormatedJSON = function (this: VideoInstance) { | 417 | toFormatedJSON = function (this: VideoInstance) { |
411 | let podHost | 418 | let podHost |
412 | 419 | ||
@@ -443,7 +450,6 @@ toFormatedJSON = function (this: VideoInstance) { | |||
443 | description: this.description, | 450 | description: this.description, |
444 | podHost, | 451 | podHost, |
445 | isLocal: this.isOwned(), | 452 | isLocal: this.isOwned(), |
446 | magnetUri: this.generateMagnetUri(), | ||
447 | author: this.Author.name, | 453 | author: this.Author.name, |
448 | duration: this.duration, | 454 | duration: this.duration, |
449 | views: this.views, | 455 | views: this.views, |
@@ -453,9 +459,24 @@ toFormatedJSON = function (this: VideoInstance) { | |||
453 | thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), | 459 | thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), |
454 | previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()), | 460 | previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()), |
455 | createdAt: this.createdAt, | 461 | createdAt: this.createdAt, |
456 | updatedAt: this.updatedAt | 462 | updatedAt: this.updatedAt, |
463 | files: [] | ||
457 | } | 464 | } |
458 | 465 | ||
466 | this.VideoFiles.forEach(videoFile => { | ||
467 | let resolutionLabel = VIDEO_FILE_RESOLUTIONS[videoFile.resolution] | ||
468 | if (!resolutionLabel) resolutionLabel = 'Unknown' | ||
469 | |||
470 | const videoFileJson = { | ||
471 | resolution: videoFile.resolution, | ||
472 | resolutionLabel, | ||
473 | magnetUri: this.generateMagnetUri(videoFile), | ||
474 | size: videoFile.size | ||
475 | } | ||
476 | |||
477 | json.files.push(videoFileJson) | ||
478 | }) | ||
479 | |||
459 | return json | 480 | return json |
460 | } | 481 | } |
461 | 482 | ||
@@ -472,19 +493,27 @@ toAddRemoteJSON = function (this: VideoInstance) { | |||
472 | language: this.language, | 493 | language: this.language, |
473 | nsfw: this.nsfw, | 494 | nsfw: this.nsfw, |
474 | description: this.description, | 495 | description: this.description, |
475 | infoHash: this.infoHash, | ||
476 | author: this.Author.name, | 496 | author: this.Author.name, |
477 | duration: this.duration, | 497 | duration: this.duration, |
478 | thumbnailData: thumbnailData.toString('binary'), | 498 | thumbnailData: thumbnailData.toString('binary'), |
479 | tags: map<TagInstance, string>(this.Tags, 'name'), | 499 | tags: map<TagInstance, string>(this.Tags, 'name'), |
480 | createdAt: this.createdAt, | 500 | createdAt: this.createdAt, |
481 | updatedAt: this.updatedAt, | 501 | updatedAt: this.updatedAt, |
482 | extname: this.extname, | ||
483 | views: this.views, | 502 | views: this.views, |
484 | likes: this.likes, | 503 | likes: this.likes, |
485 | dislikes: this.dislikes | 504 | dislikes: this.dislikes, |
505 | files: [] | ||
486 | } | 506 | } |
487 | 507 | ||
508 | this.VideoFiles.forEach(videoFile => { | ||
509 | remoteVideo.files.push({ | ||
510 | infoHash: videoFile.infoHash, | ||
511 | resolution: videoFile.resolution, | ||
512 | extname: videoFile.extname, | ||
513 | size: videoFile.size | ||
514 | }) | ||
515 | }) | ||
516 | |||
488 | return remoteVideo | 517 | return remoteVideo |
489 | }) | 518 | }) |
490 | } | 519 | } |
@@ -498,28 +527,34 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
498 | language: this.language, | 527 | language: this.language, |
499 | nsfw: this.nsfw, | 528 | nsfw: this.nsfw, |
500 | description: this.description, | 529 | description: this.description, |
501 | infoHash: this.infoHash, | ||
502 | author: this.Author.name, | 530 | author: this.Author.name, |
503 | duration: this.duration, | 531 | duration: this.duration, |
504 | tags: map<TagInstance, string>(this.Tags, 'name'), | 532 | tags: map<TagInstance, string>(this.Tags, 'name'), |
505 | createdAt: this.createdAt, | 533 | createdAt: this.createdAt, |
506 | updatedAt: this.updatedAt, | 534 | updatedAt: this.updatedAt, |
507 | extname: this.extname, | ||
508 | views: this.views, | 535 | views: this.views, |
509 | likes: this.likes, | 536 | likes: this.likes, |
510 | dislikes: this.dislikes | 537 | dislikes: this.dislikes, |
538 | files: [] | ||
511 | } | 539 | } |
512 | 540 | ||
541 | this.VideoFiles.forEach(videoFile => { | ||
542 | json.files.push({ | ||
543 | infoHash: videoFile.infoHash, | ||
544 | resolution: videoFile.resolution, | ||
545 | extname: videoFile.extname, | ||
546 | size: videoFile.size | ||
547 | }) | ||
548 | }) | ||
549 | |||
513 | return json | 550 | return json |
514 | } | 551 | } |
515 | 552 | ||
516 | transcodeVideofile = function (this: VideoInstance) { | 553 | transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileInstance) { |
517 | const video = this | ||
518 | |||
519 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 554 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
520 | const newExtname = '.mp4' | 555 | const newExtname = '.mp4' |
521 | const videoInputPath = join(videosDirectory, video.getVideoFilename()) | 556 | const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) |
522 | const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname) | 557 | const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) |
523 | 558 | ||
524 | return new Promise<void>((res, rej) => { | 559 | return new Promise<void>((res, rej) => { |
525 | ffmpeg(videoInputPath) | 560 | ffmpeg(videoInputPath) |
@@ -533,24 +568,22 @@ transcodeVideofile = function (this: VideoInstance) { | |||
533 | return unlinkPromise(videoInputPath) | 568 | return unlinkPromise(videoInputPath) |
534 | .then(() => { | 569 | .then(() => { |
535 | // Important to do this before getVideoFilename() to take in account the new file extension | 570 | // Important to do this before getVideoFilename() to take in account the new file extension |
536 | video.set('extname', newExtname) | 571 | inputVideoFile.set('extname', newExtname) |
537 | 572 | ||
538 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) | 573 | return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) |
539 | return renamePromise(videoOutputPath, newVideoPath) | ||
540 | }) | 574 | }) |
541 | .then(() => { | 575 | .then(() => { |
542 | const newVideoPath = join(videosDirectory, video.getVideoFilename()) | 576 | return this.createTorrentAndSetInfoHash(inputVideoFile) |
543 | return createTorrentFromVideo(video, newVideoPath) | ||
544 | }) | 577 | }) |
545 | .then(() => { | 578 | .then(() => { |
546 | return video.save() | 579 | return inputVideoFile.save() |
547 | }) | 580 | }) |
548 | .then(() => { | 581 | .then(() => { |
549 | return res() | 582 | return res() |
550 | }) | 583 | }) |
551 | .catch(err => { | 584 | .catch(err => { |
552 | // Autodesctruction... | 585 | // Autodestruction... |
553 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) | 586 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) |
554 | 587 | ||
555 | return rej(err) | 588 | return rej(err) |
556 | }) | 589 | }) |
@@ -559,6 +592,26 @@ transcodeVideofile = function (this: VideoInstance) { | |||
559 | }) | 592 | }) |
560 | } | 593 | } |
561 | 594 | ||
595 | removeThumbnail = function (this: VideoInstance) { | ||
596 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | ||
597 | return unlinkPromise(thumbnailPath) | ||
598 | } | ||
599 | |||
600 | removePreview = function (this: VideoInstance) { | ||
601 | // Same name than video thumbnail | ||
602 | return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) | ||
603 | } | ||
604 | |||
605 | removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
606 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | ||
607 | return unlinkPromise(filePath) | ||
608 | } | ||
609 | |||
610 | removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) { | ||
611 | const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | ||
612 | return unlinkPromise(torrenPath) | ||
613 | } | ||
614 | |||
562 | // ------------------------------ STATICS ------------------------------ | 615 | // ------------------------------ STATICS ------------------------------ |
563 | 616 | ||
564 | generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) { | 617 | generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) { |
@@ -582,7 +635,11 @@ getDurationFromFile = function (videoPath: string) { | |||
582 | } | 635 | } |
583 | 636 | ||
584 | list = function () { | 637 | list = function () { |
585 | return Video.findAll() | 638 | const query = { |
639 | include: [ Video['sequelize'].models.VideoFile ] | ||
640 | } | ||
641 | |||
642 | return Video.findAll(query) | ||
586 | } | 643 | } |
587 | 644 | ||
588 | listForApi = function (start: number, count: number, sort: string) { | 645 | listForApi = function (start: number, count: number, sort: string) { |
@@ -597,8 +654,8 @@ listForApi = function (start: number, count: number, sort: string) { | |||
597 | model: Video['sequelize'].models.Author, | 654 | model: Video['sequelize'].models.Author, |
598 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] | 655 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] |
599 | }, | 656 | }, |
600 | 657 | Video['sequelize'].models.Tag, | |
601 | Video['sequelize'].models.Tag | 658 | Video['sequelize'].models.VideoFile |
602 | ], | 659 | ], |
603 | where: createBaseVideosWhere() | 660 | where: createBaseVideosWhere() |
604 | } | 661 | } |
@@ -618,6 +675,9 @@ loadByHostAndUUID = function (fromHost: string, uuid: string) { | |||
618 | }, | 675 | }, |
619 | include: [ | 676 | include: [ |
620 | { | 677 | { |
678 | model: Video['sequelize'].models.VideoFile | ||
679 | }, | ||
680 | { | ||
621 | model: Video['sequelize'].models.Author, | 681 | model: Video['sequelize'].models.Author, |
622 | include: [ | 682 | include: [ |
623 | { | 683 | { |
@@ -640,7 +700,11 @@ listOwnedAndPopulateAuthorAndTags = function () { | |||
640 | where: { | 700 | where: { |
641 | remote: false | 701 | remote: false |
642 | }, | 702 | }, |
643 | include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ] | 703 | include: [ |
704 | Video['sequelize'].models.VideoFile, | ||
705 | Video['sequelize'].models.Author, | ||
706 | Video['sequelize'].models.Tag | ||
707 | ] | ||
644 | } | 708 | } |
645 | 709 | ||
646 | return Video.findAll(query) | 710 | return Video.findAll(query) |
@@ -653,6 +717,9 @@ listOwnedByAuthor = function (author: string) { | |||
653 | }, | 717 | }, |
654 | include: [ | 718 | include: [ |
655 | { | 719 | { |
720 | model: Video['sequelize'].models.VideoFile | ||
721 | }, | ||
722 | { | ||
656 | model: Video['sequelize'].models.Author, | 723 | model: Video['sequelize'].models.Author, |
657 | where: { | 724 | where: { |
658 | name: author | 725 | name: author |
@@ -672,14 +739,15 @@ loadByUUID = function (uuid: string) { | |||
672 | const query = { | 739 | const query = { |
673 | where: { | 740 | where: { |
674 | uuid | 741 | uuid |
675 | } | 742 | }, |
743 | include: [ Video['sequelize'].models.VideoFile ] | ||
676 | } | 744 | } |
677 | return Video.findOne(query) | 745 | return Video.findOne(query) |
678 | } | 746 | } |
679 | 747 | ||
680 | loadAndPopulateAuthor = function (id: number) { | 748 | loadAndPopulateAuthor = function (id: number) { |
681 | const options = { | 749 | const options = { |
682 | include: [ Video['sequelize'].models.Author ] | 750 | include: [ Video['sequelize'].models.VideoFile, Video['sequelize'].models.Author ] |
683 | } | 751 | } |
684 | 752 | ||
685 | return Video.findById(id, options) | 753 | return Video.findById(id, options) |
@@ -692,7 +760,8 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) { | |||
692 | model: Video['sequelize'].models.Author, | 760 | model: Video['sequelize'].models.Author, |
693 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] | 761 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] |
694 | }, | 762 | }, |
695 | Video['sequelize'].models.Tag | 763 | Video['sequelize'].models.Tag, |
764 | Video['sequelize'].models.VideoFile | ||
696 | ] | 765 | ] |
697 | } | 766 | } |
698 | 767 | ||
@@ -709,7 +778,8 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) { | |||
709 | model: Video['sequelize'].models.Author, | 778 | model: Video['sequelize'].models.Author, |
710 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] | 779 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] |
711 | }, | 780 | }, |
712 | Video['sequelize'].models.Tag | 781 | Video['sequelize'].models.Tag, |
782 | Video['sequelize'].models.VideoFile | ||
713 | ] | 783 | ] |
714 | } | 784 | } |
715 | 785 | ||
@@ -733,6 +803,10 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
733 | model: Video['sequelize'].models.Tag | 803 | model: Video['sequelize'].models.Tag |
734 | } | 804 | } |
735 | 805 | ||
806 | const videoFileInclude: Sequelize.IncludeOptions = { | ||
807 | model: Video['sequelize'].models.VideoFile | ||
808 | } | ||
809 | |||
736 | const query: Sequelize.FindOptions = { | 810 | const query: Sequelize.FindOptions = { |
737 | distinct: true, | 811 | distinct: true, |
738 | where: createBaseVideosWhere(), | 812 | where: createBaseVideosWhere(), |
@@ -743,8 +817,9 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
743 | 817 | ||
744 | // Make an exact search with the magnet | 818 | // Make an exact search with the magnet |
745 | if (field === 'magnetUri') { | 819 | if (field === 'magnetUri') { |
746 | const infoHash = magnetUtil.decode(value).infoHash | 820 | videoFileInclude.where = { |
747 | query.where['infoHash'] = infoHash | 821 | infoHash: magnetUtil.decode(value).infoHash |
822 | } | ||
748 | } else if (field === 'tags') { | 823 | } else if (field === 'tags') { |
749 | const escapedValue = Video['sequelize'].escape('%' + value + '%') | 824 | const escapedValue = Video['sequelize'].escape('%' + value + '%') |
750 | query.where['id'].$in = Video['sequelize'].literal( | 825 | query.where['id'].$in = Video['sequelize'].literal( |
@@ -777,7 +852,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
777 | } | 852 | } |
778 | 853 | ||
779 | query.include = [ | 854 | query.include = [ |
780 | authorInclude, tagInclude | 855 | authorInclude, tagInclude, videoFileInclude |
781 | ] | 856 | ] |
782 | 857 | ||
783 | return Video.findAndCountAll(query).then(({ rows, count }) => { | 858 | return Video.findAndCountAll(query).then(({ rows, count }) => { |
@@ -800,56 +875,6 @@ function createBaseVideosWhere () { | |||
800 | } | 875 | } |
801 | } | 876 | } |
802 | 877 | ||
803 | function removeThumbnail (video: VideoInstance) { | ||
804 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) | ||
805 | return unlinkPromise(thumbnailPath) | ||
806 | } | ||
807 | |||
808 | function removeFile (video: VideoInstance) { | ||
809 | const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | ||
810 | return unlinkPromise(filePath) | ||
811 | } | ||
812 | |||
813 | function removeTorrent (video: VideoInstance) { | ||
814 | const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) | ||
815 | return unlinkPromise(torrenPath) | ||
816 | } | ||
817 | |||
818 | function removePreview (video: VideoInstance) { | ||
819 | // Same name than video thumnail | ||
820 | return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName()) | ||
821 | } | ||
822 | |||
823 | function createTorrentFromVideo (video: VideoInstance, videoPath: string) { | ||
824 | const options = { | ||
825 | announceList: [ | ||
826 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] | ||
827 | ], | ||
828 | urlList: [ | ||
829 | CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + video.getVideoFilename() | ||
830 | ] | ||
831 | } | ||
832 | |||
833 | return createTorrentPromise(videoPath, options) | ||
834 | .then(torrent => { | ||
835 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) | ||
836 | return writeFilePromise(filePath, torrent).then(() => torrent) | ||
837 | }) | ||
838 | .then(torrent => { | ||
839 | const parsedTorrent = parseTorrent(torrent) | ||
840 | video.set('infoHash', parsedTorrent.infoHash) | ||
841 | return video.validate() | ||
842 | }) | ||
843 | } | ||
844 | |||
845 | function createPreview (video: VideoInstance, videoPath: string) { | ||
846 | return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null) | ||
847 | } | ||
848 | |||
849 | function createThumbnail (video: VideoInstance, videoPath: string) { | ||
850 | return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE) | ||
851 | } | ||
852 | |||
853 | function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { | 878 | function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { |
854 | const options = { | 879 | const options = { |
855 | filename: imageName, | 880 | filename: imageName, |
@@ -868,16 +893,3 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string, | |||
868 | .thumbnail(options) | 893 | .thumbnail(options) |
869 | }) | 894 | }) |
870 | } | 895 | } |
871 | |||
872 | function removeFromBlacklist (video: VideoInstance) { | ||
873 | // Find the blacklisted video | ||
874 | return db.BlacklistedVideo.loadByVideoId(video.id).then(video => { | ||
875 | // Not found the video, skip | ||
876 | if (!video) { | ||
877 | return null | ||
878 | } | ||
879 | |||
880 | // If we found the video, remove it from the blacklist | ||
881 | return video.destroy() | ||
882 | }) | ||
883 | } | ||