]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video.js
Server: add tests for video blacklists
[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
GS
546 ],
547 where: {
548 id: { $notIn: this.sequelize.literal(
549 '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
550 )}
551 }
feb4bdfd
C
552 }
553
554 return this.findAndCountAll(query).asCallback(function (err, result) {
555 if (err) return callback(err)
556
557 return callback(null, result.rows, result.count)
558 })
aaf61f38
C
559}
560
3d118fb5 561function loadByHostAndRemoteId (fromHost, remoteId, callback) {
feb4bdfd
C
562 const query = {
563 where: {
564 remoteId: remoteId
565 },
566 include: [
567 {
568 model: this.sequelize.models.Author,
569 include: [
570 {
571 model: this.sequelize.models.Pod,
7920c273 572 required: true,
feb4bdfd
C
573 where: {
574 host: fromHost
575 }
576 }
577 ]
578 }
579 ]
580 }
aaf61f38 581
3d118fb5 582 return this.findOne(query).asCallback(callback)
aaf61f38
C
583}
584
7920c273 585function listOwnedAndPopulateAuthorAndTags (callback) {
558d7c23 586 // If remoteId is null this is *our* video
feb4bdfd
C
587 const query = {
588 where: {
589 remoteId: null
590 },
7920c273 591 include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
feb4bdfd
C
592 }
593
594 return this.findAll(query).asCallback(callback)
aaf61f38
C
595}
596
9bd26629 597function listOwnedByAuthor (author, callback) {
feb4bdfd
C
598 const query = {
599 where: {
600 remoteId: null
601 },
602 include: [
603 {
604 model: this.sequelize.models.Author,
605 where: {
606 name: author
607 }
608 }
609 ]
610 }
9bd26629 611
feb4bdfd 612 return this.findAll(query).asCallback(callback)
aaf61f38
C
613}
614
615function load (id, callback) {
feb4bdfd
C
616 return this.findById(id).asCallback(callback)
617}
618
619function loadAndPopulateAuthor (id, callback) {
620 const options = {
621 include: [ this.sequelize.models.Author ]
622 }
623
624 return this.findById(id, options).asCallback(callback)
625}
626
7920c273 627function loadAndPopulateAuthorAndPodAndTags (id, callback) {
feb4bdfd
C
628 const options = {
629 include: [
630 {
631 model: this.sequelize.models.Author,
7920c273
C
632 include: [ { model: this.sequelize.models.Pod, required: false } ]
633 },
634 this.sequelize.models.Tag
feb4bdfd
C
635 ]
636 }
637
638 return this.findById(id, options).asCallback(callback)
aaf61f38
C
639}
640
7920c273 641function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
feb4bdfd 642 const podInclude = {
7920c273
C
643 model: this.sequelize.models.Pod,
644 required: false
feb4bdfd 645 }
7920c273 646
feb4bdfd
C
647 const authorInclude = {
648 model: this.sequelize.models.Author,
649 include: [
650 podInclude
651 ]
652 }
653
7920c273
C
654 const tagInclude = {
655 model: this.sequelize.models.Tag
656 }
657
feb4bdfd 658 const query = {
198b205c
GS
659 where: {
660 id: { $notIn: this.sequelize.literal(
661 '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
662 )}
663 },
feb4bdfd
C
664 offset: start,
665 limit: count,
7920c273 666 distinct: true, // For the count, a video can have many tags
178edb20 667 order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ]
feb4bdfd
C
668 }
669
aaf61f38 670 // Make an exact search with the magnet
55723d16
C
671 if (field === 'magnetUri') {
672 const infoHash = magnetUtil.decode(value).infoHash
feb4bdfd 673 query.where.infoHash = infoHash
55723d16 674 } else if (field === 'tags') {
7920c273 675 const escapedValue = this.sequelize.escape('%' + value + '%')
198b205c
GS
676 query.where.id.$in = this.sequelize.literal(
677 '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
678 )
7920c273
C
679 } else if (field === 'host') {
680 // FIXME: Include our pod? (not stored in the database)
681 podInclude.where = {
682 host: {
683 $like: '%' + value + '%'
feb4bdfd 684 }
feb4bdfd 685 }
7920c273 686 podInclude.required = true
feb4bdfd 687 } else if (field === 'author') {
7920c273
C
688 authorInclude.where = {
689 name: {
feb4bdfd
C
690 $like: '%' + value + '%'
691 }
692 }
7920c273
C
693
694 // authorInclude.or = true
aaf61f38 695 } else {
feb4bdfd
C
696 query.where[field] = {
697 $like: '%' + value + '%'
698 }
aaf61f38
C
699 }
700
7920c273
C
701 query.include = [
702 authorInclude, tagInclude
703 ]
704
705 if (tagInclude.where) {
706 // query.include.push([ this.sequelize.models.Tag ])
707 }
708
feb4bdfd
C
709 return this.findAndCountAll(query).asCallback(function (err, result) {
710 if (err) return callback(err)
711
712 return callback(null, result.rows, result.count)
713 })
aaf61f38
C
714}
715
aaf61f38
C
716// ---------------------------------------------------------------------------
717
aaf61f38 718function removeThumbnail (video, callback) {
15103f11
C
719 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
720 fs.unlink(thumbnailPath, callback)
aaf61f38
C
721}
722
723function removeFile (video, callback) {
15103f11
C
724 const filePath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
725 fs.unlink(filePath, callback)
aaf61f38
C
726}
727
aaf61f38 728function removeTorrent (video, callback) {
15103f11
C
729 const torrenPath = pathUtils.join(constants.CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
730 fs.unlink(torrenPath, callback)
aaf61f38
C
731}
732
6a94a109
C
733function removePreview (video, callback) {
734 // Same name than video thumnail
f285faa0 735 fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
6a94a109
C
736}
737
558d7c23 738function createPreview (video, videoPath, callback) {
f285faa0 739 generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
6a94a109
C
740}
741
558d7c23 742function createThumbnail (video, videoPath, callback) {
f285faa0 743 generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), constants.THUMBNAILS_SIZE, callback)
aaf61f38
C
744}
745
f285faa0 746function generateImage (video, videoPath, folder, imageName, size, callback) {
558d7c23 747 const options = {
f285faa0 748 filename: imageName,
558d7c23
C
749 count: 1,
750 folder
751 }
aaf61f38 752
558d7c23
C
753 if (!callback) {
754 callback = size
755 } else {
756 options.size = size
757 }
758
759 ffmpeg(videoPath)
760 .on('error', callback)
761 .on('end', function () {
f285faa0 762 callback(null, imageName)
aaf61f38 763 })
558d7c23 764 .thumbnail(options)
aaf61f38 765}
198b205c
GS
766
767function removeFromBlacklist (video, callback) {
768 // Find the blacklisted video
769 db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
770 // If an error occured, stop here
771 if (err) {
772 logger.error('Error when fetching video from blacklist.', { error: err })
198b205c
GS
773 return callback(err)
774 }
775
776 // If we found the video, remove it from the blacklist
777 if (video) {
778 video.destroy().asCallback(callback)
779 } else {
780 // If haven't found it, simply ignore it and do nothing
781 return callback()
782 }
783 })
784}