aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-08-25 11:36:23 +0200
committerChocobozzz <florian.bigard@gmail.com>2017-08-25 11:36:23 +0200
commit93e1258c7cbc0d1235ca6d2a1f7c1875985328b8 (patch)
treeb0a1f77af7ab54dc5f58f569fcd1e9d84b04c533 /server/models/video
parent69f224587e99d56008e1fa129d0641840a486620 (diff)
downloadPeerTube-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.ts1
-rw-r--r--server/models/video/video-file-interface.ts24
-rw-r--r--server/models/video/video-file.ts89
-rw-r--r--server/models/video/video-interface.ts67
-rw-r--r--server/models/video/video.ts434
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'
3export * from './video-abuse-interface' 3export * from './video-abuse-interface'
4export * from './video-blacklist-interface' 4export * from './video-blacklist-interface'
5export * from './video-tag-interface' 5export * from './video-tag-interface'
6export * from './video-file-interface'
6export * from './video-interface' 7export * 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 @@
1import * as Sequelize from 'sequelize'
2
3export namespace VideoFileMethods {
4}
5
6export interface VideoFileClass {
7}
8
9export interface VideoFileAttributes {
10 resolution: number
11 size: number
12 infoHash?: string
13 extname: string
14
15 videoId?: number
16}
17
18export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance<VideoFileAttributes> {
19 id: number
20 createdAt: Date
21 updatedAt: Date
22}
23
24export 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 @@
1import * as Sequelize from 'sequelize'
2import { values } from 'lodash'
3
4import { CONSTRAINTS_FIELDS } from '../../initializers'
5import {
6 isVideoFileResolutionValid,
7 isVideoFileSizeValid,
8 isVideoFileInfoHashValid
9} from '../../helpers'
10
11import { addMethodsToModel } from '../utils'
12import {
13 VideoFileInstance,
14 VideoFileAttributes
15} from './video-file-interface'
16
17let VideoFile: Sequelize.Model<VideoFileInstance, VideoFileAttributes>
18
19export 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
79function 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
4import { AuthorInstance } from './author-interface' 4import { AuthorInstance } from './author-interface'
5import { TagAttributes, TagInstance } from './tag-interface' 5import { TagAttributes, TagInstance } from './tag-interface'
6import { 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
8import { Video as FormatedVideo } from '../../../shared/models/videos/video.model' 9import { Video as FormatedVideo } from '../../../shared/models/videos/video.model'
9import { ResultList } from '../../../shared/models/result-list.model' 10import { ResultList } from '../../../shared/models/result-list.model'
10 11
12export type FormatedRemoteVideoFile = {
13 infoHash: string
14 resolution: number
15 extname: string
16 size: number
17}
18
11export type FormatedAddRemoteVideo = { 19export 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
32export type FormatedUpdateRemoteVideo = { 39export 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
52export namespace VideoMethods { 58export 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
91export interface VideoClass { 107export 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 {
118export interface VideoAttributes { 123export 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
138export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { 142export 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
157export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} 170export 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'
2const Buffer = safeBuffer.Buffer 2const Buffer = safeBuffer.Buffer
3import * as ffmpeg from 'fluent-ffmpeg' 3import * as ffmpeg from 'fluent-ffmpeg'
4import * as magnetUtil from 'magnet-uri' 4import * as magnetUtil from 'magnet-uri'
5import { map, values } from 'lodash' 5import { map } from 'lodash'
6import * as parseTorrent from 'parse-torrent' 6import * as parseTorrent from 'parse-torrent'
7import { join } from 'path' 7import { join } from 'path'
8import * as Sequelize from 'sequelize' 8import * as Sequelize from 'sequelize'
9import * as Promise from 'bluebird' 9import * as Promise from 'bluebird'
10 10
11import { database as db } from '../../initializers/database'
12import { TagInstance } from './tag-interface' 11import { TagInstance } from './tag-interface'
13import { 12import {
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'
29import { 27import {
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'
39import { JobScheduler, removeVideoToFriends } from '../../lib' 37import { removeVideoToFriends } from '../../lib'
38import { VideoFileInstance } from './video-file-interface'
40 39
41import { addMethodsToModel, getSort } from '../utils' 40import { addMethodsToModel, getSort } from '../utils'
42import { 41import {
@@ -51,12 +50,16 @@ let generateMagnetUri: VideoMethods.GenerateMagnetUri
51let getVideoFilename: VideoMethods.GetVideoFilename 50let getVideoFilename: VideoMethods.GetVideoFilename
52let getThumbnailName: VideoMethods.GetThumbnailName 51let getThumbnailName: VideoMethods.GetThumbnailName
53let getPreviewName: VideoMethods.GetPreviewName 52let getPreviewName: VideoMethods.GetPreviewName
54let getTorrentName: VideoMethods.GetTorrentName 53let getTorrentFileName: VideoMethods.GetTorrentFileName
55let isOwned: VideoMethods.IsOwned 54let isOwned: VideoMethods.IsOwned
56let toFormatedJSON: VideoMethods.ToFormatedJSON 55let toFormatedJSON: VideoMethods.ToFormatedJSON
57let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON 56let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
58let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON 57let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
59let transcodeVideofile: VideoMethods.TranscodeVideofile 58let transcodeVideofile: VideoMethods.TranscodeVideofile
59let createPreview: VideoMethods.CreatePreview
60let createThumbnail: VideoMethods.CreateThumbnail
61let getVideoFilePath: VideoMethods.GetVideoFilePath
62let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
60 63
61let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData 64let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
62let getDurationFromFile: VideoMethods.GetDurationFromFile 65let getDurationFromFile: VideoMethods.GetDurationFromFile
@@ -71,6 +74,10 @@ let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
71let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags 74let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
72let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags 75let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags
73let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags 76let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
77let removeThumbnail: VideoMethods.RemoveThumbnail
78let removePreview: VideoMethods.RemovePreview
79let removeFile: VideoMethods.RemoveFile
80let removeTorrent: VideoMethods.RemoveTorrent
74 81
75export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 82export 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
274function 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
282function 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
310function 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
335function associate (models) { 271function 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
359generateMagnetUri = function (this: VideoInstance) { 303function 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
386getVideoFilename = function (this: VideoInstance) { 330getVideoFilename = 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
390getThumbnailName = function (this: VideoInstance) { 335getThumbnailName = 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
401getTorrentName = function (this: VideoInstance) { 346getTorrentFileName = 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
356createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) {
357 return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.PREVIEWS_DIR, this.getPreviewName(), null)
358}
359
360createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) {
361 return generateImage(this, this.getVideoFilePath(videoFile), CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName(), THUMBNAILS_SIZE)
362}
363
364getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) {
365 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
366}
367
368createTorrentAndSetInfoHash = 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
390generateMagnetUri = 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
410toFormatedJSON = function (this: VideoInstance) { 417toFormatedJSON = 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
516transcodeVideofile = function (this: VideoInstance) { 553transcodeVideofile = 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
595removeThumbnail = function (this: VideoInstance) {
596 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
597 return unlinkPromise(thumbnailPath)
598}
599
600removePreview = function (this: VideoInstance) {
601 // Same name than video thumbnail
602 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
603}
604
605removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) {
606 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
607 return unlinkPromise(filePath)
608}
609
610removeTorrent = 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
564generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) { 617generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
@@ -582,7 +635,11 @@ getDurationFromFile = function (videoPath: string) {
582} 635}
583 636
584list = function () { 637list = 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
588listForApi = function (start: number, count: number, sort: string) { 645listForApi = 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
680loadAndPopulateAuthor = function (id: number) { 748loadAndPopulateAuthor = 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
803function removeThumbnail (video: VideoInstance) {
804 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
805 return unlinkPromise(thumbnailPath)
806}
807
808function removeFile (video: VideoInstance) {
809 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
810 return unlinkPromise(filePath)
811}
812
813function removeTorrent (video: VideoInstance) {
814 const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
815 return unlinkPromise(torrenPath)
816}
817
818function removePreview (video: VideoInstance) {
819 // Same name than video thumnail
820 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName())
821}
822
823function 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
845function createPreview (video: VideoInstance, videoPath: string) {
846 return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null)
847}
848
849function createThumbnail (video: VideoInstance, videoPath: string) {
850 return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE)
851}
852
853function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { 878function 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
872function 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}