diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 369 |
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 @@ | |||
1 | import * as safeBuffer from 'safe-buffer' | 1 | import * as safeBuffer from 'safe-buffer' |
2 | const Buffer = safeBuffer.Buffer | 2 | const Buffer = safeBuffer.Buffer |
3 | import * as createTorrent from 'create-torrent' | ||
4 | import * as ffmpeg from 'fluent-ffmpeg' | 3 | import * as ffmpeg from 'fluent-ffmpeg' |
5 | import * as fs from 'fs' | ||
6 | import * as magnetUtil from 'magnet-uri' | 4 | import * as magnetUtil from 'magnet-uri' |
7 | import { map, values } from 'lodash' | 5 | import { map, values } from 'lodash' |
8 | import { parallel, series } from 'async' | ||
9 | import * as parseTorrent from 'parse-torrent' | 6 | import * as parseTorrent from 'parse-torrent' |
10 | import { join } from 'path' | 7 | import { join } from 'path' |
11 | import * as Sequelize from 'sequelize' | 8 | import * as Sequelize from 'sequelize' |
9 | import * as Promise from 'bluebird' | ||
12 | 10 | ||
13 | import { database as db } from '../../initializers/database' | 11 | import { database as db } from '../../initializers/database' |
14 | import { VideoTagInstance } from './video-tag-interface' | 12 | import { TagInstance } from './tag-interface' |
15 | import { | 13 | import { |
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' |
26 | import { | 29 | import { |
27 | CONSTRAINTS_FIELDS, | 30 | CONSTRAINTS_FIELDS, |
@@ -37,7 +40,6 @@ import { JobScheduler, removeVideoToFriends } from '../../lib' | |||
37 | 40 | ||
38 | import { addMethodsToModel, getSort } from '../utils' | 41 | import { addMethodsToModel, getSort } from '../utils' |
39 | import { | 42 | import { |
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 | ||
278 | function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) { | 280 | function 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 | ||
322 | function afterDestroy (video: VideoInstance) { | 307 | function 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 | ||
500 | toAddRemoteJSON = function (this: VideoInstance, callback: VideoMethods.ToAddRemoteJSONCallback) { | 464 | toAddRemoteJSON = 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 | ||
558 | transcodeVideofile = function (this: VideoInstance, finalCallback: VideoMethods.TranscodeVideofileCallback) { | 518 | transcodeVideofile = 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 | ||
613 | generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) { | 568 | generateThumbnailFromData = 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 | ||
625 | getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) { | 578 | getDurationFromFile = 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 | ||
633 | list = function (callback: VideoMethods.ListCallback) { | 588 | list = function () { |
634 | return Video.findAll().asCallback(callback) | 589 | return Video.findAll() |
635 | } | 590 | } |
636 | 591 | ||
637 | listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) { | 592 | listForApi = 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 | ||
662 | loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) { | 618 | loadByHostAndRemoteId = 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 | ||
686 | listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) { | 642 | listOwnedAndPopulateAuthorAndTags = 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 | ||
698 | listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) { | 654 | listOwnedByAuthor = 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 | ||
716 | load = function (id: string, callback: VideoMethods.LoadCallback) { | 672 | load = function (id: string) { |
717 | return Video.findById(id).asCallback(callback) | 673 | return Video.findById(id) |
718 | } | 674 | } |
719 | 675 | ||
720 | loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) { | 676 | loadAndPopulateAuthor = 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 | ||
728 | loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) { | 684 | loadAndPopulateAuthorAndPodAndTags = 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 | ||
742 | searchAndPopulateAuthorAndPodAndTags = function ( | 698 | searchAndPopulateAuthorAndPodAndTags = 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 | ||
832 | function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) { | 786 | function 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 | ||
837 | function removeFile (video: VideoInstance, callback: (err: Error) => void) { | 791 | function 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 | ||
842 | function removeTorrent (video: VideoInstance, callback: (err: Error) => void) { | 796 | function 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 | ||
847 | function removePreview (video: VideoInstance, callback: (err: Error) => void) { | 801 | function 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 | ||
852 | function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 806 | function 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 | ||
876 | function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 828 | function 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 | ||
880 | function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) { | 832 | function 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 | ||
884 | type GenerateImageCallback = (err: Error, imageName: string) => void | 836 | function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string) { |
885 | function 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 | ||
904 | function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) { | 857 | function 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 | } |