aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/remote.js2
-rw-r--r--server/controllers/api/videos.js29
-rw-r--r--server/helpers/custom-validators/videos.js9
-rw-r--r--server/models/video.js146
4 files changed, 109 insertions, 77 deletions
diff --git a/server/controllers/api/remote.js b/server/controllers/api/remote.js
index 17b1d07c4..94808693d 100644
--- a/server/controllers/api/remote.js
+++ b/server/controllers/api/remote.js
@@ -64,7 +64,7 @@ function addRemoteVideo (videoToCreateData, callback) {
64 64
65function removeRemoteVideo (videoToRemoveData, fromUrl, callback) { 65function removeRemoteVideo (videoToRemoveData, fromUrl, callback) {
66 // We need the list because we have to remove some other stuffs (thumbnail etc) 66 // We need the list because we have to remove some other stuffs (thumbnail etc)
67 Video.listByUrlAndMagnet(fromUrl, videoToRemoveData.magnetUri, function (err, videosList) { 67 Video.listByUrlAndRemoteId(fromUrl, videoToRemoveData.remoteId, function (err, videosList) {
68 if (err) { 68 if (err) {
69 logger.error('Cannot list videos from url and magnets.', { error: err }) 69 logger.error('Cannot list videos from url and magnets.', { error: err })
70 return callback(err) 70 return callback(err)
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js
index e2d393074..2c9e4940e 100644
--- a/server/controllers/api/videos.js
+++ b/server/controllers/api/videos.js
@@ -1,8 +1,10 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const fs = require('fs')
4const mongoose = require('mongoose') 5const mongoose = require('mongoose')
5const multer = require('multer') 6const multer = require('multer')
7const path = require('path')
6const waterfall = require('async/waterfall') 8const waterfall = require('async/waterfall')
7 9
8const constants = require('../../initializers/constants') 10const constants = require('../../initializers/constants')
@@ -85,11 +87,14 @@ function addVideo (req, res, next) {
85 const videoInfos = req.body 87 const videoInfos = req.body
86 88
87 waterfall([ 89 waterfall([
90 function createVideoObject (callback) {
91 const id = mongoose.Types.ObjectId()
88 92
89 function insertIntoDB (callback) {
90 const videoData = { 93 const videoData = {
94 _id: id,
91 name: videoInfos.name, 95 name: videoInfos.name,
92 filename: videoFile.filename, 96 remoteId: null,
97 extname: path.extname(videoFile.filename),
93 description: videoInfos.description, 98 description: videoInfos.description,
94 author: res.locals.oauth.token.user.username, 99 author: res.locals.oauth.token.user.username,
95 duration: videoFile.duration, 100 duration: videoFile.duration,
@@ -97,7 +102,23 @@ function addVideo (req, res, next) {
97 } 102 }
98 103
99 const video = new Video(videoData) 104 const video = new Video(videoData)
100 video.save(function (err, video) { 105
106 return callback(null, video)
107 },
108
109 // Set the videoname the same as the MongoDB id
110 function renameVideoFile (video, callback) {
111 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
112 const source = path.join(videoDir, videoFile.filename)
113 const destination = path.join(videoDir, video.getFilename())
114
115 fs.rename(source, destination, function (err) {
116 return callback(err, video)
117 })
118 },
119
120 function insertIntoDB (video, callback) {
121 video.save(function (err, video, videoFile) {
101 // Assert there are only one argument sent to the next function (video) 122 // Assert there are only one argument sent to the next function (video)
102 return callback(err, video) 123 return callback(err, video)
103 }) 124 })
@@ -164,7 +185,7 @@ function removeVideo (req, res, next) {
164 function sendInformationToFriends (video, callback) { 185 function sendInformationToFriends (video, callback) {
165 const params = { 186 const params = {
166 name: video.name, 187 name: video.name,
167 magnetUri: video.magnetUri 188 remoteId: video._id
168 } 189 }
169 190
170 friends.removeVideoToFriends(params) 191 friends.removeVideoToFriends(params)
diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js
index a507ff686..c4c59808f 100644
--- a/server/helpers/custom-validators/videos.js
+++ b/server/helpers/custom-validators/videos.js
@@ -35,12 +35,13 @@ function isEachRemoteVideosValid (requests) {
35 isVideoNameValid(video.name) && 35 isVideoNameValid(video.name) &&
36 isVideoPodUrlValid(video.podUrl) && 36 isVideoPodUrlValid(video.podUrl) &&
37 isVideoTagsValid(video.tags) && 37 isVideoTagsValid(video.tags) &&
38 isVideoThumbnail64Valid(video.thumbnailBase64) 38 isVideoThumbnail64Valid(video.thumbnailBase64) &&
39 isVideoRemoteIdValid(video.remoteId)
39 ) || 40 ) ||
40 ( 41 (
41 isRequestTypeRemoveValid(request.type) && 42 isRequestTypeRemoveValid(request.type) &&
42 isVideoNameValid(video.name) && 43 isVideoNameValid(video.name) &&
43 isVideoMagnetUriValid(video.magnetUri) 44 isVideoRemoteIdValid(video.remoteId)
44 ) 45 )
45 }) 46 })
46} 47}
@@ -92,6 +93,10 @@ function isVideoThumbnail64Valid (value) {
92 validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64) 93 validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64)
93} 94}
94 95
96function isVideoRemoteIdValid (value) {
97 return validator.isMongoId(value)
98}
99
95// --------------------------------------------------------------------------- 100// ---------------------------------------------------------------------------
96 101
97module.exports = videosValidators 102module.exports = videosValidators
diff --git a/server/models/video.js b/server/models/video.js
index bfa1fca15..6cffa87af 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -13,14 +13,17 @@ const constants = require('../initializers/constants')
13const customVideosValidators = require('../helpers/custom-validators').videos 13const customVideosValidators = require('../helpers/custom-validators').videos
14const logger = require('../helpers/logger') 14const logger = require('../helpers/logger')
15const modelUtils = require('./utils') 15const modelUtils = require('./utils')
16const utils = require('../helpers/utils')
17 16
18// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
19 18
20// TODO: add indexes on searchable columns 19// TODO: add indexes on searchable columns
21const VideoSchema = mongoose.Schema({ 20const VideoSchema = mongoose.Schema({
22 name: String, 21 name: String,
23 filename: String, 22 extname: {
23 type: String,
24 enum: [ '.mp4', '.webm', '.ogv' ]
25 },
26 remoteId: mongoose.Schema.Types.ObjectId,
24 description: String, 27 description: String,
25 magnetUri: String, 28 magnetUri: String,
26 podUrl: String, 29 podUrl: String,
@@ -48,6 +51,9 @@ VideoSchema.path('thumbnail').validate(function (value) {
48VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) 51VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
49 52
50VideoSchema.methods = { 53VideoSchema.methods = {
54 getFilename,
55 getJPEGName,
56 getTorrentName,
51 isOwned, 57 isOwned,
52 toFormatedJSON, 58 toFormatedJSON,
53 toRemoteJSON 59 toRemoteJSON
@@ -56,7 +62,7 @@ VideoSchema.methods = {
56VideoSchema.statics = { 62VideoSchema.statics = {
57 getDurationFromFile, 63 getDurationFromFile,
58 listForApi, 64 listForApi,
59 listByUrlAndMagnet, 65 listByUrlAndRemoteId,
60 listByUrl, 66 listByUrl,
61 listOwned, 67 listOwned,
62 listOwnedByAuthor, 68 listOwnedByAuthor,
@@ -97,7 +103,7 @@ VideoSchema.pre('save', function (next) {
97 const tasks = [] 103 const tasks = []
98 104
99 if (video.isOwned()) { 105 if (video.isOwned()) {
100 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.filename) 106 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getFilename())
101 this.podUrl = constants.CONFIG.WEBSERVER.URL 107 this.podUrl = constants.CONFIG.WEBSERVER.URL
102 108
103 tasks.push( 109 tasks.push(
@@ -108,18 +114,18 @@ VideoSchema.pre('save', function (next) {
108 [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ] 114 [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
109 ], 115 ],
110 urlList: [ 116 urlList: [
111 constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.filename 117 constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getFilename()
112 ] 118 ]
113 } 119 }
114 120
115 createTorrent(videoPath, options, function (err, torrent) { 121 createTorrent(videoPath, options, function (err, torrent) {
116 if (err) return callback(err) 122 if (err) return callback(err)
117 123
118 fs.writeFile(constants.CONFIG.STORAGE.TORRENTS_DIR + video.filename + '.torrent', torrent, function (err) { 124 fs.writeFile(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), torrent, function (err) {
119 if (err) return callback(err) 125 if (err) return callback(err)
120 126
121 const parsedTorrent = parseTorrent(torrent) 127 const parsedTorrent = parseTorrent(torrent)
122 parsedTorrent.xs = video.podUrl + constants.STATIC_PATHS.TORRENTS + video.filename + '.torrent' 128 parsedTorrent.xs = video.podUrl + constants.STATIC_PATHS.TORRENTS + video.getTorrentName()
123 video.magnetUri = magnet.encode(parsedTorrent) 129 video.magnetUri = magnet.encode(parsedTorrent)
124 130
125 callback(null) 131 callback(null)
@@ -127,28 +133,16 @@ VideoSchema.pre('save', function (next) {
127 }) 133 })
128 }, 134 },
129 function (callback) { 135 function (callback) {
130 createThumbnail(videoPath, callback) 136 createThumbnail(video, videoPath, callback)
131 }, 137 },
132 function (callback) { 138 function (callback) {
133 createPreview(videoPath, callback) 139 createPreview(video, videoPath, callback)
134 } 140 }
135 ) 141 )
136 142
137 parallel(tasks, function (err, results) { 143 parallel(tasks, next)
138 if (err) return next(err)
139
140 video.thumbnail = results[1]
141
142 return next()
143 })
144 } else { 144 } else {
145 generateThumbnailFromBase64(video.thumbnail, function (err, thumbnailName) { 145 generateThumbnailFromBase64(video, video.thumbnail, next)
146 if (err) return next(err)
147
148 video.thumbnail = thumbnailName
149
150 return next()
151 })
152 } 146 }
153}) 147})
154 148
@@ -156,8 +150,20 @@ mongoose.model('Video', VideoSchema)
156 150
157// ------------------------------ METHODS ------------------------------ 151// ------------------------------ METHODS ------------------------------
158 152
153function getFilename () {
154 return this._id + this.extname
155}
156
157function getJPEGName () {
158 return this._id + '.jpg'
159}
160
161function getTorrentName () {
162 return this._id + '.torrent'
163}
164
159function isOwned () { 165function isOwned () {
160 return this.filename !== null 166 return this.remoteId === null
161} 167}
162 168
163function toFormatedJSON () { 169function toFormatedJSON () {
@@ -171,7 +177,7 @@ function toFormatedJSON () {
171 author: this.author, 177 author: this.author,
172 duration: this.duration, 178 duration: this.duration,
173 tags: this.tags, 179 tags: this.tags,
174 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.thumbnail, 180 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getJPEGName(),
175 createdDate: this.createdDate 181 createdDate: this.createdDate
176 } 182 }
177 183
@@ -182,7 +188,8 @@ function toRemoteJSON (callback) {
182 const self = this 188 const self = this
183 189
184 // Convert thumbnail to base64 190 // Convert thumbnail to base64
185 fs.readFile(pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.thumbnail), function (err, thumbnailData) { 191 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getJPEGName())
192 fs.readFile(thumbnailPath, function (err, thumbnailData) {
186 if (err) { 193 if (err) {
187 logger.error('Cannot read the thumbnail of the video') 194 logger.error('Cannot read the thumbnail of the video')
188 return callback(err) 195 return callback(err)
@@ -192,7 +199,7 @@ function toRemoteJSON (callback) {
192 name: self.name, 199 name: self.name,
193 description: self.description, 200 description: self.description,
194 magnetUri: self.magnetUri, 201 magnetUri: self.magnetUri,
195 filename: null, 202 remoteId: self._id,
196 author: self.author, 203 author: self.author,
197 duration: self.duration, 204 duration: self.duration,
198 thumbnailBase64: new Buffer(thumbnailData).toString('base64'), 205 thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
@@ -220,8 +227,8 @@ function listForApi (start, count, sort, callback) {
220 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 227 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
221} 228}
222 229
223function listByUrlAndMagnet (fromUrl, magnetUri, callback) { 230function listByUrlAndRemoteId (fromUrl, remoteId, callback) {
224 this.find({ podUrl: fromUrl, magnetUri: magnetUri }, callback) 231 this.find({ podUrl: fromUrl, remoteId: remoteId }, callback)
225} 232}
226 233
227function listByUrl (fromUrl, callback) { 234function listByUrl (fromUrl, callback) {
@@ -229,16 +236,16 @@ function listByUrl (fromUrl, callback) {
229} 236}
230 237
231function listOwned (callback) { 238function listOwned (callback) {
232 // If filename is not null this is *our* video 239 // If remoteId is null this is *our* video
233 this.find({ filename: { $ne: null } }, callback) 240 this.find({ remoteId: null }, callback)
234} 241}
235 242
236function listOwnedByAuthor (author, callback) { 243function listOwnedByAuthor (author, callback) {
237 this.find({ filename: { $ne: null }, author: author }, callback) 244 this.find({ remoteId: null, author: author }, callback)
238} 245}
239 246
240function listRemotes (callback) { 247function listRemotes (callback) {
241 this.find({ filename: null }, callback) 248 this.find({ remoteId: { $ne: null } }, callback)
242} 249}
243 250
244function load (id, callback) { 251function load (id, callback) {
@@ -260,62 +267,61 @@ function search (value, field, start, count, sort, callback) {
260// --------------------------------------------------------------------------- 267// ---------------------------------------------------------------------------
261 268
262function removeThumbnail (video, callback) { 269function removeThumbnail (video, callback) {
263 fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.thumbnail, callback) 270 fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.getJPEGName(), callback)
264} 271}
265 272
266function removeFile (video, callback) { 273function removeFile (video, callback) {
267 fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.filename, callback) 274 fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.getFilename(), callback)
268} 275}
269 276
270function removeTorrent (video, callback) { 277function removeTorrent (video, callback) {
271 fs.unlink(constants.CONFIG.STORAGE.TORRENTS_DIR + video.filename + '.torrent', callback) 278 fs.unlink(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), callback)
272} 279}
273 280
274function removePreview (video, callback) { 281function removePreview (video, callback) {
275 // Same name than video thumnail 282 // Same name than video thumnail
276 // TODO: refractoring 283 // TODO: refractoring
277 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.thumbnail, callback) 284 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getJPEGName(), callback)
278} 285}
279 286
280function createPreview (videoPath, callback) { 287function createPreview (video, videoPath, callback) {
281 const filename = pathUtils.basename(videoPath) + '.jpg' 288 generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, callback)
282 ffmpeg(videoPath)
283 .on('error', callback)
284 .on('end', function () {
285 callback(null, filename)
286 })
287 .thumbnail({
288 count: 1,
289 folder: constants.CONFIG.STORAGE.PREVIEWS_DIR,
290 filename: filename
291 })
292} 289}
293 290
294function createThumbnail (videoPath, callback) { 291function createThumbnail (video, videoPath, callback) {
295 const filename = pathUtils.basename(videoPath) + '.jpg' 292 generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, constants.THUMBNAILS_SIZE, callback)
296 ffmpeg(videoPath)
297 .on('error', callback)
298 .on('end', function () {
299 callback(null, filename)
300 })
301 .thumbnail({
302 count: 1,
303 folder: constants.CONFIG.STORAGE.THUMBNAILS_DIR,
304 size: constants.THUMBNAILS_SIZE,
305 filename: filename
306 })
307} 293}
308 294
309function generateThumbnailFromBase64 (data, callback) { 295function generateThumbnailFromBase64 (video, thumbnailData, callback) {
310 // Creating the thumbnail for this remote video 296 // Creating the thumbnail for this remote video)
311 utils.generateRandomString(16, function (err, randomString) { 297
298 const thumbnailName = video.getJPEGName()
299 const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName
300 fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) {
312 if (err) return callback(err) 301 if (err) return callback(err)
313 302
314 const thumbnailName = randomString + '.jpg' 303 return callback(null, thumbnailName)
315 fs.writeFile(constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName, data, { encoding: 'base64' }, function (err) { 304 })
316 if (err) return callback(err) 305}
306
307function generateImage (video, videoPath, folder, size, callback) {
308 const filename = video.getJPEGName()
309 const options = {
310 filename,
311 count: 1,
312 folder
313 }
317 314
318 return callback(null, thumbnailName) 315 if (!callback) {
316 callback = size
317 } else {
318 options.size = size
319 }
320
321 ffmpeg(videoPath)
322 .on('error', callback)
323 .on('end', function () {
324 callback(null, filename)
319 }) 325 })
320 }) 326 .thumbnail(options)
321} 327}