aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/video.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r--server/models/video/video.ts369
1 files changed, 158 insertions, 211 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e66ebee2d..629051ae4 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1,17 +1,15 @@
1import * as safeBuffer from 'safe-buffer' 1import * as safeBuffer from 'safe-buffer'
2const Buffer = safeBuffer.Buffer 2const Buffer = safeBuffer.Buffer
3import * as createTorrent from 'create-torrent'
4import * as ffmpeg from 'fluent-ffmpeg' 3import * as ffmpeg from 'fluent-ffmpeg'
5import * as fs from 'fs'
6import * as magnetUtil from 'magnet-uri' 4import * as magnetUtil from 'magnet-uri'
7import { map, values } from 'lodash' 5import { map, values } from 'lodash'
8import { parallel, series } from 'async'
9import * as parseTorrent from 'parse-torrent' 6import * as parseTorrent from 'parse-torrent'
10import { join } from 'path' 7import { join } from 'path'
11import * as Sequelize from 'sequelize' 8import * as Sequelize from 'sequelize'
9import * as Promise from 'bluebird'
12 10
13import { database as db } from '../../initializers/database' 11import { database as db } from '../../initializers/database'
14import { VideoTagInstance } from './video-tag-interface' 12import { TagInstance } from './tag-interface'
15import { 13import {
16 logger, 14 logger,
17 isVideoNameValid, 15 isVideoNameValid,
@@ -21,7 +19,12 @@ import {
21 isVideoNSFWValid, 19 isVideoNSFWValid,
22 isVideoDescriptionValid, 20 isVideoDescriptionValid,
23 isVideoInfoHashValid, 21 isVideoInfoHashValid,
24 isVideoDurationValid 22 isVideoDurationValid,
23 readFileBufferPromise,
24 unlinkPromise,
25 renamePromise,
26 writeFilePromise,
27 createTorrentPromise
25} from '../../helpers' 28} from '../../helpers'
26import { 29import {
27 CONSTRAINTS_FIELDS, 30 CONSTRAINTS_FIELDS,
@@ -37,7 +40,6 @@ import { JobScheduler, removeVideoToFriends } from '../../lib'
37 40
38import { addMethodsToModel, getSort } from '../utils' 41import { addMethodsToModel, getSort } from '../utils'
39import { 42import {
40 VideoClass,
41 VideoInstance, 43 VideoInstance,
42 VideoAttributes, 44 VideoAttributes,
43 45
@@ -260,7 +262,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
260 toFormatedJSON, 262 toFormatedJSON,
261 toAddRemoteJSON, 263 toAddRemoteJSON,
262 toUpdateRemoteJSON, 264 toUpdateRemoteJSON,
263 transcodeVideofile, 265 transcodeVideofile
264 ] 266 ]
265 addMethodsToModel(Video, classMethods, instanceMethods) 267 addMethodsToModel(Video, classMethods, instanceMethods)
266 268
@@ -276,91 +278,53 @@ function beforeValidate (video: VideoInstance) {
276} 278}
277 279
278function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) { 280function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
279 return new Promise(function (resolve, reject) { 281 if (video.isOwned()) {
282 const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
280 const tasks = [] 283 const tasks = []
281 284
282 if (video.isOwned()) { 285 tasks.push(
283 const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) 286 createTorrentFromVideo(video, videoPath),
284 287 createThumbnail(video, videoPath),
285 tasks.push( 288 createPreview(video, videoPath)
286 function createVideoTorrent (callback) { 289 )
287 createTorrentFromVideo(video, videoPath, callback)
288 },
289
290 function createVideoThumbnail (callback) {
291 createThumbnail(video, videoPath, callback)
292 },
293
294 function createVideoPreview (callback) {
295 createPreview(video, videoPath, callback)
296 }
297 )
298
299 if (CONFIG.TRANSCODING.ENABLED === true) {
300 tasks.push(
301 function createVideoTranscoderJob (callback) {
302 const dataInput = {
303 id: video.id
304 }
305 290
306 JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback) 291 if (CONFIG.TRANSCODING.ENABLED === true) {
307 } 292 const dataInput = {
308 ) 293 id: video.id
309 } 294 }
310 295
311 return parallel(tasks, function (err) { 296 tasks.push(
312 if (err) return reject(err) 297 JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput)
313 298 )
314 return resolve()
315 })
316 } 299 }
317 300
318 return resolve() 301 return Promise.all(tasks)
319 }) 302 }
303
304 return Promise.resolve()
320} 305}
321 306
322function afterDestroy (video: VideoInstance) { 307function afterDestroy (video: VideoInstance) {
323 return new Promise(function (resolve, reject) { 308 const tasks = []
324 const tasks = []
325
326 tasks.push(
327 function (callback) {
328 removeThumbnail(video, callback)
329 }
330 )
331
332 if (video.isOwned()) {
333 tasks.push(
334 function removeVideoFile (callback) {
335 removeFile(video, callback)
336 },
337 309
338 function removeVideoTorrent (callback) { 310 tasks.push(
339 removeTorrent(video, callback) 311 removeThumbnail(video)
340 }, 312 )
341
342 function removeVideoPreview (callback) {
343 removePreview(video, callback)
344 },
345
346 function notifyFriends (callback) {
347 const params = {
348 remoteId: video.id
349 }
350
351 removeVideoToFriends(params)
352 313
353 return callback() 314 if (video.isOwned()) {
354 } 315 const removeVideoToFriendsParams = {
355 ) 316 remoteId: video.id
356 } 317 }
357 318
358 parallel(tasks, function (err) { 319 tasks.push(
359 if (err) return reject(err) 320 removeFile(video),
321 removeTorrent(video),
322 removePreview(video),
323 removeVideoToFriends(removeVideoToFriendsParams)
324 )
325 }
360 326
361 return resolve() 327 return Promise.all(tasks)
362 })
363 })
364} 328}
365 329
366// ------------------------------ METHODS ------------------------------ 330// ------------------------------ METHODS ------------------------------
@@ -488,7 +452,7 @@ toFormatedJSON = function (this: VideoInstance) {
488 views: this.views, 452 views: this.views,
489 likes: this.likes, 453 likes: this.likes,
490 dislikes: this.dislikes, 454 dislikes: this.dislikes,
491 tags: map<VideoTagInstance, string>(this.Tags, 'name'), 455 tags: map<TagInstance, string>(this.Tags, 'name'),
492 thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), 456 thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
493 createdAt: this.createdAt, 457 createdAt: this.createdAt,
494 updatedAt: this.updatedAt 458 updatedAt: this.updatedAt
@@ -497,15 +461,11 @@ toFormatedJSON = function (this: VideoInstance) {
497 return json 461 return json
498} 462}
499 463
500toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRemoteJSONCallback) { 464toAddRemoteJSON = function (this: VideoInstance) {
501 // Get thumbnail data to send to the other pod 465 // Get thumbnail data to send to the other pod
502 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) 466 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
503 fs.readFile(thumbnailPath, (err, thumbnailData) => {
504 if (err) {
505 logger.error('Cannot read the thumbnail of the video')
506 return callback(err)
507 }
508 467
468 return readFileBufferPromise(thumbnailPath).then(thumbnailData => {
509 const remoteVideo = { 469 const remoteVideo = {
510 name: this.name, 470 name: this.name,
511 category: this.category, 471 category: this.category,
@@ -518,7 +478,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem
518 author: this.Author.name, 478 author: this.Author.name,
519 duration: this.duration, 479 duration: this.duration,
520 thumbnailData: thumbnailData.toString('binary'), 480 thumbnailData: thumbnailData.toString('binary'),
521 tags: map<VideoTagInstance, string>(this.Tags, 'name'), 481 tags: map<TagInstance, string>(this.Tags, 'name'),
522 createdAt: this.createdAt, 482 createdAt: this.createdAt,
523 updatedAt: this.updatedAt, 483 updatedAt: this.updatedAt,
524 extname: this.extname, 484 extname: this.extname,
@@ -527,7 +487,7 @@ toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRem
527 dislikes: this.dislikes 487 dislikes: this.dislikes
528 } 488 }
529 489
530 return callback(null, remoteVideo) 490 return remoteVideo
531 }) 491 })
532} 492}
533 493
@@ -543,7 +503,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
543 remoteId: this.id, 503 remoteId: this.id,
544 author: this.Author.name, 504 author: this.Author.name,
545 duration: this.duration, 505 duration: this.duration,
546 tags: map<VideoTagInstance, string>(this.Tags, 'name'), 506 tags: map<TagInstance, string>(this.Tags, 'name'),
547 createdAt: this.createdAt, 507 createdAt: this.createdAt,
548 updatedAt: this.updatedAt, 508 updatedAt: this.updatedAt,
549 extname: this.extname, 509 extname: this.extname,
@@ -555,7 +515,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
555 return json 515 return json
556} 516}
557 517
558transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.TranscodeVideofileCallback) { 518transcodeVideofile = function (this: VideoInstance) {
559 const video = this 519 const video = this
560 520
561 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 521 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
@@ -563,78 +523,73 @@ transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.
563 const videoInputPath = join(videosDirectory, video.getVideoFilename()) 523 const videoInputPath = join(videosDirectory, video.getVideoFilename())
564 const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname) 524 const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
565 525
566 ffmpeg(videoInputPath) 526 return new Promise<void>((res, rej) => {
567 .output(videoOutputPath) 527 ffmpeg(videoInputPath)
568 .videoCodec('libx264') 528 .output(videoOutputPath)
569 .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) 529 .videoCodec('libx264')
570 .outputOption('-movflags faststart') 530 .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
571 .on('error', finalCallback) 531 .outputOption('-movflags faststart')
572 .on('end', function () { 532 .on('error', rej)
573 series([ 533 .on('end', () => {
574 function removeOldFile (callback) { 534
575 fs.unlink(videoInputPath, callback) 535 return unlinkPromise(videoInputPath)
576 }, 536 .then(() => {
577 537 // Important to do this before getVideoFilename() to take in account the new file extension
578 function moveNewFile (callback) { 538 video.set('extname', newExtname)
579 // Important to do this before getVideoFilename() to take in account the new file extension 539
580 video.set('extname', newExtname) 540 const newVideoPath = join(videosDirectory, video.getVideoFilename())
581 541 return renamePromise(videoOutputPath, newVideoPath)
582 const newVideoPath = join(videosDirectory, video.getVideoFilename())
583 fs.rename(videoOutputPath, newVideoPath, callback)
584 },
585
586 function torrent (callback) {
587 const newVideoPath = join(videosDirectory, video.getVideoFilename())
588 createTorrentFromVideo(video, newVideoPath, callback)
589 },
590
591 function videoExtension (callback) {
592 video.save().asCallback(callback)
593 }
594
595 ], function (err: Error) {
596 if (err) {
597 // Autodesctruction...
598 video.destroy().asCallback(function (err) {
599 if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
600 }) 542 })
543 .then(() => {
544 const newVideoPath = join(videosDirectory, video.getVideoFilename())
545 return createTorrentFromVideo(video, newVideoPath)
546 })
547 .then(() => {
548 return video.save()
549 })
550 .then(() => {
551 return res()
552 })
553 .catch(err => {
554 // Autodesctruction...
555 video.destroy().asCallback(function (err) {
556 if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
557 })
601 558
602 return finalCallback(err) 559 return rej(err)
603 } 560 })
604
605 return finalCallback(null)
606 }) 561 })
607 }) 562 .run()
608 .run() 563 })
609} 564}
610 565
611// ------------------------------ STATICS ------------------------------ 566// ------------------------------ STATICS ------------------------------
612 567
613generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) { 568generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string) {
614 // Creating the thumbnail for a remote video 569 // Creating the thumbnail for a remote video
615 570
616 const thumbnailName = video.getThumbnailName() 571 const thumbnailName = video.getThumbnailName()
617 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) 572 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
618 fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) { 573 return writeFilePromise(thumbnailPath, Buffer.from(thumbnailData, 'binary')).then(() => {
619 if (err) return callback(err) 574 return thumbnailName
620
621 return callback(null, thumbnailName)
622 }) 575 })
623} 576}
624 577
625getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) { 578getDurationFromFile = function (videoPath: string) {
626 ffmpeg.ffprobe(videoPath, function (err, metadata) { 579 return new Promise<number>((res, rej) => {
627 if (err) return callback(err) 580 ffmpeg.ffprobe(videoPath, function (err, metadata) {
581 if (err) return rej(err)
628 582
629 return callback(null, Math.floor(metadata.format.duration)) 583 return res(Math.floor(metadata.format.duration))
584 })
630 }) 585 })
631} 586}
632 587
633list = function (callback: VideoMethods.ListCallback) { 588list = function () {
634 return Video.findAll().asCallback(callback) 589 return Video.findAll()
635} 590}
636 591
637listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) { 592listForApi = function (start: number, count: number, sort: string) {
638 // Exclude Blakclisted videos from the list 593 // Exclude Blakclisted videos from the list
639 const query = { 594 const query = {
640 distinct: true, 595 distinct: true,
@@ -652,14 +607,15 @@ listForApi = function (start: number, count: number, sort: string, callback: Vid
652 where: createBaseVideosWhere() 607 where: createBaseVideosWhere()
653 } 608 }
654 609
655 return Video.findAndCountAll(query).asCallback(function (err, result) { 610 return Video.findAndCountAll(query).then(({ rows, count }) => {
656 if (err) return callback(err) 611 return {
657 612 data: rows,
658 return callback(null, result.rows, result.count) 613 total: count
614 }
659 }) 615 })
660} 616}
661 617
662loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) { 618loadByHostAndRemoteId = function (fromHost: string, remoteId: string) {
663 const query = { 619 const query = {
664 where: { 620 where: {
665 remoteId: remoteId 621 remoteId: remoteId
@@ -680,10 +636,10 @@ loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback:
680 ] 636 ]
681 } 637 }
682 638
683 return Video.findOne(query).asCallback(callback) 639 return Video.findOne(query)
684} 640}
685 641
686listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) { 642listOwnedAndPopulateAuthorAndTags = function () {
687 // If remoteId is null this is *our* video 643 // If remoteId is null this is *our* video
688 const query = { 644 const query = {
689 where: { 645 where: {
@@ -692,10 +648,10 @@ listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAn
692 include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ] 648 include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
693 } 649 }
694 650
695 return Video.findAll(query).asCallback(callback) 651 return Video.findAll(query)
696} 652}
697 653
698listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) { 654listOwnedByAuthor = function (author: string) {
699 const query = { 655 const query = {
700 where: { 656 where: {
701 remoteId: null 657 remoteId: null
@@ -710,22 +666,22 @@ listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedBy
710 ] 666 ]
711 } 667 }
712 668
713 return Video.findAll(query).asCallback(callback) 669 return Video.findAll(query)
714} 670}
715 671
716load = function (id: string, callback: VideoMethods.LoadCallback) { 672load = function (id: string) {
717 return Video.findById(id).asCallback(callback) 673 return Video.findById(id)
718} 674}
719 675
720loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) { 676loadAndPopulateAuthor = function (id: string) {
721 const options = { 677 const options = {
722 include: [ Video['sequelize'].models.Author ] 678 include: [ Video['sequelize'].models.Author ]
723 } 679 }
724 680
725 return Video.findById(id, options).asCallback(callback) 681 return Video.findById(id, options)
726} 682}
727 683
728loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) { 684loadAndPopulateAuthorAndPodAndTags = function (id: string) {
729 const options = { 685 const options = {
730 include: [ 686 include: [
731 { 687 {
@@ -736,17 +692,10 @@ loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethod
736 ] 692 ]
737 } 693 }
738 694
739 return Video.findById(id, options).asCallback(callback) 695 return Video.findById(id, options)
740} 696}
741 697
742searchAndPopulateAuthorAndPodAndTags = function ( 698searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) {
743 value: string,
744 field: string,
745 start: number,
746 count: number,
747 sort: string,
748 callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback
749) {
750 const podInclude: any = { 699 const podInclude: any = {
751 model: Video['sequelize'].models.Pod, 700 model: Video['sequelize'].models.Pod,
752 required: false 701 required: false
@@ -778,7 +727,11 @@ searchAndPopulateAuthorAndPodAndTags = function (
778 } else if (field === 'tags') { 727 } else if (field === 'tags') {
779 const escapedValue = Video['sequelize'].escape('%' + value + '%') 728 const escapedValue = Video['sequelize'].escape('%' + value + '%')
780 query.where.id.$in = Video['sequelize'].literal( 729 query.where.id.$in = Video['sequelize'].literal(
781 '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')' 730 `(SELECT "VideoTags"."videoId"
731 FROM "Tags"
732 INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId"
733 WHERE name LIKE ${escapedValue}
734 )`
782 ) 735 )
783 } else if (field === 'host') { 736 } else if (field === 'host') {
784 // FIXME: Include our pod? (not stored in the database) 737 // FIXME: Include our pod? (not stored in the database)
@@ -810,10 +763,11 @@ searchAndPopulateAuthorAndPodAndTags = function (
810 // query.include.push([ Video['sequelize'].models.Tag ]) 763 // query.include.push([ Video['sequelize'].models.Tag ])
811 } 764 }
812 765
813 return Video.findAndCountAll(query).asCallback(function (err, result) { 766 return Video.findAndCountAll(query).then(({ rows, count }) => {
814 if (err) return callback(err) 767 return {
815 768 data: rows,
816 return callback(null, result.rows, result.count) 769 total: count
770 }
817 }) 771 })
818} 772}
819 773
@@ -829,27 +783,27 @@ function createBaseVideosWhere () {
829 } 783 }
830} 784}
831 785
832function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) { 786function removeThumbnail (video: VideoInstance) {
833 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) 787 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
834 fs.unlink(thumbnailPath, callback) 788 return unlinkPromise(thumbnailPath)
835} 789}
836 790
837function removeFile (video: VideoInstance, callback: (err: Error) => void) { 791function removeFile (video: VideoInstance) {
838 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) 792 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
839 fs.unlink(filePath, callback) 793 return unlinkPromise(filePath)
840} 794}
841 795
842function removeTorrent (video: VideoInstance, callback: (err: Error) => void) { 796function removeTorrent (video: VideoInstance) {
843 const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) 797 const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
844 fs.unlink(torrenPath, callback) 798 return unlinkPromise(torrenPath)
845} 799}
846 800
847function removePreview (video: VideoInstance, callback: (err: Error) => void) { 801function removePreview (video: VideoInstance) {
848 // Same name than video thumnail 802 // Same name than video thumnail
849 fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback) 803 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName())
850} 804}
851 805
852function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { 806function createTorrentFromVideo (video: VideoInstance, videoPath: string) {
853 const options = { 807 const options = {
854 announceList: [ 808 announceList: [
855 [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] 809 [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
@@ -859,30 +813,27 @@ function createTorrentFromVideo (video: VideoInstance, videoPath: string, callba
859 ] 813 ]
860 } 814 }
861 815
862 createTorrent(videoPath, options, function (err, torrent) { 816 return createTorrentPromise(videoPath, options)
863 if (err) return callback(err) 817 .then(torrent => {
864 818 const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
865 const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName()) 819 return writeFilePromise(filePath, torrent).then(() => torrent)
866 fs.writeFile(filePath, torrent, function (err) { 820 })
867 if (err) return callback(err) 821 .then(torrent => {
868
869 const parsedTorrent = parseTorrent(torrent) 822 const parsedTorrent = parseTorrent(torrent)
870 video.set('infoHash', parsedTorrent.infoHash) 823 video.set('infoHash', parsedTorrent.infoHash)
871 video.validate().asCallback(callback) 824 return video.validate()
872 }) 825 })
873 })
874} 826}
875 827
876function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { 828function createPreview (video: VideoInstance, videoPath: string) {
877 generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback) 829 return generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null)
878} 830}
879 831
880function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { 832function createThumbnail (video: VideoInstance, videoPath: string) {
881 generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback) 833 return generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE)
882} 834}
883 835
884type GenerateImageCallback = (err: Error, imageName: string) => void 836function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) {
885function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) {
886 const options: any = { 837 const options: any = {
887 filename: imageName, 838 filename: imageName,
888 count: 1, 839 count: 1,
@@ -893,29 +844,25 @@ function generateImage (video: VideoInstance, videoPath: string, folder: string,
893 options.size = size 844 options.size = size
894 } 845 }
895 846
896 ffmpeg(videoPath) 847 return new Promise<string>((res, rej) => {
897 .on('error', callback) 848 ffmpeg(videoPath)
898 .on('end', function () { 849 .on('error', rej)
899 callback(null, imageName) 850 .on('end', function () {
900 }) 851 return res(imageName)
901 .thumbnail(options) 852 })
853 .thumbnail(options)
854 })
902} 855}
903 856
904function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) { 857function removeFromBlacklist (video: VideoInstance) {
905 // Find the blacklisted video 858 // Find the blacklisted video
906 db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) { 859 return db.BlacklistedVideo.loadByVideoId(video.id).then(video => {
907 // If an error occured, stop here 860 // Not found the video, skip
908 if (err) { 861 if (!video) {
909 logger.error('Error when fetching video from blacklist.', { error: err }) 862 return null
910 return callback(err)
911 } 863 }
912 864
913 // If we found the video, remove it from the blacklist 865 // If we found the video, remove it from the blacklist
914 if (video) { 866 return video.destroy()
915 video.destroy().asCallback(callback)
916 } else {
917 // If haven't found it, simply ignore it and do nothing
918 return callback(null)
919 }
920 }) 867 })
921} 868}