]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video.js
Server: little refractoring when listing videos
[github/Chocobozzz/PeerTube.git] / server / models / video.js
CommitLineData
aaf61f38
C
1'use strict'
2
4d324488 3const Buffer = require('safe-buffer').Buffer
052937db 4const createTorrent = require('create-torrent')
aaf61f38
C
5const ffmpeg = require('fluent-ffmpeg')
6const fs = require('fs')
f285faa0 7const magnetUtil = require('magnet-uri')
7920c273 8const map = require('lodash/map')
1a42c9e2 9const parallel = require('async/parallel')
052937db 10const parseTorrent = require('parse-torrent')
aaf61f38 11const pathUtils = require('path')
67bf9b96 12const values = require('lodash/values')
aaf61f38
C
13
14const constants = require('../initializers/constants')
aaf61f38 15const logger = require('../helpers/logger')
98ac898a 16const friends = require('../lib/friends')
0ff21c1c 17const modelUtils = require('./utils')
67bf9b96 18const customVideosValidators = require('../helpers/custom-validators').videos
198b205c 19const db = require('../initializers/database')
aaf61f38 20
aaf61f38
C
21// ---------------------------------------------------------------------------
22
feb4bdfd 23module.exports = function (sequelize, DataTypes) {
feb4bdfd
C
24 const Video = sequelize.define('Video',
25 {
26 id: {
27 type: DataTypes.UUID,
28 defaultValue: DataTypes.UUIDV4,
67bf9b96
C
29 primaryKey: true,
30 validate: {
31 isUUID: 4
32 }
aaf61f38 33 },
feb4bdfd 34 name: {
67bf9b96
C
35 type: DataTypes.STRING,
36 allowNull: false,
37 validate: {
38 nameValid: function (value) {
39 const res = customVideosValidators.isVideoNameValid(value)
40 if (res === false) throw new Error('Video name is not valid.')
41 }
42 }
6a94a109 43 },
feb4bdfd 44 extname: {
67bf9b96
C
45 type: DataTypes.ENUM(values(constants.CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
46 allowNull: false
feb4bdfd
C
47 },
48 remoteId: {
67bf9b96
C
49 type: DataTypes.UUID,
50 allowNull: true,
51 validate: {
52 isUUID: 4
53 }
feb4bdfd 54 },
6e07c3de
C
55 category: {
56 type: DataTypes.INTEGER,
57 allowNull: false,
58 validate: {
59 categoryValid: function (value) {
60 const res = customVideosValidators.isVideoCategoryValid(value)
61 if (res === false) throw new Error('Video category is not valid.')
62 }
63 }
64 },
6f0c39e2
C
65 licence: {
66 type: DataTypes.INTEGER,
67 allowNull: false,
3092476e 68 defaultValue: null,
6f0c39e2
C
69 validate: {
70 licenceValid: function (value) {
71 const res = customVideosValidators.isVideoLicenceValid(value)
72 if (res === false) throw new Error('Video licence is not valid.')
73 }
74 }
75 },
3092476e
C
76 language: {
77 type: DataTypes.INTEGER,
78 allowNull: true,
79 validate: {
80 languageValid: function (value) {
81 const res = customVideosValidators.isVideoLanguageValid(value)
82 if (res === false) throw new Error('Video language is not valid.')
83 }
84 }
85 },
31b59b47
C
86 nsfw: {
87 type: DataTypes.BOOLEAN,
88 allowNull: false,
89 validate: {
90 nsfwValid: function (value) {
91 const res = customVideosValidators.isVideoNSFWValid(value)
92 if (res === false) throw new Error('Video nsfw attribute is not valid.')
93 }
94 }
95 },
feb4bdfd 96 description: {
67bf9b96
C
97 type: DataTypes.STRING,
98 allowNull: false,
99 validate: {
100 descriptionValid: function (value) {
101 const res = customVideosValidators.isVideoDescriptionValid(value)
102 if (res === false) throw new Error('Video description is not valid.')
103 }
104 }
feb4bdfd
C
105 },
106 infoHash: {
67bf9b96
C
107 type: DataTypes.STRING,
108 allowNull: false,
109 validate: {
110 infoHashValid: function (value) {
111 const res = customVideosValidators.isVideoInfoHashValid(value)
112 if (res === false) throw new Error('Video info hash is not valid.')
113 }
114 }
feb4bdfd
C
115 },
116 duration: {
67bf9b96
C
117 type: DataTypes.INTEGER,
118 allowNull: false,
119 validate: {
120 durationValid: function (value) {
121 const res = customVideosValidators.isVideoDurationValid(value)
122 if (res === false) throw new Error('Video duration is not valid.')
123 }
124 }
9e167724
C
125 },
126 views: {
127 type: DataTypes.INTEGER,
128 allowNull: false,
129 defaultValue: 0,
130 validate: {
131 min: 0,
132 isInt: true
133 }
d38b8281
C
134 },
135 likes: {
136 type: DataTypes.INTEGER,
137 allowNull: false,
138 defaultValue: 0,
139 validate: {
140 min: 0,
141 isInt: true
142 }
143 },
144 dislikes: {
145 type: DataTypes.INTEGER,
146 allowNull: false,
147 defaultValue: 0,
148 validate: {
149 min: 0,
150 isInt: true
151 }
aaf61f38 152 }
feb4bdfd
C
153 },
154 {
319d072e
C
155 indexes: [
156 {
157 fields: [ 'authorId' ]
158 },
159 {
160 fields: [ 'remoteId' ]
161 },
162 {
163 fields: [ 'name' ]
164 },
165 {
166 fields: [ 'createdAt' ]
167 },
168 {
169 fields: [ 'duration' ]
170 },
171 {
172 fields: [ 'infoHash' ]
9e167724
C
173 },
174 {
175 fields: [ 'views' ]
d38b8281
C
176 },
177 {
178 fields: [ 'likes' ]
319d072e
C
179 }
180 ],
feb4bdfd
C
181 classMethods: {
182 associate,
183
4d324488 184 generateThumbnailFromData,
feb4bdfd 185 getDurationFromFile,
b769007f 186 list,
feb4bdfd 187 listForApi,
7920c273 188 listOwnedAndPopulateAuthorAndTags,
feb4bdfd
C
189 listOwnedByAuthor,
190 load,
3d118fb5 191 loadByHostAndRemoteId,
feb4bdfd 192 loadAndPopulateAuthor,
7920c273
C
193 loadAndPopulateAuthorAndPodAndTags,
194 searchAndPopulateAuthorAndPodAndTags
feb4bdfd
C
195 },
196 instanceMethods: {
197 generateMagnetUri,
198 getVideoFilename,
199 getThumbnailName,
200 getPreviewName,
201 getTorrentName,
202 isOwned,
203 toFormatedJSON,
7b1f49de 204 toAddRemoteJSON,
198b205c
GS
205 toUpdateRemoteJSON,
206 removeFromBlacklist
feb4bdfd
C
207 },
208 hooks: {
67bf9b96 209 beforeValidate,
feb4bdfd
C
210 beforeCreate,
211 afterDestroy
212 }
213 }
214 )
aaf61f38 215
feb4bdfd
C
216 return Video
217}
aaf61f38 218
67bf9b96 219function beforeValidate (video, options, next) {
7f4e7c36
C
220 // Put a fake infoHash if it does not exists yet
221 if (video.isOwned() && !video.infoHash) {
67bf9b96
C
222 // 40 hexa length
223 video.infoHash = '0123456789abcdef0123456789abcdef01234567'
224 }
225
226 return next(null)
227}
feb4bdfd
C
228
229function beforeCreate (video, options, next) {
aaf61f38
C
230 const tasks = []
231
232 if (video.isOwned()) {
f285faa0 233 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
aaf61f38
C
234
235 tasks.push(
15103f11 236 function createVideoTorrent (callback) {
25cad919
C
237 const options = {
238 announceList: [
3737bbaf 239 [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
25cad919
C
240 ],
241 urlList: [
f285faa0 242 constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getVideoFilename()
25cad919
C
243 ]
244 }
245
246 createTorrent(videoPath, options, function (err, torrent) {
052937db
C
247 if (err) return callback(err)
248
15103f11
C
249 const filePath = pathUtils.join(constants.CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
250 fs.writeFile(filePath, torrent, function (err) {
052937db
C
251 if (err) return callback(err)
252
253 const parsedTorrent = parseTorrent(torrent)
67bf9b96
C
254 video.set('infoHash', parsedTorrent.infoHash)
255 video.validate().asCallback(callback)
052937db
C
256 })
257 })
aaf61f38 258 },
15103f11
C
259
260 function createVideoThumbnail (callback) {
558d7c23 261 createThumbnail(video, videoPath, callback)
6a94a109 262 },
15103f11
C
263
264 function createVIdeoPreview (callback) {
558d7c23 265 createPreview(video, videoPath, callback)
aaf61f38
C
266 }
267 )
268
c77fa067 269 return parallel(tasks, next)
aaf61f38 270 }
c77fa067
C
271
272 return next()
feb4bdfd 273}
aaf61f38 274
feb4bdfd
C
275function afterDestroy (video, options, next) {
276 const tasks = []
277
278 tasks.push(
279 function (callback) {
280 removeThumbnail(video, callback)
281 }
282 )
283
284 if (video.isOwned()) {
285 tasks.push(
15103f11 286 function removeVideoFile (callback) {
feb4bdfd
C
287 removeFile(video, callback)
288 },
98ac898a 289
15103f11 290 function removeVideoTorrent (callback) {
feb4bdfd
C
291 removeTorrent(video, callback)
292 },
98ac898a 293
15103f11 294 function removeVideoPreview (callback) {
feb4bdfd 295 removePreview(video, callback)
98ac898a
C
296 },
297
15103f11 298 function removeVideoToFriends (callback) {
98ac898a 299 const params = {
98ac898a
C
300 remoteId: video.id
301 }
302
303 friends.removeVideoToFriends(params)
304
305 return callback()
feb4bdfd
C
306 }
307 )
308 }
309
310 parallel(tasks, next)
311}
aaf61f38
C
312
313// ------------------------------ METHODS ------------------------------
314
feb4bdfd
C
315function associate (models) {
316 this.belongsTo(models.Author, {
317 foreignKey: {
318 name: 'authorId',
319 allowNull: false
320 },
321 onDelete: 'cascade'
322 })
7920c273
C
323
324 this.belongsToMany(models.Tag, {
325 foreignKey: 'videoId',
326 through: models.VideoTag,
327 onDelete: 'cascade'
328 })
55fa55a9
C
329
330 this.hasMany(models.VideoAbuse, {
331 foreignKey: {
332 name: 'videoId',
333 allowNull: false
334 },
335 onDelete: 'cascade'
336 })
feb4bdfd
C
337}
338
f285faa0
C
339function generateMagnetUri () {
340 let baseUrlHttp, baseUrlWs
341
342 if (this.isOwned()) {
343 baseUrlHttp = constants.CONFIG.WEBSERVER.URL
344 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
345 } else {
feb4bdfd
C
346 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
347 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
f285faa0
C
348 }
349
350 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
351 const announce = baseUrlWs + '/tracker/socket'
352 const urlList = [ baseUrlHttp + constants.STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
353
354 const magnetHash = {
355 xs,
356 announce,
357 urlList,
feb4bdfd 358 infoHash: this.infoHash,
f285faa0
C
359 name: this.name
360 }
361
362 return magnetUtil.encode(magnetHash)
558d7c23
C
363}
364
f285faa0 365function getVideoFilename () {
feb4bdfd 366 if (this.isOwned()) return this.id + this.extname
f285faa0
C
367
368 return this.remoteId + this.extname
369}
370
371function getThumbnailName () {
372 // We always have a copy of the thumbnail
feb4bdfd 373 return this.id + '.jpg'
558d7c23
C
374}
375
f285faa0
C
376function getPreviewName () {
377 const extension = '.jpg'
378
feb4bdfd 379 if (this.isOwned()) return this.id + extension
f285faa0
C
380
381 return this.remoteId + extension
382}
383
558d7c23 384function getTorrentName () {
f285faa0
C
385 const extension = '.torrent'
386
feb4bdfd 387 if (this.isOwned()) return this.id + extension
f285faa0
C
388
389 return this.remoteId + extension
558d7c23
C
390}
391
aaf61f38 392function isOwned () {
558d7c23 393 return this.remoteId === null
aaf61f38
C
394}
395
396function toFormatedJSON () {
feb4bdfd
C
397 let podHost
398
399 if (this.Author.Pod) {
400 podHost = this.Author.Pod.host
401 } else {
402 // It means it's our video
403 podHost = constants.CONFIG.WEBSERVER.HOST
404 }
405
6e07c3de
C
406 // Maybe our pod is not up to date and there are new categories since our version
407 let categoryLabel = constants.VIDEO_CATEGORIES[this.category]
408 if (!categoryLabel) categoryLabel = 'Misc'
409
6f0c39e2
C
410 // Maybe our pod is not up to date and there are new licences since our version
411 let licenceLabel = constants.VIDEO_LICENCES[this.licence]
412 if (!licenceLabel) licenceLabel = 'Unknown'
413
3092476e
C
414 // Language is an optional attribute
415 let languageLabel = constants.VIDEO_LANGUAGES[this.language]
416 if (!languageLabel) languageLabel = 'Unknown'
417
aaf61f38 418 const json = {
feb4bdfd 419 id: this.id,
aaf61f38 420 name: this.name,
6e07c3de
C
421 category: this.category,
422 categoryLabel,
6f0c39e2
C
423 licence: this.licence,
424 licenceLabel,
3092476e
C
425 language: this.language,
426 languageLabel,
31b59b47 427 nsfw: this.nsfw,
aaf61f38 428 description: this.description,
feb4bdfd 429 podHost,
aaf61f38 430 isLocal: this.isOwned(),
f285faa0 431 magnetUri: this.generateMagnetUri(),
feb4bdfd 432 author: this.Author.name,
aaf61f38 433 duration: this.duration,
9e167724 434 views: this.views,
d38b8281
C
435 likes: this.likes,
436 dislikes: this.dislikes,
7920c273 437 tags: map(this.Tags, 'name'),
91cc839a 438 thumbnailPath: pathUtils.join(constants.STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
79066fdf
C
439 createdAt: this.createdAt,
440 updatedAt: this.updatedAt
aaf61f38
C
441 }
442
443 return json
444}
445
7b1f49de 446function toAddRemoteJSON (callback) {
aaf61f38
C
447 const self = this
448
4d324488 449 // Get thumbnail data to send to the other pod
f285faa0 450 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
558d7c23 451 fs.readFile(thumbnailPath, function (err, thumbnailData) {
aaf61f38
C
452 if (err) {
453 logger.error('Cannot read the thumbnail of the video')
454 return callback(err)
455 }
456
457 const remoteVideo = {
458 name: self.name,
6e07c3de 459 category: self.category,
6f0c39e2 460 licence: self.licence,
3092476e 461 language: self.language,
31b59b47 462 nsfw: self.nsfw,
aaf61f38 463 description: self.description,
feb4bdfd
C
464 infoHash: self.infoHash,
465 remoteId: self.id,
466 author: self.Author.name,
aaf61f38 467 duration: self.duration,
4d324488 468 thumbnailData: thumbnailData.toString('binary'),
7920c273 469 tags: map(self.Tags, 'name'),
feb4bdfd 470 createdAt: self.createdAt,
79066fdf 471 updatedAt: self.updatedAt,
e3d156b3 472 extname: self.extname,
d38b8281
C
473 views: self.views,
474 likes: self.likes,
475 dislikes: self.dislikes
aaf61f38
C
476 }
477
478 return callback(null, remoteVideo)
479 })
480}
481
7b1f49de
C
482function toUpdateRemoteJSON (callback) {
483 const json = {
484 name: this.name,
6e07c3de 485 category: this.category,
6f0c39e2 486 licence: this.licence,
3092476e 487 language: this.language,
31b59b47 488 nsfw: this.nsfw,
7b1f49de
C
489 description: this.description,
490 infoHash: this.infoHash,
491 remoteId: this.id,
492 author: this.Author.name,
493 duration: this.duration,
494 tags: map(this.Tags, 'name'),
495 createdAt: this.createdAt,
79066fdf 496 updatedAt: this.updatedAt,
e3d156b3 497 extname: this.extname,
d38b8281
C
498 views: this.views,
499 likes: this.likes,
500 dislikes: this.dislikes
7b1f49de
C
501 }
502
503 return json
504}
505
aaf61f38
C
506// ------------------------------ STATICS ------------------------------
507
4d324488 508function generateThumbnailFromData (video, thumbnailData, callback) {
c77fa067
C
509 // Creating the thumbnail for a remote video
510
511 const thumbnailName = video.getThumbnailName()
15103f11 512 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
4d324488 513 fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) {
c77fa067
C
514 if (err) return callback(err)
515
516 return callback(null, thumbnailName)
517 })
518}
519
aaf61f38
C
520function getDurationFromFile (videoPath, callback) {
521 ffmpeg.ffprobe(videoPath, function (err, metadata) {
522 if (err) return callback(err)
523
524 return callback(null, Math.floor(metadata.format.duration))
525 })
526}
527
b769007f 528function list (callback) {
9cc99d7b 529 return this.findAll().asCallback(callback)
b769007f
C
530}
531
0ff21c1c 532function listForApi (start, count, sort, callback) {
198b205c 533 // Exclude Blakclisted videos from the list
feb4bdfd
C
534 const query = {
535 offset: start,
536 limit: count,
7920c273 537 distinct: true, // For the count, a video can have many tags
178edb20 538 order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ],
feb4bdfd
C
539 include: [
540 {
541 model: this.sequelize.models.Author,
7920c273
C
542 include: [ { model: this.sequelize.models.Pod, required: false } ]
543 },
544
545 this.sequelize.models.Tag
198b205c 546 ],
15d4ee04 547 where: createBaseVideosWhere.call(this)
feb4bdfd
C
548 }
549
550 return this.findAndCountAll(query).asCallback(function (err, result) {
551 if (err) return callback(err)
552
553 return callback(null, result.rows, result.count)
554 })
aaf61f38
C
555}
556
3d118fb5 557function loadByHostAndRemoteId (fromHost, remoteId, callback) {
feb4bdfd
C
558 const query = {
559 where: {
560 remoteId: remoteId
561 },
562 include: [
563 {
564 model: this.sequelize.models.Author,
565 include: [
566 {
567 model: this.sequelize.models.Pod,
7920c273 568 required: true,
feb4bdfd
C
569 where: {
570 host: fromHost
571 }
572 }
573 ]
574 }
575 ]
576 }
aaf61f38 577
3d118fb5 578 return this.findOne(query).asCallback(callback)
aaf61f38
C
579}
580
7920c273 581function listOwnedAndPopulateAuthorAndTags (callback) {
558d7c23 582 // If remoteId is null this is *our* video
feb4bdfd
C
583 const query = {
584 where: {
585 remoteId: null
586 },
7920c273 587 include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
feb4bdfd
C
588 }
589
590 return this.findAll(query).asCallback(callback)
aaf61f38
C
591}
592
9bd26629 593function listOwnedByAuthor (author, callback) {
feb4bdfd
C
594 const query = {
595 where: {
596 remoteId: null
597 },
598 include: [
599 {
600 model: this.sequelize.models.Author,
601 where: {
602 name: author
603 }
604 }
605 ]
606 }
9bd26629 607
feb4bdfd 608 return this.findAll(query).asCallback(callback)
aaf61f38
C
609}
610
611function load (id, callback) {
feb4bdfd
C
612 return this.findById(id).asCallback(callback)
613}
614
615function loadAndPopulateAuthor (id, callback) {
616 const options = {
617 include: [ this.sequelize.models.Author ]
618 }
619
620 return this.findById(id, options).asCallback(callback)
621}
622
7920c273 623function loadAndPopulateAuthorAndPodAndTags (id, callback) {
feb4bdfd
C
624 const options = {
625 include: [
626 {
627 model: this.sequelize.models.Author,
7920c273
C
628 include: [ { model: this.sequelize.models.Pod, required: false } ]
629 },
630 this.sequelize.models.Tag
feb4bdfd
C
631 ]
632 }
633
634 return this.findById(id, options).asCallback(callback)
aaf61f38
C
635}
636
7920c273 637function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
feb4bdfd 638 const podInclude = {
7920c273
C
639 model: this.sequelize.models.Pod,
640 required: false
feb4bdfd 641 }
7920c273 642
feb4bdfd
C
643 const authorInclude = {
644 model: this.sequelize.models.Author,
645 include: [
646 podInclude
647 ]
648 }
649
7920c273
C
650 const tagInclude = {
651 model: this.sequelize.models.Tag
652 }
653
feb4bdfd 654 const query = {
15d4ee04 655 where: createBaseVideosWhere.call(this),
feb4bdfd
C
656 offset: start,
657 limit: count,
7920c273 658 distinct: true, // For the count, a video can have many tags
178edb20 659 order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ]
feb4bdfd
C
660 }
661
aaf61f38 662 // Make an exact search with the magnet
55723d16
C
663 if (field === 'magnetUri') {
664 const infoHash = magnetUtil.decode(value).infoHash
feb4bdfd 665 query.where.infoHash = infoHash
55723d16 666 } else if (field === 'tags') {
7920c273 667 const escapedValue = this.sequelize.escape('%' + value + '%')
198b205c
GS
668 query.where.id.$in = this.sequelize.literal(
669 '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
670 )
7920c273
C
671 } else if (field === 'host') {
672 // FIXME: Include our pod? (not stored in the database)
673 podInclude.where = {
674 host: {
675 $like: '%' + value + '%'
feb4bdfd 676 }
feb4bdfd 677 }
7920c273 678 podInclude.required = true
feb4bdfd 679 } else if (field === 'author') {
7920c273
C
680 authorInclude.where = {
681 name: {
feb4bdfd
C
682 $like: '%' + value + '%'
683 }
684 }
7920c273
C
685
686 // authorInclude.or = true
aaf61f38 687 } else {
feb4bdfd
C
688 query.where[field] = {
689 $like: '%' + value + '%'
690 }
aaf61f38
C
691 }
692
7920c273
C
693 query.include = [
694 authorInclude, tagInclude
695 ]
696
697 if (tagInclude.where) {
698 // query.include.push([ this.sequelize.models.Tag ])
699 }
700
feb4bdfd
C
701 return this.findAndCountAll(query).asCallback(function (err, result) {
702 if (err) return callback(err)
703
704 return callback(null, result.rows, result.count)
705 })
aaf61f38
C
706}
707
aaf61f38
C
708// ---------------------------------------------------------------------------
709
15d4ee04
C
710function createBaseVideosWhere () {
711 return {
712 id: {
713 $notIn: this.sequelize.literal(
714 '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
715 )
716 }
717 }
718}
719
aaf61f38 720function removeThumbnail (video, callback) {
15103f11
C
721 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
722 fs.unlink(thumbnailPath, callback)
aaf61f38
C
723}
724
725function removeFile (video, callback) {
15103f11
C
726 const filePath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
727 fs.unlink(filePath, callback)
aaf61f38
C
728}
729
aaf61f38 730function removeTorrent (video, callback) {
15103f11
C
731 const torrenPath = pathUtils.join(constants.CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
732 fs.unlink(torrenPath, callback)
aaf61f38
C
733}
734
6a94a109
C
735function removePreview (video, callback) {
736 // Same name than video thumnail
f285faa0 737 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
6a94a109
C
738}
739
558d7c23 740function createPreview (video, videoPath, callback) {
f285faa0 741 generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
6a94a109
C
742}
743
558d7c23 744function createThumbnail (video, videoPath, callback) {
f285faa0 745 generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), constants.THUMBNAILS_SIZE, callback)
aaf61f38
C
746}
747
f285faa0 748function generateImage (video, videoPath, folder, imageName, size, callback) {
558d7c23 749 const options = {
f285faa0 750 filename: imageName,
558d7c23
C
751 count: 1,
752 folder
753 }
aaf61f38 754
558d7c23
C
755 if (!callback) {
756 callback = size
757 } else {
758 options.size = size
759 }
760
761 ffmpeg(videoPath)
762 .on('error', callback)
763 .on('end', function () {
f285faa0 764 callback(null, imageName)
aaf61f38 765 })
558d7c23 766 .thumbnail(options)
aaf61f38 767}
198b205c
GS
768
769function removeFromBlacklist (video, callback) {
770 // Find the blacklisted video
771 db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
772 // If an error occured, stop here
773 if (err) {
774 logger.error('Error when fetching video from blacklist.', { error: err })
198b205c
GS
775 return callback(err)
776 }
777
778 // If we found the video, remove it from the blacklist
779 if (video) {
780 video.destroy().asCallback(callback)
781 } else {
782 // If haven't found it, simply ignore it and do nothing
783 return callback()
784 }
785 })
786}