]>
git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video.js
3 const createTorrent
= require('create-torrent')
4 const ffmpeg
= require('fluent-ffmpeg')
5 const fs
= require('fs')
6 const parallel
= require('async/parallel')
7 const parseTorrent
= require('parse-torrent')
8 const pathUtils
= require('path')
9 const magnet
= require('magnet-uri')
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
,
40 VideoSchema
.path('name').validate(customVideosValidators
.isVideoNameValid
)
41 VideoSchema
.path('description').validate(customVideosValidators
.isVideoDescriptionValid
)
42 VideoSchema
.path('magnetUri').validate(customVideosValidators
.isVideoMagnetUriValid
)
43 VideoSchema
.path('podUrl').validate(customVideosValidators
.isVideoPodUrlValid
)
44 VideoSchema
.path('author').validate(customVideosValidators
.isVideoAuthorValid
)
45 VideoSchema
.path('duration').validate(customVideosValidators
.isVideoDurationValid
)
46 // The tumbnail can be the path or the data in base 64
47 // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
48 VideoSchema
.path('thumbnail').validate(function (value
) {
49 return customVideosValidators
.isVideoThumbnailValid(value
) || customVideosValidators
.isVideoThumbnail64Valid(value
)
51 VideoSchema
.path('tags').validate(customVideosValidators
.isVideoTagsValid
)
53 VideoSchema
.methods
= {
62 VideoSchema
.statics
= {
74 VideoSchema
.pre('remove', function (next
) {
80 removeThumbnail(video
, callback
)
84 if (video
.isOwned()) {
87 removeFile(video
, callback
)
90 removeTorrent(video
, callback
)
93 removePreview(video
, callback
)
101 VideoSchema
.pre('save', function (next
) {
105 if (video
.isOwned()) {
106 const videoPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.VIDEOS_DIR
, video
.getFilename())
107 this.podUrl
= constants
.CONFIG
.WEBSERVER
.URL
110 // TODO: refractoring
111 function (callback
) {
114 [ constants
.CONFIG
.WEBSERVER
.WS
+ '://' + constants
.CONFIG
.WEBSERVER
.HOSTNAME
+ ':' + constants
.CONFIG
.WEBSERVER
.PORT
+ '/tracker/socket' ]
117 constants
.CONFIG
.WEBSERVER
.URL
+ constants
.STATIC_PATHS
.WEBSEED
+ video
.getFilename()
121 createTorrent(videoPath
, options
, function (err
, torrent
) {
122 if (err
) return callback(err
)
124 fs
.writeFile(constants
.CONFIG
.STORAGE
.TORRENTS_DIR
+ video
.getTorrentName(), torrent
, function (err
) {
125 if (err
) return callback(err
)
127 const parsedTorrent
= parseTorrent(torrent
)
128 parsedTorrent
.xs
= video
.podUrl
+ constants
.STATIC_PATHS
.TORRENTS
+ video
.getTorrentName()
129 video
.magnetUri
= magnet
.encode(parsedTorrent
)
135 function (callback
) {
136 createThumbnail(video
, videoPath
, callback
)
138 function (callback
) {
139 createPreview(video
, videoPath
, callback
)
143 parallel(tasks
, next
)
145 generateThumbnailFromBase64(video
, video
.thumbnail
, next
)
149 mongoose
.model('Video', VideoSchema
)
151 // ------------------------------ METHODS ------------------------------
153 function getFilename () {
154 return this._id
+ this.extname
157 function getJPEGName () {
158 return this._id
+ '.jpg'
161 function getTorrentName () {
162 return this._id
+ '.torrent'
165 function isOwned () {
166 return this.remoteId
=== null
169 function toFormatedJSON () {
173 description: this.description
,
174 podUrl: this.podUrl
.replace(/^https?:\/\//, ''),
175 isLocal: this.isOwned(),
176 magnetUri: this.magnetUri
,
178 duration: this.duration
,
180 thumbnailPath: constants
.STATIC_PATHS
.THUMBNAILS
+ '/' + this.getJPEGName(),
181 createdDate: this.createdDate
187 function toRemoteJSON (callback
) {
190 // Convert thumbnail to base64
191 const thumbnailPath
= pathUtils
.join(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
, this.getJPEGName())
192 fs
.readFile(thumbnailPath
, function (err
, thumbnailData
) {
194 logger
.error('Cannot read the thumbnail of the video')
198 const remoteVideo
= {
200 description: self
.description
,
201 magnetUri: self
.magnetUri
,
204 duration: self
.duration
,
205 thumbnailBase64: new Buffer(thumbnailData
).toString('base64'),
207 createdDate: self
.createdDate
,
211 return callback(null, remoteVideo
)
215 // ------------------------------ STATICS ------------------------------
217 function getDurationFromFile (videoPath
, callback
) {
218 ffmpeg
.ffprobe(videoPath
, function (err
, metadata
) {
219 if (err
) return callback(err
)
221 return callback(null, Math
.floor(metadata
.format
.duration
))
225 function listForApi (start
, count
, sort
, callback
) {
227 return modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
230 function listByUrlAndRemoteId (fromUrl
, remoteId
, callback
) {
231 this.find({ podUrl: fromUrl
, remoteId: remoteId
}, callback
)
234 function listByUrl (fromUrl
, callback
) {
235 this.find({ podUrl: fromUrl
}, callback
)
238 function listOwned (callback
) {
239 // If remoteId is null this is *our* video
240 this.find({ remoteId: null }, callback
)
243 function listOwnedByAuthor (author
, callback
) {
244 this.find({ remoteId: null, author: author
}, callback
)
247 function listRemotes (callback
) {
248 this.find({ remoteId: { $ne: null } }, callback
)
251 function load (id
, callback
) {
252 this.findById(id
, callback
)
255 function search (value
, field
, start
, count
, sort
, callback
) {
257 // Make an exact search with the magnet
258 if (field
=== 'magnetUri' || field
=== 'tags') {
261 query
[field
] = new RegExp(value
, 'i')
264 modelUtils
.listForApiWithCount
.call(this, query
, start
, count
, sort
, callback
)
267 // ---------------------------------------------------------------------------
269 function removeThumbnail (video
, callback
) {
270 fs
.unlink(constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ video
.getJPEGName(), callback
)
273 function removeFile (video
, callback
) {
274 fs
.unlink(constants
.CONFIG
.STORAGE
.VIDEOS_DIR
+ video
.getFilename(), callback
)
277 function removeTorrent (video
, callback
) {
278 fs
.unlink(constants
.CONFIG
.STORAGE
.TORRENTS_DIR
+ video
.getTorrentName(), callback
)
281 function removePreview (video
, callback
) {
282 // Same name than video thumnail
283 // TODO: refractoring
284 fs
.unlink(constants
.CONFIG
.STORAGE
.PREVIEWS_DIR
+ video
.getJPEGName(), callback
)
287 function createPreview (video
, videoPath
, callback
) {
288 generateImage(video
, videoPath
, constants
.CONFIG
.STORAGE
.PREVIEWS_DIR
, callback
)
291 function createThumbnail (video
, videoPath
, callback
) {
292 generateImage(video
, videoPath
, constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
, constants
.THUMBNAILS_SIZE
, callback
)
295 function generateThumbnailFromBase64 (video
, thumbnailData
, callback
) {
296 // Creating the thumbnail for this remote video)
298 const thumbnailName
= video
.getJPEGName()
299 const thumbnailPath
= constants
.CONFIG
.STORAGE
.THUMBNAILS_DIR
+ thumbnailName
300 fs
.writeFile(thumbnailPath
, thumbnailData
, { encoding: 'base64' }, function (err
) {
301 if (err
) return callback(err
)
303 return callback(null, thumbnailName
)
307 function generateImage (video
, videoPath
, folder
, size
, callback
) {
308 const filename
= video
.getJPEGName()
322 .on('error', callback
)
323 .on('end', function () {
324 callback(null, filename
)