aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2016-11-11 15:20:03 +0100
committerChocobozzz <florian.bigard@gmail.com>2016-11-16 20:29:26 +0100
commitf285faa04e84b45e62bd05e7050dc500113b0356 (patch)
treebae907d0714e435e92137347e0b892b806c5302c
parent558d7c2385d8a152a94140eed753f511e90986d7 (diff)
downloadPeerTube-f285faa04e84b45e62bd05e7050dc500113b0356.tar.gz
PeerTube-f285faa04e84b45e62bd05e7050dc500113b0356.tar.zst
PeerTube-f285faa04e84b45e62bd05e7050dc500113b0356.zip
Server: generate magnet uri on the fly
-rw-r--r--server/controllers/api/videos.js4
-rw-r--r--server/helpers/custom-validators/videos.js8
-rw-r--r--server/initializers/constants.js18
-rw-r--r--server/models/video.js102
4 files changed, 92 insertions, 40 deletions
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js
index 2c9e4940e..daf452573 100644
--- a/server/controllers/api/videos.js
+++ b/server/controllers/api/videos.js
@@ -110,7 +110,7 @@ function addVideo (req, res, next) {
110 function renameVideoFile (video, callback) { 110 function renameVideoFile (video, callback) {
111 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR 111 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
112 const source = path.join(videoDir, videoFile.filename) 112 const source = path.join(videoDir, videoFile.filename)
113 const destination = path.join(videoDir, video.getFilename()) 113 const destination = path.join(videoDir, video.getVideoFilename())
114 114
115 fs.rename(source, destination, function (err) { 115 fs.rename(source, destination, function (err) {
116 return callback(err, video) 116 return callback(err, video)
@@ -118,7 +118,7 @@ function addVideo (req, res, next) {
118 }, 118 },
119 119
120 function insertIntoDB (video, callback) { 120 function insertIntoDB (video, callback) {
121 video.save(function (err, video, videoFile) { 121 video.save(function (err, video) {
122 // 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)
123 return callback(err, video) 123 return callback(err, video)
124 }) 124 })
diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js
index c4c59808f..166158ef3 100644
--- a/server/helpers/custom-validators/videos.js
+++ b/server/helpers/custom-validators/videos.js
@@ -13,7 +13,7 @@ const videosValidators = {
13 isVideoDateValid, 13 isVideoDateValid,
14 isVideoDescriptionValid, 14 isVideoDescriptionValid,
15 isVideoDurationValid, 15 isVideoDurationValid,
16 isVideoMagnetUriValid, 16 isVideoMagnetValid,
17 isVideoNameValid, 17 isVideoNameValid,
18 isVideoPodUrlValid, 18 isVideoPodUrlValid,
19 isVideoTagsValid, 19 isVideoTagsValid,
@@ -31,7 +31,7 @@ function isEachRemoteVideosValid (requests) {
31 isVideoDateValid(video.createdDate) && 31 isVideoDateValid(video.createdDate) &&
32 isVideoDescriptionValid(video.description) && 32 isVideoDescriptionValid(video.description) &&
33 isVideoDurationValid(video.duration) && 33 isVideoDurationValid(video.duration) &&
34 isVideoMagnetUriValid(video.magnetUri) && 34 isVideoMagnetValid(video.magnetUri) &&
35 isVideoNameValid(video.name) && 35 isVideoNameValid(video.name) &&
36 isVideoPodUrlValid(video.podUrl) && 36 isVideoPodUrlValid(video.podUrl) &&
37 isVideoTagsValid(video.tags) && 37 isVideoTagsValid(video.tags) &&
@@ -62,8 +62,8 @@ function isVideoDurationValid (value) {
62 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) 62 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
63} 63}
64 64
65function isVideoMagnetUriValid (value) { 65function isVideoMagnetValid (value) {
66 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.MAGNET_URI) 66 return validator.isLength(value.infoHash, VIDEOS_CONSTRAINTS_FIELDS.MAGNET.XT)
67} 67}
68 68
69function isVideoNameValid (value) { 69function isVideoNameValid (value) {
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index a50eb2f66..55129fa3e 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -66,7 +66,9 @@ const CONSTRAINTS_FIELDS = {
66 VIDEOS: { 66 VIDEOS: {
67 NAME: { min: 3, max: 50 }, // Length 67 NAME: { min: 3, max: 50 }, // Length
68 DESCRIPTION: { min: 3, max: 250 }, // Length 68 DESCRIPTION: { min: 3, max: 250 }, // Length
69 MAGNET_URI: { min: 10 }, // Length 69 MAGNET: {
70 XT: { min: 10 } // Length
71 },
70 DURATION: { min: 1, max: 7200 }, // Number 72 DURATION: { min: 1, max: 7200 }, // Number
71 TAGS: { min: 1, max: 3 }, // Number of total tags 73 TAGS: { min: 1, max: 3 }, // Number of total tags
72 TAG: { min: 2, max: 10 }, // Length 74 TAG: { min: 2, max: 10 }, // Length
@@ -131,13 +133,18 @@ const REQUEST_ENDPOINTS = {
131 133
132// --------------------------------------------------------------------------- 134// ---------------------------------------------------------------------------
133 135
136const REMOTE_SCHEME = {
137 HTTP: 'https',
138 WS: 'WS'
139}
140
134// Password encryption 141// Password encryption
135const BCRYPT_SALT_SIZE = 10 142const BCRYPT_SALT_SIZE = 10
136 143
137// Express static paths (router) 144// Express static paths (router)
138const STATIC_PATHS = { 145const STATIC_PATHS = {
139 PREVIEWS: '/static/previews', 146 PREVIEWS: '/static/previews/',
140 THUMBNAILS: '/static/thumbnails', 147 THUMBNAILS: '/static/thumbnails/',
141 TORRENTS: '/static/torrents/', 148 TORRENTS: '/static/torrents/',
142 WEBSEED: '/static/webseed/' 149 WEBSEED: '/static/webseed/'
143} 150}
@@ -161,6 +168,8 @@ if (isTestInstance() === true) {
161 CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 168 CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14
162 FRIEND_SCORE.BASE = 20 169 FRIEND_SCORE.BASE = 20
163 REQUESTS_INTERVAL = 10000 170 REQUESTS_INTERVAL = 10000
171 REMOTE_SCHEME.HTTP = 'http'
172 REMOTE_SCHEME.WS = 'ws'
164 STATIC_MAX_AGE = 0 173 STATIC_MAX_AGE = 0
165} 174}
166 175
@@ -177,12 +186,13 @@ module.exports = {
177 OAUTH_LIFETIME, 186 OAUTH_LIFETIME,
178 PAGINATION_COUNT_DEFAULT, 187 PAGINATION_COUNT_DEFAULT,
179 PODS_SCORE, 188 PODS_SCORE,
189 PREVIEWS_SIZE,
190 REMOTE_SCHEME,
180 REQUEST_ENDPOINTS, 191 REQUEST_ENDPOINTS,
181 REQUESTS_IN_PARALLEL, 192 REQUESTS_IN_PARALLEL,
182 REQUESTS_INTERVAL, 193 REQUESTS_INTERVAL,
183 REQUESTS_LIMIT, 194 REQUESTS_LIMIT,
184 RETRY_REQUESTS, 195 RETRY_REQUESTS,
185 PREVIEWS_SIZE,
186 SEARCHABLE_COLUMNS, 196 SEARCHABLE_COLUMNS,
187 SORTABLE_COLUMNS, 197 SORTABLE_COLUMNS,
188 STATIC_MAX_AGE, 198 STATIC_MAX_AGE,
diff --git a/server/models/video.js b/server/models/video.js
index 6cffa87af..19136ba25 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -3,10 +3,10 @@
3const createTorrent = require('create-torrent') 3const createTorrent = require('create-torrent')
4const ffmpeg = require('fluent-ffmpeg') 4const ffmpeg = require('fluent-ffmpeg')
5const fs = require('fs') 5const fs = require('fs')
6const magnetUtil = require('magnet-uri')
6const parallel = require('async/parallel') 7const parallel = require('async/parallel')
7const parseTorrent = require('parse-torrent') 8const parseTorrent = require('parse-torrent')
8const pathUtils = require('path') 9const pathUtils = require('path')
9const magnet = require('magnet-uri')
10const mongoose = require('mongoose') 10const mongoose = require('mongoose')
11 11
12const constants = require('../initializers/constants') 12const constants = require('../initializers/constants')
@@ -25,7 +25,9 @@ const VideoSchema = mongoose.Schema({
25 }, 25 },
26 remoteId: mongoose.Schema.Types.ObjectId, 26 remoteId: mongoose.Schema.Types.ObjectId,
27 description: String, 27 description: String,
28 magnetUri: String, 28 magnet: {
29 infoHash: String
30 },
29 podUrl: String, 31 podUrl: String,
30 author: String, 32 author: String,
31 duration: Number, 33 duration: Number,
@@ -39,7 +41,6 @@ const VideoSchema = mongoose.Schema({
39 41
40VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) 42VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
41VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) 43VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
42VideoSchema.path('magnetUri').validate(customVideosValidators.isVideoMagnetUriValid)
43VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid) 44VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid)
44VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) 45VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
45VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) 46VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
@@ -51,8 +52,10 @@ VideoSchema.path('thumbnail').validate(function (value) {
51VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) 52VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
52 53
53VideoSchema.methods = { 54VideoSchema.methods = {
54 getFilename, 55 generateMagnetUri,
55 getJPEGName, 56 getVideoFilename,
57 getThumbnailName,
58 getPreviewName,
56 getTorrentName, 59 getTorrentName,
57 isOwned, 60 isOwned,
58 toFormatedJSON, 61 toFormatedJSON,
@@ -103,8 +106,8 @@ VideoSchema.pre('save', function (next) {
103 const tasks = [] 106 const tasks = []
104 107
105 if (video.isOwned()) { 108 if (video.isOwned()) {
106 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getFilename()) 109 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
107 this.podUrl = constants.CONFIG.WEBSERVER.URL 110 this.podUrl = constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
108 111
109 tasks.push( 112 tasks.push(
110 // TODO: refractoring 113 // TODO: refractoring
@@ -114,7 +117,7 @@ VideoSchema.pre('save', function (next) {
114 [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ] 117 [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
115 ], 118 ],
116 urlList: [ 119 urlList: [
117 constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getFilename() 120 constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getVideoFilename()
118 ] 121 ]
119 } 122 }
120 123
@@ -125,9 +128,9 @@ VideoSchema.pre('save', function (next) {
125 if (err) return callback(err) 128 if (err) return callback(err)
126 129
127 const parsedTorrent = parseTorrent(torrent) 130 const parsedTorrent = parseTorrent(torrent)
128 parsedTorrent.xs = video.podUrl + constants.STATIC_PATHS.TORRENTS + video.getTorrentName() 131 video.magnet.infoHash = parsedTorrent.infoHash
129 video.magnetUri = magnet.encode(parsedTorrent)
130 132
133 console.log(parsedTorrent)
131 callback(null) 134 callback(null)
132 }) 135 })
133 }) 136 })
@@ -150,16 +153,57 @@ mongoose.model('Video', VideoSchema)
150 153
151// ------------------------------ METHODS ------------------------------ 154// ------------------------------ METHODS ------------------------------
152 155
153function getFilename () { 156function generateMagnetUri () {
154 return this._id + this.extname 157 let baseUrlHttp, baseUrlWs
158
159 if (this.isOwned()) {
160 baseUrlHttp = constants.CONFIG.WEBSERVER.URL
161 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
162 } else {
163 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + this.podUrl
164 baseUrlWs = constants.REMOTE_SCHEME.WS + this.podUrl
165 }
166
167 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
168 const announce = baseUrlWs + '/tracker/socket'
169 const urlList = [ baseUrlHttp + constants.STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
170
171 const magnetHash = {
172 xs,
173 announce,
174 urlList,
175 infoHash: this.magnet.infoHash,
176 name: this.name
177 }
178
179 return magnetUtil.encode(magnetHash)
155} 180}
156 181
157function getJPEGName () { 182function getVideoFilename () {
183 if (this.isOwned()) return this._id + this.extname
184
185 return this.remoteId + this.extname
186}
187
188function getThumbnailName () {
189 // We always have a copy of the thumbnail
158 return this._id + '.jpg' 190 return this._id + '.jpg'
159} 191}
160 192
193function getPreviewName () {
194 const extension = '.jpg'
195
196 if (this.isOwned()) return this._id + extension
197
198 return this.remoteId + extension
199}
200
161function getTorrentName () { 201function getTorrentName () {
162 return this._id + '.torrent' 202 const extension = '.torrent'
203
204 if (this.isOwned()) return this._id + extension
205
206 return this.remoteId + extension
163} 207}
164 208
165function isOwned () { 209function isOwned () {
@@ -171,13 +215,13 @@ function toFormatedJSON () {
171 id: this._id, 215 id: this._id,
172 name: this.name, 216 name: this.name,
173 description: this.description, 217 description: this.description,
174 podUrl: this.podUrl.replace(/^https?:\/\//, ''), 218 podUrl: this.podUrl,
175 isLocal: this.isOwned(), 219 isLocal: this.isOwned(),
176 magnetUri: this.magnetUri, 220 magnetUri: this.generateMagnetUri(),
177 author: this.author, 221 author: this.author,
178 duration: this.duration, 222 duration: this.duration,
179 tags: this.tags, 223 tags: this.tags,
180 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getJPEGName(), 224 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
181 createdDate: this.createdDate 225 createdDate: this.createdDate
182 } 226 }
183 227
@@ -188,7 +232,7 @@ function toRemoteJSON (callback) {
188 const self = this 232 const self = this
189 233
190 // Convert thumbnail to base64 234 // Convert thumbnail to base64
191 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getJPEGName()) 235 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
192 fs.readFile(thumbnailPath, function (err, thumbnailData) { 236 fs.readFile(thumbnailPath, function (err, thumbnailData) {
193 if (err) { 237 if (err) {
194 logger.error('Cannot read the thumbnail of the video') 238 logger.error('Cannot read the thumbnail of the video')
@@ -198,7 +242,7 @@ function toRemoteJSON (callback) {
198 const remoteVideo = { 242 const remoteVideo = {
199 name: self.name, 243 name: self.name,
200 description: self.description, 244 description: self.description,
201 magnetUri: self.magnetUri, 245 magnet: self.magnet,
202 remoteId: self._id, 246 remoteId: self._id,
203 author: self.author, 247 author: self.author,
204 duration: self.duration, 248 duration: self.duration,
@@ -267,11 +311,11 @@ function search (value, field, start, count, sort, callback) {
267// --------------------------------------------------------------------------- 311// ---------------------------------------------------------------------------
268 312
269function removeThumbnail (video, callback) { 313function removeThumbnail (video, callback) {
270 fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.getJPEGName(), callback) 314 fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.getThumbnailName(), callback)
271} 315}
272 316
273function removeFile (video, callback) { 317function removeFile (video, callback) {
274 fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.getFilename(), callback) 318 fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.getVideoFilename(), callback)
275} 319}
276 320
277function removeTorrent (video, callback) { 321function removeTorrent (video, callback) {
@@ -280,22 +324,21 @@ function removeTorrent (video, callback) {
280 324
281function removePreview (video, callback) { 325function removePreview (video, callback) {
282 // Same name than video thumnail 326 // Same name than video thumnail
283 // TODO: refractoring 327 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
284 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getJPEGName(), callback)
285} 328}
286 329
287function createPreview (video, videoPath, callback) { 330function createPreview (video, videoPath, callback) {
288 generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, callback) 331 generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
289} 332}
290 333
291function createThumbnail (video, videoPath, callback) { 334function createThumbnail (video, videoPath, callback) {
292 generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, constants.THUMBNAILS_SIZE, callback) 335 generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), constants.THUMBNAILS_SIZE, callback)
293} 336}
294 337
295function generateThumbnailFromBase64 (video, thumbnailData, callback) { 338function generateThumbnailFromBase64 (video, thumbnailData, callback) {
296 // Creating the thumbnail for this remote video) 339 // Creating the thumbnail for this remote video)
297 340
298 const thumbnailName = video.getJPEGName() 341 const thumbnailName = video.getThumbnailName()
299 const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName 342 const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName
300 fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) { 343 fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) {
301 if (err) return callback(err) 344 if (err) return callback(err)
@@ -304,10 +347,9 @@ function generateThumbnailFromBase64 (video, thumbnailData, callback) {
304 }) 347 })
305} 348}
306 349
307function generateImage (video, videoPath, folder, size, callback) { 350function generateImage (video, videoPath, folder, imageName, size, callback) {
308 const filename = video.getJPEGName()
309 const options = { 351 const options = {
310 filename, 352 filename: imageName,
311 count: 1, 353 count: 1,
312 folder 354 folder
313 } 355 }
@@ -321,7 +363,7 @@ function generateImage (video, videoPath, folder, size, callback) {
321 ffmpeg(videoPath) 363 ffmpeg(videoPath)
322 .on('error', callback) 364 .on('error', callback)
323 .on('end', function () { 365 .on('end', function () {
324 callback(null, filename) 366 callback(null, imageName)
325 }) 367 })
326 .thumbnail(options) 368 .thumbnail(options)
327} 369}