]>
git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video.js
3 const config
= require('config')
4 const eachLimit
= require('async/eachLimit')
5 const ffmpeg
= require('fluent-ffmpeg')
6 const fs
= require('fs')
7 const parallel
= require('async/parallel')
8 const pathUtils
= require('path')
9 const mongoose
= require('mongoose')
11 const constants
= require('../initializers/constants')
12 const customVideosValidators
= require('../helpers/custom-validators').videos
13 const logger
= require('../helpers/logger')
14 const modelUtils
= require('./utils')
15 const utils
= require('../helpers/utils')
16 const webtorrent
= require('../lib/webtorrent')
18 const http
= config
.get('webserver.https') === true ? 'https' : 'http'
19 const host
= config
.get('webserver.host')
20 const port
= config
.get('webserver.port')
21 const uploadsDir
= pathUtils
.join(__dirname
, '..', '..', config
.get('storage.uploads'))
22 const thumbnailsDir
= pathUtils
.join(__dirname
, '..', '..', config
.get('storage.thumbnails'))
24 // ---------------------------------------------------------------------------
26 // TODO: add indexes on searchable columns
27 const VideoSchema
= mongoose
.Schema({
43 VideoSchema
.path('name').validate(customVideosValidators
.isVideoNameValid
)
44 VideoSchema
.path('description').validate(customVideosValidators
.isVideoDescriptionValid
)
45 VideoSchema
.path('magnetUri').validate(customVideosValidators
.isVideoMagnetUriValid
)
46 VideoSchema
.path('podUrl').validate(customVideosValidators
.isVideoPodUrlValid
)
47 VideoSchema
.path('author').validate(customVideosValidators
.isVideoAuthorValid
)
48 VideoSchema
.path('duration').validate(customVideosValidators
.isVideoDurationValid
)
49 // The tumbnail can be the path or the data in base 64
50 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
51 VideoSchema
.path('thumbnail').validate(function (value
) {
52 return customVideosValidators
.isVideoThumbnailValid(value
) || customVideosValidators
.isVideoThumbnail64Valid(value
)
54 VideoSchema
.path('tags').validate(customVideosValidators
.isVideoTagsValid
)
56 VideoSchema
.methods
= {
58 toFormatedJSON: toFormatedJSON
,
59 toRemoteJSON: toRemoteJSON
62 VideoSchema
.statics
= {
63 getDurationFromFile: getDurationFromFile
,
64 listForApi: listForApi
,
65 listByUrlAndMagnet: listByUrlAndMagnet
,
66 listByUrls: listByUrls
,
68 listOwnedByAuthor: listOwnedByAuthor
,
69 listRemotes: listRemotes
,
72 seedAllExisting: seedAllExisting
75 VideoSchema
.pre('remove', function (next
) {
81 removeThumbnail(video
, callback
)
85 if (video
.isOwned()) {
88 removeFile(video
, callback
)
91 removeTorrent(video
, callback
)
99 VideoSchema
.pre('save', function (next
) {
103 if (video
.isOwned()) {
104 const videoPath
= pathUtils
.join(uploadsDir
, video
.filename
)
105 this.podUrl
= http
+ '://' + host
+ ':' + port
108 function (callback
) {
109 seed(videoPath
, callback
)
111 function (callback
) {
112 createThumbnail(videoPath
, callback
)
116 parallel(tasks
, function (err
, results
) {
117 if (err
) return next(err
)
119 video
.magnetUri
= results
[0].magnetURI
120 video
.thumbnail
= results
[1]
125 generateThumbnailFromBase64(video
.thumbnail
, function (err
, thumbnailName
) {
126 if (err
) return next(err
)
128 video
.thumbnail
= thumbnailName
135 mongoose
.model('Video', VideoSchema
)
137 // ------------------------------ METHODS ------------------------------
139 function isOwned () {
140 return this.filename
!== null
143 function toFormatedJSON () {
147 description: this.description
,
148 podUrl: this.podUrl
.replace(/^https?:\/\//, ''),
149 isLocal: this.isOwned(),
150 magnetUri: this.magnetUri
,
152 duration: this.duration
,
154 thumbnailPath: constants
.THUMBNAILS_STATIC_PATH
+ '/' + this.thumbnail
,
155 createdDate: this.createdDate
161 function toRemoteJSON (callback
) {
164 // Convert thumbnail to base64
165 fs
.readFile(pathUtils
.join(thumbnailsDir
, this.thumbnail
), function (err
, thumbnailData
) {
167 logger
.error('Cannot read the thumbnail of the video')
171 const remoteVideo
= {
173 description: self
.description
,
174 magnetUri: self
.magnetUri
,
177 duration: self
.duration
,
178 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
180 createdDate: self
.createdDate
,
184 return callback(null, remoteVideo
)
188 // ------------------------------ STATICS ------------------------------
190 function getDurationFromFile (videoPath
, callback
) {
191 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
192 if (err
) return callback(err
)
194 return callback(null, Math
.floor(metadata
.format
.duration
))
198 function listForApi (start
, count
, sort
, callback
) {
200 return modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
203 function listByUrlAndMagnet (fromUrl
, magnetUri
, callback
) {
204 this.find({ podUrl: fromUrl
, magnetUri: magnetUri
}, callback
)
207 function listByUrls (fromUrls
, callback
) {
208 this.find({ podUrl: { $in: fromUrls
} }, callback
)
211 function listOwned (callback
) {
212 // If filename is not null this is *our* video
213 this.find({ filename: { $ne: null } }, callback
)
216 function listOwnedByAuthor (author
, callback
) {
217 this.find({ filename: { $ne: null }, author: author
}, callback
)
220 function listRemotes (callback
) {
221 this.find({ filename: null }, callback
)
224 function load (id
, callback
) {
225 this.findById(id
, callback
)
228 function search (value
, field
, start
, count
, sort
, callback
) {
230 // Make an exact search with the magnet
231 if (field
=== 'magnetUri' || field
=== 'tags') {
234 query
[field
] = new RegExp(value
)
237 modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
240 function seedAllExisting (callback
) {
241 listOwned
.call(this, function (err
, videos
) {
242 if (err
) return callback(err
)
244 eachLimit(videos
, constants
.SEEDS_IN_PARALLEL
, function (video
, callbackEach
) {
245 const videoPath
= pathUtils
.join(uploadsDir
, video
.filename
)
246 seed(videoPath
, callbackEach
)
251 // ---------------------------------------------------------------------------
253 function removeThumbnail (video
, callback
) {
254 fs
.unlink(thumbnailsDir
+ video
.thumbnail
, callback
)
257 function removeFile (video
, callback
) {
258 fs
.unlink(uploadsDir
+ video
.filename
, callback
)
261 // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
262 function removeTorrent (video
, callback
) {
264 webtorrent
.remove(video
.magnetUri
, callback
)
266 logger
.warn('Cannot remove the torrent from WebTorrent', { err: err
})
267 return callback(null)
271 function createThumbnail (videoPath
, callback
) {
272 const filename
= pathUtils
.basename(videoPath
) + '.jpg'
274 .on('error', callback
)
275 .on('end', function () {
276 callback(null, filename
)
280 folder: thumbnailsDir
,
281 size: constants
.THUMBNAILS_SIZE
,
286 function seed (path
, callback
) {
287 logger
.info('Seeding %s...', path
)
289 webtorrent
.seed(path
, function (torrent
) {
290 logger
.info('%s seeded (%s).', path
, torrent
.magnetURI
)
292 return callback(null, torrent
)
296 function generateThumbnailFromBase64 (data
, callback
) {
297 // Creating the thumbnail for this remote video
298 utils
.generateRandomString(16, function (err
, randomString
) {
299 if (err
) return callback(err
)
301 const thumbnailName
= randomString
+ '.jpg'
302 fs
.writeFile(thumbnailsDir
+ thumbnailName
, data
, { encoding: 'base64' }, function (err
) {
303 if (err
) return callback(err
)
305 return callback(null, thumbnailName
)