]>
git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video.js
3 const async
= require('async')
4 const config
= require('config')
5 const ffmpeg
= require('fluent-ffmpeg')
6 const fs
= require('fs')
7 const pathUtils
= require('path')
8 const mongoose
= require('mongoose')
10 const constants
= require('../initializers/constants')
11 const customValidators
= require('../helpers/customValidators')
12 const logger
= require('../helpers/logger')
13 const utils
= require('../helpers/utils')
14 const webtorrent
= require('../lib/webtorrent')
16 const http
= config
.get('webserver.https') === true ? 'https' : 'http'
17 const host
= config
.get('webserver.host')
18 const port
= config
.get('webserver.port')
19 const uploadsDir
= pathUtils
.join(__dirname
, '..', '..', config
.get('storage.uploads'))
20 const thumbnailsDir
= pathUtils
.join(__dirname
, '..', '..', config
.get('storage.thumbnails'))
22 // ---------------------------------------------------------------------------
24 // TODO: add indexes on searchable columns
25 const VideoSchema
= mongoose
.Schema({
41 VideoSchema
.path('name').validate(customValidators
.isVideoNameValid
)
42 VideoSchema
.path('description').validate(customValidators
.isVideoDescriptionValid
)
43 VideoSchema
.path('magnetUri').validate(customValidators
.isVideoMagnetUriValid
)
44 VideoSchema
.path('podUrl').validate(customValidators
.isVideoPodUrlValid
)
45 VideoSchema
.path('author').validate(customValidators
.isVideoAuthorValid
)
46 VideoSchema
.path('duration').validate(customValidators
.isVideoDurationValid
)
47 // The tumbnail can be the path or the data in base 64
48 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
49 VideoSchema
.path('thumbnail').validate(function (value
) {
50 return customValidators
.isVideoThumbnailValid(value
) || customValidators
.isVideoThumbnail64Valid(value
)
52 VideoSchema
.path('tags').validate(customValidators
.isVideoTagsValid
)
54 VideoSchema
.methods
= {
56 toFormatedJSON: toFormatedJSON
,
57 toRemoteJSON: toRemoteJSON
60 VideoSchema
.statics
= {
61 getDurationFromFile: getDurationFromFile
,
63 listByUrlAndMagnet: listByUrlAndMagnet
,
64 listByUrls: listByUrls
,
66 listRemotes: listRemotes
,
69 seedAllExisting: seedAllExisting
72 VideoSchema
.pre('remove', function (next
) {
78 removeThumbnail(video
, callback
)
82 if (video
.isOwned()) {
85 removeFile(video
, callback
)
88 removeTorrent(video
, callback
)
93 async
.parallel(tasks
, next
)
96 VideoSchema
.pre('save', function (next
) {
100 if (video
.isOwned()) {
101 const videoPath
= pathUtils
.join(uploadsDir
, video
.filename
)
102 this.podUrl
= http
+ '://' + host
+ ':' + port
105 function (callback
) {
106 seed(videoPath
, callback
)
108 function (callback
) {
109 createThumbnail(videoPath
, callback
)
113 async
.parallel(tasks
, function (err
, results
) {
114 if (err
) return next(err
)
116 video
.magnetUri
= results
[0].magnetURI
117 video
.thumbnail
= results
[1]
122 generateThumbnailFromBase64(video
.thumbnail
, function (err
, thumbnailName
) {
123 if (err
) return next(err
)
125 video
.thumbnail
= thumbnailName
132 mongoose
.model('Video', VideoSchema
)
134 // ------------------------------ METHODS ------------------------------
136 function isOwned () {
137 return this.filename
!== null
140 function toFormatedJSON () {
144 description: this.description
,
145 podUrl: this.podUrl
.replace(/^https?:\/\//, ''),
146 isLocal: this.isOwned(),
147 magnetUri: this.magnetUri
,
149 duration: this.duration
,
151 thumbnailPath: constants
.THUMBNAILS_STATIC_PATH
+ '/' + this.thumbnail
,
152 createdDate: this.createdDate
158 function toRemoteJSON (callback
) {
161 // Convert thumbnail to base64
162 fs
.readFile(pathUtils
.join(thumbnailsDir
, this.thumbnail
), function (err
, thumbnailData
) {
164 logger
.error('Cannot read the thumbnail of the video')
168 const remoteVideo
= {
170 description: self
.description
,
171 magnetUri: self
.magnetUri
,
174 duration: self
.duration
,
175 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
177 createdDate: self
.createdDate
,
181 return callback(null, remoteVideo
)
185 // ------------------------------ STATICS ------------------------------
187 function getDurationFromFile (videoPath
, callback
) {
188 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
189 if (err
) return callback(err
)
191 return callback(null, Math
.floor(metadata
.format
.duration
))
195 function list (start
, count
, sort
, callback
) {
197 return findWithCount
.call(this, query
, start
, count
, sort
, callback
)
200 function listByUrlAndMagnet (fromUrl
, magnetUri
, callback
) {
201 this.find({ podUrl: fromUrl
, magnetUri: magnetUri
}, callback
)
204 function listByUrls (fromUrls
, callback
) {
205 this.find({ podUrl: { $in: fromUrls
} }, callback
)
208 function listOwned (callback
) {
209 // If filename is not null this is *our* video
210 this.find({ filename: { $ne: null } }, callback
)
213 function listRemotes (callback
) {
214 this.find({ filename: null }, callback
)
217 function load (id
, callback
) {
218 this.findById(id
, callback
)
221 function search (value
, field
, start
, count
, sort
, callback
) {
223 // Make an exact search with the magnet
224 if (field
=== 'magnetUri' || field
=== 'tags') {
227 query
[field
] = new RegExp(value
)
230 findWithCount
.call(this, query
, start
, count
, sort
, callback
)
233 function seedAllExisting (callback
) {
234 listOwned
.call(this, function (err
, videos
) {
235 if (err
) return callback(err
)
237 async
.each(videos
, function (video
, callbackEach
) {
238 const videoPath
= pathUtils
.join(uploadsDir
, video
.filename
)
239 seed(videoPath
, callbackEach
)
244 // ---------------------------------------------------------------------------
246 function findWithCount (query
, start
, count
, sort
, callback
) {
250 function (asyncCallback
) {
251 self
.find(query
).skip(start
).limit(start
+ count
).sort(sort
).exec(asyncCallback
)
253 function (asyncCallback
) {
254 self
.count(query
, asyncCallback
)
256 ], function (err
, results
) {
257 if (err
) return callback(err
)
259 const videos
= results
[0]
260 const totalVideos
= results
[1]
261 return callback(null, videos
, totalVideos
)
265 function removeThumbnail (video
, callback
) {
266 fs
.unlink(thumbnailsDir
+ video
.thumbnail
, callback
)
269 function removeFile (video
, callback
) {
270 fs
.unlink(uploadsDir
+ video
.filename
, callback
)
273 // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
274 function removeTorrent (video
, callback
) {
276 webtorrent
.remove(video
.magnetUri
, callback
)
278 logger
.warn('Cannot remove the torrent from WebTorrent', { err: err
})
279 return callback(null)
283 function createThumbnail (videoPath
, callback
) {
284 const filename
= pathUtils
.basename(videoPath
) + '.jpg'
286 .on('error', callback
)
287 .on('end', function () {
288 callback(null, filename
)
292 folder: thumbnailsDir
,
293 size: constants
.THUMBNAILS_SIZE
,
298 function seed (path
, callback
) {
299 logger
.info('Seeding %s...', path
)
301 webtorrent
.seed(path
, function (torrent
) {
302 logger
.info('%s seeded (%s).', path
, torrent
.magnetURI
)
304 return callback(null, torrent
)
308 function generateThumbnailFromBase64 (data
, callback
) {
309 // Creating the thumbnail for this remote video
310 utils
.generateRandomString(16, function (err
, randomString
) {
311 if (err
) return callback(err
)
313 const thumbnailName
= randomString
+ '.jpg'
314 fs
.writeFile(thumbnailsDir
+ thumbnailName
, data
, { encoding: 'base64' }, function (err
) {
315 if (err
) return callback(err
)
317 return callback(null, thumbnailName
)