3 const createTorrent
= require('create-torrent')
4 const ffmpeg
= require('fluent-ffmpeg')
5 const fs
= require('fs')
6 const magnetUtil
= require('magnet-uri')
7 const parallel
= require('async/parallel')
8 const parseTorrent
= require('parse-torrent')
9 const pathUtils
= require('path')
10 const mongoose
= require('mongoose')
12 const constants
= require('../initializers/constants')
13 const customVideosValidators
= require('../helpers/custom-validators').videos
14 const logger
= require('../helpers/logger')
15 const modelUtils
= require('./utils')
17 // ---------------------------------------------------------------------------
19 // TODO: add indexes on searchable columns
20 const VideoSchema
= mongoose
.Schema({
24 enum: [ '.mp4', '.webm', '.ogv' ]
26 remoteId: mongoose
.Schema
.Types
.ObjectId
,
41 VideoSchema
.path('name').validate(customVideosValidators
.isVideoNameValid
)
42 VideoSchema
.path('description').validate(customVideosValidators
.isVideoDescriptionValid
)
43 VideoSchema
.path('podHost').validate(customVideosValidators
.isVideoPodHostValid
)
44 VideoSchema
.path('author').validate(customVideosValidators
.isVideoAuthorValid
)
45 VideoSchema
.path('duration').validate(customVideosValidators
.isVideoDurationValid
)
46 VideoSchema
.path('tags').validate(customVideosValidators
.isVideoTagsValid
)
48 VideoSchema
.methods
= {
59 VideoSchema
.statics
= {
60 generateThumbnailFromBase64
,
63 listByHostAndRemoteId
,
72 VideoSchema
.pre('remove', function (next
) {
78 removeThumbnail(video
, callback
)
82 if (video
.isOwned()) {
85 removeFile(video
, callback
)
88 removeTorrent(video
, callback
)
91 removePreview(video
, callback
)
99 VideoSchema
.pre('save', function (next
) {
103 if (video
.isOwned()) {
104 const videoPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.VIDEOS_DIR
, video
.getVideoFilename())
105 this.podHost
= constants
.CONFIG
.WEBSERVER
.HOST
108 // TODO: refractoring
109 function (callback
) {
112 [ constants
.CONFIG
.WEBSERVER
.WS
+ '://' + constants
.CONFIG
.WEBSERVER
.HOSTNAME
+ ':' + constants
.CONFIG
.WEBSERVER
.PORT
+ '/tracker/socket' ]
115 constants
.CONFIG
.WEBSERVER
.URL
+ constants
.STATIC_PATHS
.WEBSEED
+ video
.getVideoFilename()
119 createTorrent(videoPath
, options
, function (err
, torrent
) {
120 if (err
) return callback(err
)
122 fs
.writeFile(constants
.CONFIG
.STORAGE
.TORRENTS_DIR
+ video
.getTorrentName(), torrent
, function (err
) {
123 if (err
) return callback(err
)
125 const parsedTorrent
= parseTorrent(torrent
)
126 video
.magnet
.infoHash
= parsedTorrent
.infoHash
132 function (callback
) {
133 createThumbnail(video
, videoPath
, callback
)
135 function (callback
) {
136 createPreview(video
, videoPath
, callback
)
140 return parallel(tasks
, next
)
146 mongoose
.model('Video', VideoSchema
)
148 // ------------------------------ METHODS ------------------------------
150 function generateMagnetUri () {
151 let baseUrlHttp
, baseUrlWs
153 if (this.isOwned()) {
154 baseUrlHttp
= constants
.CONFIG
.WEBSERVER
.URL
155 baseUrlWs
= constants
.CONFIG
.WEBSERVER
.WS
+ '://' + constants
.CONFIG
.WEBSERVER
.HOSTNAME
+ ':' + constants
.CONFIG
.WEBSERVER
.PORT
157 baseUrlHttp
= constants
.REMOTE_SCHEME
.HTTP
+ '://' + this.podHost
158 baseUrlWs
= constants
.REMOTE_SCHEME
.WS
+ this.podHost
161 const xs
= baseUrlHttp
+ constants
.STATIC_PATHS
.TORRENTS
+ this.getTorrentName()
162 const announce
= baseUrlWs
+ '/tracker/socket'
163 const urlList
= [ baseUrlHttp
+ constants
.STATIC_PATHS
.WEBSEED
+ this.getVideoFilename() ]
169 infoHash: this.magnet
.infoHash
,
173 return magnetUtil
.encode(magnetHash
)
176 function getVideoFilename () {
177 if (this.isOwned()) return this._id
+ this.extname
179 return this.remoteId
+ this.extname
182 function getThumbnailName () {
183 // We always have a copy of the thumbnail
184 return this._id
+ '.jpg'
187 function getPreviewName () {
188 const extension
= '.jpg'
190 if (this.isOwned()) return this._id
+ extension
192 return this.remoteId
+ extension
195 function getTorrentName () {
196 const extension
= '.torrent'
198 if (this.isOwned()) return this._id
+ extension
200 return this.remoteId
+ extension
203 function isOwned () {
204 return this.remoteId
=== null
207 function toFormatedJSON () {
211 description: this.description
,
212 podHost: this.podHost
,
213 isLocal: this.isOwned(),
214 magnetUri: this.generateMagnetUri(),
216 duration: this.duration
,
218 thumbnailPath: constants
.STATIC_PATHS
.THUMBNAILS
+ '/' + this.getThumbnailName(),
219 createdDate: this.createdDate
225 function toRemoteJSON (callback
) {
228 // Convert thumbnail to base64
229 const thumbnailPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
, this.getThumbnailName())
230 fs
.readFile(thumbnailPath
, function (err
, thumbnailData
) {
232 logger
.error('Cannot read the thumbnail of the video')
236 const remoteVideo
= {
238 description: self
.description
,
242 duration: self
.duration
,
243 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
245 createdDate: self
.createdDate
,
246 podHost: self
.podHost
249 return callback(null, remoteVideo
)
253 // ------------------------------ STATICS ------------------------------
255 function generateThumbnailFromBase64 (video
, thumbnailData
, callback
) {
256 // Creating the thumbnail for a remote video
258 const thumbnailName
= video
.getThumbnailName()
259 const thumbnailPath
= constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ thumbnailName
260 fs
.writeFile(thumbnailPath
, thumbnailData
, { encoding: 'base64' }, function (err
) {
261 if (err
) return callback(err
)
263 return callback(null, thumbnailName
)
267 function getDurationFromFile (videoPath
, callback
) {
268 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
269 if (err
) return callback(err
)
271 return callback(null, Math
.floor(metadata
.format
.duration
))
275 function listForApi (start
, count
, sort
, callback
) {
277 return modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
280 function listByHostAndRemoteId (fromHost
, remoteId
, callback
) {
281 this.find({ podHost: fromHost
, remoteId: remoteId
}, callback
)
284 function listByHost (fromHost
, callback
) {
285 this.find({ podHost: fromHost
}, callback
)
288 function listOwned (callback
) {
289 // If remoteId is null this is *our* video
290 this.find({ remoteId: null }, callback
)
293 function listOwnedByAuthor (author
, callback
) {
294 this.find({ remoteId: null, author: author
}, callback
)
297 function listRemotes (callback
) {
298 this.find({ remoteId: { $ne: null } }, callback
)
301 function load (id
, callback
) {
302 this.findById(id
, callback
)
305 function search (value
, field
, start
, count
, sort
, callback
) {
307 // Make an exact search with the magnet
308 if (field
=== 'magnetUri') {
309 const infoHash
= magnetUtil
.decode(value
).infoHash
313 } else if (field
=== 'tags') {
316 query
[field
] = new RegExp(value
, 'i')
319 modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
322 // ---------------------------------------------------------------------------
324 function removeThumbnail (video
, callback
) {
325 fs
.unlink(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ video
.getThumbnailName(), callback
)
328 function removeFile (video
, callback
) {
329 fs
.unlink(constants
.CONFIG
.STORAGE
.VIDEOS_DIR
+ video
.getVideoFilename(), callback
)
332 function removeTorrent (video
, callback
) {
333 fs
.unlink(constants
.CONFIG
.STORAGE
.TORRENTS_DIR
+ video
.getTorrentName(), callback
)
336 function removePreview (video
, callback
) {
337 // Same name than video thumnail
338 fs
.unlink(constants
.CONFIG
.STORAGE
.PREVIEWS_DIR
+ video
.getPreviewName(), callback
)
341 function createPreview (video
, videoPath
, callback
) {
342 generateImage(video
, videoPath
, constants
.CONFIG
.STORAGE
.PREVIEWS_DIR
, video
.getPreviewName(), callback
)
345 function createThumbnail (video
, videoPath
, callback
) {
346 generateImage(video
, videoPath
, constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
, video
.getThumbnailName(), constants
.THUMBNAILS_SIZE
, callback
)
349 function generateImage (video
, videoPath
, folder
, imageName
, size
, callback
) {
363 .on('error', callback
)
364 .on('end', function () {
365 callback(null, imageName
)