diff options
-rw-r--r-- | server/controllers/api/remote.js | 2 | ||||
-rw-r--r-- | server/controllers/api/videos.js | 29 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.js | 9 | ||||
-rw-r--r-- | server/models/video.js | 146 |
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 | ||
65 | function removeRemoteVideo (videoToRemoveData, fromUrl, callback) { | 65 | function 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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const fs = require('fs') | ||
4 | const mongoose = require('mongoose') | 5 | const mongoose = require('mongoose') |
5 | const multer = require('multer') | 6 | const multer = require('multer') |
7 | const path = require('path') | ||
6 | const waterfall = require('async/waterfall') | 8 | const waterfall = require('async/waterfall') |
7 | 9 | ||
8 | const constants = require('../../initializers/constants') | 10 | const 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 | ||
96 | function isVideoRemoteIdValid (value) { | ||
97 | return validator.isMongoId(value) | ||
98 | } | ||
99 | |||
95 | // --------------------------------------------------------------------------- | 100 | // --------------------------------------------------------------------------- |
96 | 101 | ||
97 | module.exports = videosValidators | 102 | module.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') | |||
13 | const customVideosValidators = require('../helpers/custom-validators').videos | 13 | const customVideosValidators = require('../helpers/custom-validators').videos |
14 | const logger = require('../helpers/logger') | 14 | const logger = require('../helpers/logger') |
15 | const modelUtils = require('./utils') | 15 | const modelUtils = require('./utils') |
16 | const utils = require('../helpers/utils') | ||
17 | 16 | ||
18 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
19 | 18 | ||
20 | // TODO: add indexes on searchable columns | 19 | // TODO: add indexes on searchable columns |
21 | const VideoSchema = mongoose.Schema({ | 20 | const 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) { | |||
48 | VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) | 51 | VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) |
49 | 52 | ||
50 | VideoSchema.methods = { | 53 | VideoSchema.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 = { | |||
56 | VideoSchema.statics = { | 62 | VideoSchema.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 | ||
153 | function getFilename () { | ||
154 | return this._id + this.extname | ||
155 | } | ||
156 | |||
157 | function getJPEGName () { | ||
158 | return this._id + '.jpg' | ||
159 | } | ||
160 | |||
161 | function getTorrentName () { | ||
162 | return this._id + '.torrent' | ||
163 | } | ||
164 | |||
159 | function isOwned () { | 165 | function isOwned () { |
160 | return this.filename !== null | 166 | return this.remoteId === null |
161 | } | 167 | } |
162 | 168 | ||
163 | function toFormatedJSON () { | 169 | function 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 | ||
223 | function listByUrlAndMagnet (fromUrl, magnetUri, callback) { | 230 | function listByUrlAndRemoteId (fromUrl, remoteId, callback) { |
224 | this.find({ podUrl: fromUrl, magnetUri: magnetUri }, callback) | 231 | this.find({ podUrl: fromUrl, remoteId: remoteId }, callback) |
225 | } | 232 | } |
226 | 233 | ||
227 | function listByUrl (fromUrl, callback) { | 234 | function listByUrl (fromUrl, callback) { |
@@ -229,16 +236,16 @@ function listByUrl (fromUrl, callback) { | |||
229 | } | 236 | } |
230 | 237 | ||
231 | function listOwned (callback) { | 238 | function 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 | ||
236 | function listOwnedByAuthor (author, callback) { | 243 | function listOwnedByAuthor (author, callback) { |
237 | this.find({ filename: { $ne: null }, author: author }, callback) | 244 | this.find({ remoteId: null, author: author }, callback) |
238 | } | 245 | } |
239 | 246 | ||
240 | function listRemotes (callback) { | 247 | function listRemotes (callback) { |
241 | this.find({ filename: null }, callback) | 248 | this.find({ remoteId: { $ne: null } }, callback) |
242 | } | 249 | } |
243 | 250 | ||
244 | function load (id, callback) { | 251 | function load (id, callback) { |
@@ -260,62 +267,61 @@ function search (value, field, start, count, sort, callback) { | |||
260 | // --------------------------------------------------------------------------- | 267 | // --------------------------------------------------------------------------- |
261 | 268 | ||
262 | function removeThumbnail (video, callback) { | 269 | function 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 | ||
266 | function removeFile (video, callback) { | 273 | function 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 | ||
270 | function removeTorrent (video, callback) { | 277 | function 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 | ||
274 | function removePreview (video, callback) { | 281 | function 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 | ||
280 | function createPreview (videoPath, callback) { | 287 | function 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 | ||
294 | function createThumbnail (videoPath, callback) { | 291 | function 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 | ||
309 | function generateThumbnailFromBase64 (data, callback) { | 295 | function 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 | |||
307 | function 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 | } |