]>
git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video.js
3 const config
= require('config')
4 const createTorrent
= require('create-torrent')
5 const ffmpeg
= require('fluent-ffmpeg')
6 const fs
= require('fs')
7 const parallel
= require('async/parallel')
8 const parseTorrent
= require('parse-torrent')
9 const pathUtils
= require('path')
10 const magnet
= require('magnet-uri')
11 const mongoose
= require('mongoose')
13 const constants
= require('../initializers/constants')
14 const customValidators
= require('../helpers/custom-validators')
15 const logger
= require('../helpers/logger')
16 const utils
= require('../helpers/utils')
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'))
23 const torrentsDir
= pathUtils
.join(__dirname
, '..', '..', config
.get('storage.torrents'))
24 const webseedBaseUrl
= http
+ '://' + host
+ ':' + port
+ constants
.STATIC_PATHS
.WEBSEED
26 // ---------------------------------------------------------------------------
28 // TODO: add indexes on searchable columns
29 const VideoSchema
= mongoose
.Schema({
45 VideoSchema
.path('name').validate(customValidators
.isVideoNameValid
)
46 VideoSchema
.path('description').validate(customValidators
.isVideoDescriptionValid
)
47 VideoSchema
.path('magnetUri').validate(customValidators
.isVideoMagnetUriValid
)
48 VideoSchema
.path('podUrl').validate(customValidators
.isVideoPodUrlValid
)
49 VideoSchema
.path('author').validate(customValidators
.isVideoAuthorValid
)
50 VideoSchema
.path('duration').validate(customValidators
.isVideoDurationValid
)
51 // The tumbnail can be the path or the data in base 64
52 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
53 VideoSchema
.path('thumbnail').validate(function (value
) {
54 return customValidators
.isVideoThumbnailValid(value
) || customValidators
.isVideoThumbnail64Valid(value
)
56 VideoSchema
.path('tags').validate(customValidators
.isVideoTagsValid
)
58 VideoSchema
.methods
= {
60 toFormatedJSON: toFormatedJSON
,
61 toRemoteJSON: toRemoteJSON
64 VideoSchema
.statics
= {
65 getDurationFromFile: getDurationFromFile
,
67 listByUrlAndMagnet: listByUrlAndMagnet
,
68 listByUrls: listByUrls
,
70 listRemotes: listRemotes
,
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 // TODO: refractoring
109 function (callback
) {
110 createTorrent(videoPath
, { announceList: [ [ 'ws://' + host
+ ':' + port
+ '/tracker/socket' ] ], urlList: [ webseedBaseUrl
+ video
.filename
] }, function (err
, torrent
) {
111 if (err
) return callback(err
)
113 fs
.writeFile(torrentsDir
+ video
.filename
+ '.torrent', torrent
, function (err
) {
114 if (err
) return callback(err
)
116 const parsedTorrent
= parseTorrent(torrent
)
117 parsedTorrent
.xs
= video
.podUrl
+ constants
.STATIC_PATHS
.TORRENTS
+ video
.filename
+ '.torrent'
118 video
.magnetUri
= magnet
.encode(parsedTorrent
)
124 function (callback
) {
125 createThumbnail(videoPath
, callback
)
129 parallel(tasks
, function (err
, results
) {
130 if (err
) return next(err
)
132 video
.thumbnail
= results
[1]
137 generateThumbnailFromBase64(video
.thumbnail
, function (err
, thumbnailName
) {
138 if (err
) return next(err
)
140 video
.thumbnail
= thumbnailName
147 mongoose
.model('Video', VideoSchema
)
149 // ------------------------------ METHODS ------------------------------
151 function isOwned () {
152 return this.filename
!== null
155 function toFormatedJSON () {
159 description: this.description
,
160 podUrl: this.podUrl
.replace(/^https?:\/\//, ''),
161 isLocal: this.isOwned(),
162 magnetUri: this.magnetUri
,
164 duration: this.duration
,
166 thumbnailPath: constants
.STATIC_PATHS
.THUMBNAILS
+ '/' + this.thumbnail
,
167 createdDate: this.createdDate
173 function toRemoteJSON (callback
) {
176 // Convert thumbnail to base64
177 fs
.readFile(pathUtils
.join(thumbnailsDir
, this.thumbnail
), function (err
, thumbnailData
) {
179 logger
.error('Cannot read the thumbnail of the video')
183 const remoteVideo
= {
185 description: self
.description
,
186 magnetUri: self
.magnetUri
,
189 duration: self
.duration
,
190 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
192 createdDate: self
.createdDate
,
196 return callback(null, remoteVideo
)
200 // ------------------------------ STATICS ------------------------------
202 function getDurationFromFile (videoPath
, callback
) {
203 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
204 if (err
) return callback(err
)
206 return callback(null, Math
.floor(metadata
.format
.duration
))
210 function list (start
, count
, sort
, callback
) {
212 return findWithCount
.call(this, query
, start
, count
, sort
, callback
)
215 function listByUrlAndMagnet (fromUrl
, magnetUri
, callback
) {
216 this.find({ podUrl: fromUrl
, magnetUri: magnetUri
}, callback
)
219 function listByUrls (fromUrls
, callback
) {
220 this.find({ podUrl: { $in: fromUrls
} }, callback
)
223 function listOwned (callback
) {
224 // If filename is not null this is *our* video
225 this.find({ filename: { $ne: null } }, callback
)
228 function listRemotes (callback
) {
229 this.find({ filename: null }, callback
)
232 function load (id
, callback
) {
233 this.findById(id
, callback
)
236 function search (value
, field
, start
, count
, sort
, callback
) {
238 // Make an exact search with the magnet
239 if (field
=== 'magnetUri' || field
=== 'tags') {
242 query
[field
] = new RegExp(value
)
245 findWithCount
.call(this, query
, start
, count
, sort
, callback
)
248 // ---------------------------------------------------------------------------
250 function findWithCount (query
, start
, count
, sort
, callback
) {
254 function (asyncCallback
) {
255 self
.find(query
).skip(start
).limit(count
).sort(sort
).exec(asyncCallback
)
257 function (asyncCallback
) {
258 self
.count(query
, asyncCallback
)
260 ], function (err
, results
) {
261 if (err
) return callback(err
)
263 const videos
= results
[0]
264 const totalVideos
= results
[1]
265 return callback(null, videos
, totalVideos
)
269 function removeThumbnail (video
, callback
) {
270 fs
.unlink(thumbnailsDir
+ video
.thumbnail
, callback
)
273 function removeFile (video
, callback
) {
274 fs
.unlink(uploadsDir
+ video
.filename
, callback
)
277 // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
278 function removeTorrent (video
, callback
) {
279 fs
.unlink(torrentsDir
+ video
.filename
+ '.torrent')
282 function createThumbnail (videoPath
, callback
) {
283 const filename
= pathUtils
.basename(videoPath
) + '.jpg'
285 .on('error', callback
)
286 .on('end', function () {
287 callback(null, filename
)
291 folder: thumbnailsDir
,
292 size: constants
.THUMBNAILS_SIZE
,
297 function generateThumbnailFromBase64 (data
, callback
) {
298 // Creating the thumbnail for this remote video
299 utils
.generateRandomString(16, function (err
, randomString
) {
300 if (err
) return callback(err
)
302 const thumbnailName
= randomString
+ '.jpg'
303 fs
.writeFile(thumbnailsDir
+ thumbnailName
, data
, { encoding: 'base64' }, function (err
) {
304 if (err
) return callback(err
)
306 return callback(null, thumbnailName
)