]>
git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video.js
3 const eachLimit
= require('async/eachLimit')
4 const ffmpeg
= require('fluent-ffmpeg')
5 const fs
= require('fs')
6 const parallel
= require('async/parallel')
7 const pathUtils
= require('path')
8 const mongoose
= require('mongoose')
10 const constants
= require('../initializers/constants')
11 const customVideosValidators
= require('../helpers/custom-validators').videos
12 const logger
= require('../helpers/logger')
13 const modelUtils
= require('./utils')
14 const utils
= require('../helpers/utils')
15 const webtorrent
= require('../lib/webtorrent')
17 // ---------------------------------------------------------------------------
19 // TODO: add indexes on searchable columns
20 const VideoSchema
= mongoose
.Schema({
36 VideoSchema
.path('name').validate(customVideosValidators
.isVideoNameValid
)
37 VideoSchema
.path('description').validate(customVideosValidators
.isVideoDescriptionValid
)
38 VideoSchema
.path('magnetUri').validate(customVideosValidators
.isVideoMagnetUriValid
)
39 VideoSchema
.path('podUrl').validate(customVideosValidators
.isVideoPodUrlValid
)
40 VideoSchema
.path('author').validate(customVideosValidators
.isVideoAuthorValid
)
41 VideoSchema
.path('duration').validate(customVideosValidators
.isVideoDurationValid
)
42 // The tumbnail can be the path or the data in base 64
43 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
44 VideoSchema
.path('thumbnail').validate(function (value
) {
45 return customVideosValidators
.isVideoThumbnailValid(value
) || customVideosValidators
.isVideoThumbnail64Valid(value
)
47 VideoSchema
.path('tags').validate(customVideosValidators
.isVideoTagsValid
)
49 VideoSchema
.methods
= {
55 VideoSchema
.statics
= {
68 VideoSchema
.pre('remove', function (next
) {
74 removeThumbnail(video
, callback
)
78 if (video
.isOwned()) {
81 removeFile(video
, callback
)
84 removeTorrent(video
, callback
)
92 VideoSchema
.pre('save', function (next
) {
96 if (video
.isOwned()) {
97 const videoPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.UPLOAD_DIR
, video
.filename
)
98 this.podUrl
= constants
.CONFIG
.WEBSERVER
.URL
101 function (callback
) {
102 seed(videoPath
, callback
)
104 function (callback
) {
105 createThumbnail(videoPath
, callback
)
109 parallel(tasks
, function (err
, results
) {
110 if (err
) return next(err
)
112 video
.magnetUri
= results
[0].magnetURI
113 video
.thumbnail
= results
[1]
118 generateThumbnailFromBase64(video
.thumbnail
, function (err
, thumbnailName
) {
119 if (err
) return next(err
)
121 video
.thumbnail
= thumbnailName
128 mongoose
.model('Video', VideoSchema
)
130 // ------------------------------ METHODS ------------------------------
132 function isOwned () {
133 return this.filename
!== null
136 function toFormatedJSON () {
140 description: this.description
,
141 podUrl: this.podUrl
.replace(/^https?:\/\//, ''),
142 isLocal: this.isOwned(),
143 magnetUri: this.magnetUri
,
145 duration: this.duration
,
147 thumbnailPath: constants
.THUMBNAILS_STATIC_PATH
+ '/' + this.thumbnail
,
148 createdDate: this.createdDate
154 function toRemoteJSON (callback
) {
157 // Convert thumbnail to base64
158 fs
.readFile(pathUtils
.join(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
, this.thumbnail
), function (err
, thumbnailData
) {
160 logger
.error('Cannot read the thumbnail of the video')
164 const remoteVideo
= {
166 description: self
.description
,
167 magnetUri: self
.magnetUri
,
170 duration: self
.duration
,
171 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
173 createdDate: self
.createdDate
,
177 return callback(null, remoteVideo
)
181 // ------------------------------ STATICS ------------------------------
183 function getDurationFromFile (videoPath
, callback
) {
184 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
185 if (err
) return callback(err
)
187 return callback(null, Math
.floor(metadata
.format
.duration
))
191 function listForApi (start
, count
, sort
, callback
) {
193 return modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
196 function listByUrlAndMagnet (fromUrl
, magnetUri
, callback
) {
197 this.find({ podUrl: fromUrl
, magnetUri: magnetUri
}, callback
)
200 function listByUrls (fromUrls
, callback
) {
201 this.find({ podUrl: { $in: fromUrls
} }, callback
)
204 function listOwned (callback
) {
205 // If filename is not null this is *our* video
206 this.find({ filename: { $ne: null } }, callback
)
209 function listOwnedByAuthor (author
, callback
) {
210 this.find({ filename: { $ne: null }, author: author
}, 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 modelUtils
.listForApiWithCount
.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 eachLimit(videos
, constants
.SEEDS_IN_PARALLEL
, function (video
, callbackEach
) {
238 const videoPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.UPLOAD_DIR
, video
.filename
)
239 seed(videoPath
, callbackEach
)
244 // ---------------------------------------------------------------------------
246 function removeThumbnail (video
, callback
) {
247 fs
.unlink(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ video
.thumbnail
, callback
)
250 function removeFile (video
, callback
) {
251 fs
.unlink(constants
.CONFIG
.STORAGE
.UPLOAD_DIR
+ video
.filename
, callback
)
254 // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
255 function removeTorrent (video
, callback
) {
257 webtorrent
.remove(video
.magnetUri
, callback
)
259 logger
.warn('Cannot remove the torrent from WebTorrent', { err: err
})
260 return callback(null)
264 function createThumbnail (videoPath
, callback
) {
265 const filename
= pathUtils
.basename(videoPath
) + '.jpg'
267 .on('error', callback
)
268 .on('end', function () {
269 callback(null, filename
)
273 folder: constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
,
274 size: constants
.THUMBNAILS_SIZE
,
279 function seed (path
, callback
) {
280 logger
.info('Seeding %s...', path
)
282 webtorrent
.seed(path
, function (torrent
) {
283 logger
.info('%s seeded (%s).', path
, torrent
.magnetURI
)
285 return callback(null, torrent
)
289 function generateThumbnailFromBase64 (data
, callback
) {
290 // Creating the thumbnail for this remote video
291 utils
.generateRandomString(16, function (err
, randomString
) {
292 if (err
) return callback(err
)
294 const thumbnailName
= randomString
+ '.jpg'
295 fs
.writeFile(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ thumbnailName
, data
, { encoding: 'base64' }, function (err
) {
296 if (err
) return callback(err
)
298 return callback(null, thumbnailName
)