diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 318 |
1 files changed, 151 insertions, 167 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index eacffe186..1ec8d717e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,18 +1,7 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { maxBy, minBy } from 'lodash' | 2 | import { maxBy, minBy } from 'lodash' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { | 4 | import { CountOptions, FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
5 | CountOptions, | ||
6 | FindOptions, | ||
7 | IncludeOptions, | ||
8 | ModelIndexesOptions, | ||
9 | Op, | ||
10 | QueryTypes, | ||
11 | ScopeOptions, | ||
12 | Sequelize, | ||
13 | Transaction, | ||
14 | WhereOptions | ||
15 | } from 'sequelize' | ||
16 | import { | 5 | import { |
17 | AllowNull, | 6 | AllowNull, |
18 | BeforeDestroy, | 7 | BeforeDestroy, |
@@ -136,8 +125,7 @@ import { | |||
136 | MVideoThumbnailBlacklist, | 125 | MVideoThumbnailBlacklist, |
137 | MVideoWithAllFiles, | 126 | MVideoWithAllFiles, |
138 | MVideoWithFile, | 127 | MVideoWithFile, |
139 | MVideoWithRights, | 128 | MVideoWithRights |
140 | MStreamingPlaylistFiles | ||
141 | } from '../../typings/models' | 129 | } from '../../typings/models' |
142 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' | 130 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' |
143 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 131 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
@@ -145,74 +133,6 @@ import { VideoFile } from '@shared/models/videos/video-file.model' | |||
145 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 133 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
146 | import validator from 'validator' | 134 | import validator from 'validator' |
147 | 135 | ||
148 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | ||
149 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ | ||
150 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
151 | |||
152 | { fields: [ 'createdAt' ] }, | ||
153 | { | ||
154 | fields: [ | ||
155 | { name: 'publishedAt', order: 'DESC' }, | ||
156 | { name: 'id', order: 'ASC' } | ||
157 | ] | ||
158 | }, | ||
159 | { fields: [ 'duration' ] }, | ||
160 | { fields: [ 'views' ] }, | ||
161 | { fields: [ 'channelId' ] }, | ||
162 | { | ||
163 | fields: [ 'originallyPublishedAt' ], | ||
164 | where: { | ||
165 | originallyPublishedAt: { | ||
166 | [Op.ne]: null | ||
167 | } | ||
168 | } | ||
169 | }, | ||
170 | { | ||
171 | fields: [ 'category' ], // We don't care videos with an unknown category | ||
172 | where: { | ||
173 | category: { | ||
174 | [Op.ne]: null | ||
175 | } | ||
176 | } | ||
177 | }, | ||
178 | { | ||
179 | fields: [ 'licence' ], // We don't care videos with an unknown licence | ||
180 | where: { | ||
181 | licence: { | ||
182 | [Op.ne]: null | ||
183 | } | ||
184 | } | ||
185 | }, | ||
186 | { | ||
187 | fields: [ 'language' ], // We don't care videos with an unknown language | ||
188 | where: { | ||
189 | language: { | ||
190 | [Op.ne]: null | ||
191 | } | ||
192 | } | ||
193 | }, | ||
194 | { | ||
195 | fields: [ 'nsfw' ], // Most of the videos are not NSFW | ||
196 | where: { | ||
197 | nsfw: true | ||
198 | } | ||
199 | }, | ||
200 | { | ||
201 | fields: [ 'remote' ], // Only index local videos | ||
202 | where: { | ||
203 | remote: false | ||
204 | } | ||
205 | }, | ||
206 | { | ||
207 | fields: [ 'uuid' ], | ||
208 | unique: true | ||
209 | }, | ||
210 | { | ||
211 | fields: [ 'url' ], | ||
212 | unique: true | ||
213 | } | ||
214 | ] | ||
215 | |||
216 | export enum ScopeNames { | 136 | export enum ScopeNames { |
217 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 137 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
218 | FOR_API = 'FOR_API', | 138 | FOR_API = 'FOR_API', |
@@ -267,7 +187,7 @@ export type AvailableForListIDsOptions = { | |||
267 | } | 187 | } |
268 | 188 | ||
269 | @Scopes(() => ({ | 189 | @Scopes(() => ({ |
270 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 190 | [ScopeNames.FOR_API]: (options: ForAPIOptions) => { |
271 | const query: FindOptions = { | 191 | const query: FindOptions = { |
272 | include: [ | 192 | include: [ |
273 | { | 193 | { |
@@ -292,7 +212,7 @@ export type AvailableForListIDsOptions = { | |||
292 | if (options.ids) { | 212 | if (options.ids) { |
293 | query.where = { | 213 | query.where = { |
294 | id: { | 214 | id: { |
295 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken | 215 | [Op.in]: options.ids |
296 | } | 216 | } |
297 | } | 217 | } |
298 | } | 218 | } |
@@ -316,7 +236,7 @@ export type AvailableForListIDsOptions = { | |||
316 | 236 | ||
317 | return query | 237 | return query |
318 | }, | 238 | }, |
319 | [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { | 239 | [ScopeNames.AVAILABLE_FOR_LIST_IDS]: (options: AvailableForListIDsOptions) => { |
320 | const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] | 240 | const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] |
321 | 241 | ||
322 | const query: FindOptions = { | 242 | const query: FindOptions = { |
@@ -327,11 +247,11 @@ export type AvailableForListIDsOptions = { | |||
327 | const attributesType = options.attributesType || 'id' | 247 | const attributesType = options.attributesType || 'id' |
328 | 248 | ||
329 | if (attributesType === 'id') query.attributes = [ 'id' ] | 249 | if (attributesType === 'id') query.attributes = [ 'id' ] |
330 | else if (attributesType === 'none') query.attributes = [ ] | 250 | else if (attributesType === 'none') query.attributes = [] |
331 | 251 | ||
332 | whereAnd.push({ | 252 | whereAnd.push({ |
333 | id: { | 253 | id: { |
334 | [ Op.notIn ]: Sequelize.literal( | 254 | [Op.notIn]: Sequelize.literal( |
335 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | 255 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' |
336 | ) | 256 | ) |
337 | } | 257 | } |
@@ -340,7 +260,7 @@ export type AvailableForListIDsOptions = { | |||
340 | if (options.serverAccountId) { | 260 | if (options.serverAccountId) { |
341 | whereAnd.push({ | 261 | whereAnd.push({ |
342 | channelId: { | 262 | channelId: { |
343 | [ Op.notIn ]: Sequelize.literal( | 263 | [Op.notIn]: Sequelize.literal( |
344 | '(' + | 264 | '(' + |
345 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | 265 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + |
346 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | 266 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + |
@@ -353,15 +273,14 @@ export type AvailableForListIDsOptions = { | |||
353 | 273 | ||
354 | // Only list public/published videos | 274 | // Only list public/published videos |
355 | if (!options.filter || options.filter !== 'all-local') { | 275 | if (!options.filter || options.filter !== 'all-local') { |
356 | |||
357 | const publishWhere = { | 276 | const publishWhere = { |
358 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | 277 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding |
359 | [ Op.or ]: [ | 278 | [Op.or]: [ |
360 | { | 279 | { |
361 | state: VideoState.PUBLISHED | 280 | state: VideoState.PUBLISHED |
362 | }, | 281 | }, |
363 | { | 282 | { |
364 | [ Op.and ]: { | 283 | [Op.and]: { |
365 | state: VideoState.TO_TRANSCODE, | 284 | state: VideoState.TO_TRANSCODE, |
366 | waitTranscoding: false | 285 | waitTranscoding: false |
367 | } | 286 | } |
@@ -448,7 +367,7 @@ export type AvailableForListIDsOptions = { | |||
448 | [Op.or]: [ | 367 | [Op.or]: [ |
449 | { | 368 | { |
450 | id: { | 369 | id: { |
451 | [ Op.in ]: Sequelize.literal( | 370 | [Op.in]: Sequelize.literal( |
452 | '(' + | 371 | '(' + |
453 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | 372 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + |
454 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 373 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
@@ -459,7 +378,7 @@ export type AvailableForListIDsOptions = { | |||
459 | }, | 378 | }, |
460 | { | 379 | { |
461 | id: { | 380 | id: { |
462 | [ Op.in ]: Sequelize.literal( | 381 | [Op.in]: Sequelize.literal( |
463 | '(' + | 382 | '(' + |
464 | 'SELECT "video"."id" AS "id" FROM "video" ' + | 383 | 'SELECT "video"."id" AS "id" FROM "video" ' + |
465 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 384 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
@@ -479,7 +398,7 @@ export type AvailableForListIDsOptions = { | |||
479 | if (options.withFiles === true) { | 398 | if (options.withFiles === true) { |
480 | whereAnd.push({ | 399 | whereAnd.push({ |
481 | id: { | 400 | id: { |
482 | [ Op.in ]: Sequelize.literal( | 401 | [Op.in]: Sequelize.literal( |
483 | '(SELECT "videoId" FROM "videoFile")' | 402 | '(SELECT "videoId" FROM "videoFile")' |
484 | ) | 403 | ) |
485 | } | 404 | } |
@@ -493,7 +412,7 @@ export type AvailableForListIDsOptions = { | |||
493 | 412 | ||
494 | whereAnd.push({ | 413 | whereAnd.push({ |
495 | id: { | 414 | id: { |
496 | [ Op.in ]: Sequelize.literal( | 415 | [Op.in]: Sequelize.literal( |
497 | '(' + | 416 | '(' + |
498 | 'SELECT "videoId" FROM "videoTag" ' + | 417 | 'SELECT "videoId" FROM "videoTag" ' + |
499 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 418 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -509,7 +428,7 @@ export type AvailableForListIDsOptions = { | |||
509 | 428 | ||
510 | whereAnd.push({ | 429 | whereAnd.push({ |
511 | id: { | 430 | id: { |
512 | [ Op.in ]: Sequelize.literal( | 431 | [Op.in]: Sequelize.literal( |
513 | '(' + | 432 | '(' + |
514 | 'SELECT "videoId" FROM "videoTag" ' + | 433 | 'SELECT "videoId" FROM "videoTag" ' + |
515 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 434 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -529,7 +448,7 @@ export type AvailableForListIDsOptions = { | |||
529 | if (options.categoryOneOf) { | 448 | if (options.categoryOneOf) { |
530 | whereAnd.push({ | 449 | whereAnd.push({ |
531 | category: { | 450 | category: { |
532 | [ Op.or ]: options.categoryOneOf | 451 | [Op.or]: options.categoryOneOf |
533 | } | 452 | } |
534 | }) | 453 | }) |
535 | } | 454 | } |
@@ -537,7 +456,7 @@ export type AvailableForListIDsOptions = { | |||
537 | if (options.licenceOneOf) { | 456 | if (options.licenceOneOf) { |
538 | whereAnd.push({ | 457 | whereAnd.push({ |
539 | licence: { | 458 | licence: { |
540 | [ Op.or ]: options.licenceOneOf | 459 | [Op.or]: options.licenceOneOf |
541 | } | 460 | } |
542 | }) | 461 | }) |
543 | } | 462 | } |
@@ -552,12 +471,12 @@ export type AvailableForListIDsOptions = { | |||
552 | [Op.or]: [ | 471 | [Op.or]: [ |
553 | { | 472 | { |
554 | language: { | 473 | language: { |
555 | [ Op.or ]: videoLanguages | 474 | [Op.or]: videoLanguages |
556 | } | 475 | } |
557 | }, | 476 | }, |
558 | { | 477 | { |
559 | id: { | 478 | id: { |
560 | [ Op.in ]: Sequelize.literal( | 479 | [Op.in]: Sequelize.literal( |
561 | '(' + | 480 | '(' + |
562 | 'SELECT "videoId" FROM "videoCaption" ' + | 481 | 'SELECT "videoId" FROM "videoCaption" ' + |
563 | 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + | 482 | 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + |
@@ -591,12 +510,12 @@ export type AvailableForListIDsOptions = { | |||
591 | } | 510 | } |
592 | 511 | ||
593 | query.where = { | 512 | query.where = { |
594 | [ Op.and ]: whereAnd | 513 | [Op.and]: whereAnd |
595 | } | 514 | } |
596 | 515 | ||
597 | return query | 516 | return query |
598 | }, | 517 | }, |
599 | [ ScopeNames.WITH_THUMBNAILS ]: { | 518 | [ScopeNames.WITH_THUMBNAILS]: { |
600 | include: [ | 519 | include: [ |
601 | { | 520 | { |
602 | model: ThumbnailModel, | 521 | model: ThumbnailModel, |
@@ -604,7 +523,7 @@ export type AvailableForListIDsOptions = { | |||
604 | } | 523 | } |
605 | ] | 524 | ] |
606 | }, | 525 | }, |
607 | [ ScopeNames.WITH_USER_ID ]: { | 526 | [ScopeNames.WITH_USER_ID]: { |
608 | include: [ | 527 | include: [ |
609 | { | 528 | { |
610 | attributes: [ 'accountId' ], | 529 | attributes: [ 'accountId' ], |
@@ -620,7 +539,7 @@ export type AvailableForListIDsOptions = { | |||
620 | } | 539 | } |
621 | ] | 540 | ] |
622 | }, | 541 | }, |
623 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 542 | [ScopeNames.WITH_ACCOUNT_DETAILS]: { |
624 | include: [ | 543 | include: [ |
625 | { | 544 | { |
626 | model: VideoChannelModel.unscoped(), | 545 | model: VideoChannelModel.unscoped(), |
@@ -672,10 +591,10 @@ export type AvailableForListIDsOptions = { | |||
672 | } | 591 | } |
673 | ] | 592 | ] |
674 | }, | 593 | }, |
675 | [ ScopeNames.WITH_TAGS ]: { | 594 | [ScopeNames.WITH_TAGS]: { |
676 | include: [ TagModel ] | 595 | include: [ TagModel ] |
677 | }, | 596 | }, |
678 | [ ScopeNames.WITH_BLACKLISTED ]: { | 597 | [ScopeNames.WITH_BLACKLISTED]: { |
679 | include: [ | 598 | include: [ |
680 | { | 599 | { |
681 | attributes: [ 'id', 'reason', 'unfederated' ], | 600 | attributes: [ 'id', 'reason', 'unfederated' ], |
@@ -684,7 +603,7 @@ export type AvailableForListIDsOptions = { | |||
684 | } | 603 | } |
685 | ] | 604 | ] |
686 | }, | 605 | }, |
687 | [ ScopeNames.WITH_WEBTORRENT_FILES ]: (withRedundancies = false) => { | 606 | [ScopeNames.WITH_WEBTORRENT_FILES]: (withRedundancies = false) => { |
688 | let subInclude: any[] = [] | 607 | let subInclude: any[] = [] |
689 | 608 | ||
690 | if (withRedundancies === true) { | 609 | if (withRedundancies === true) { |
@@ -708,7 +627,7 @@ export type AvailableForListIDsOptions = { | |||
708 | ] | 627 | ] |
709 | } | 628 | } |
710 | }, | 629 | }, |
711 | [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { | 630 | [ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => { |
712 | const subInclude: IncludeOptions[] = [ | 631 | const subInclude: IncludeOptions[] = [ |
713 | { | 632 | { |
714 | model: VideoFileModel.unscoped(), | 633 | model: VideoFileModel.unscoped(), |
@@ -735,7 +654,7 @@ export type AvailableForListIDsOptions = { | |||
735 | ] | 654 | ] |
736 | } | 655 | } |
737 | }, | 656 | }, |
738 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 657 | [ScopeNames.WITH_SCHEDULED_UPDATE]: { |
739 | include: [ | 658 | include: [ |
740 | { | 659 | { |
741 | model: ScheduleVideoUpdateModel.unscoped(), | 660 | model: ScheduleVideoUpdateModel.unscoped(), |
@@ -743,7 +662,7 @@ export type AvailableForListIDsOptions = { | |||
743 | } | 662 | } |
744 | ] | 663 | ] |
745 | }, | 664 | }, |
746 | [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { | 665 | [ScopeNames.WITH_USER_HISTORY]: (userId: number) => { |
747 | return { | 666 | return { |
748 | include: [ | 667 | include: [ |
749 | { | 668 | { |
@@ -760,7 +679,72 @@ export type AvailableForListIDsOptions = { | |||
760 | })) | 679 | })) |
761 | @Table({ | 680 | @Table({ |
762 | tableName: 'video', | 681 | tableName: 'video', |
763 | indexes | 682 | indexes: [ |
683 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
684 | |||
685 | { fields: [ 'createdAt' ] }, | ||
686 | { | ||
687 | fields: [ | ||
688 | { name: 'publishedAt', order: 'DESC' }, | ||
689 | { name: 'id', order: 'ASC' } | ||
690 | ] | ||
691 | }, | ||
692 | { fields: [ 'duration' ] }, | ||
693 | { fields: [ 'views' ] }, | ||
694 | { fields: [ 'channelId' ] }, | ||
695 | { | ||
696 | fields: [ 'originallyPublishedAt' ], | ||
697 | where: { | ||
698 | originallyPublishedAt: { | ||
699 | [Op.ne]: null | ||
700 | } | ||
701 | } | ||
702 | }, | ||
703 | { | ||
704 | fields: [ 'category' ], // We don't care videos with an unknown category | ||
705 | where: { | ||
706 | category: { | ||
707 | [Op.ne]: null | ||
708 | } | ||
709 | } | ||
710 | }, | ||
711 | { | ||
712 | fields: [ 'licence' ], // We don't care videos with an unknown licence | ||
713 | where: { | ||
714 | licence: { | ||
715 | [Op.ne]: null | ||
716 | } | ||
717 | } | ||
718 | }, | ||
719 | { | ||
720 | fields: [ 'language' ], // We don't care videos with an unknown language | ||
721 | where: { | ||
722 | language: { | ||
723 | [Op.ne]: null | ||
724 | } | ||
725 | } | ||
726 | }, | ||
727 | { | ||
728 | fields: [ 'nsfw' ], // Most of the videos are not NSFW | ||
729 | where: { | ||
730 | nsfw: true | ||
731 | } | ||
732 | }, | ||
733 | { | ||
734 | fields: [ 'remote' ], // Only index local videos | ||
735 | where: { | ||
736 | remote: false | ||
737 | } | ||
738 | }, | ||
739 | { | ||
740 | fields: [ 'uuid' ], | ||
741 | unique: true | ||
742 | }, | ||
743 | { | ||
744 | fields: [ 'url' ], | ||
745 | unique: true | ||
746 | } | ||
747 | ] | ||
764 | }) | 748 | }) |
765 | export class VideoModel extends Model<VideoModel> { | 749 | export class VideoModel extends Model<VideoModel> { |
766 | 750 | ||
@@ -1031,7 +1015,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1031 | }, | 1015 | }, |
1032 | onDelete: 'cascade', | 1016 | onDelete: 'cascade', |
1033 | hooks: true, | 1017 | hooks: true, |
1034 | [ 'separate' as any ]: true | 1018 | ['separate' as any]: true |
1035 | }) | 1019 | }) |
1036 | VideoCaptions: VideoCaptionModel[] | 1020 | VideoCaptions: VideoCaptionModel[] |
1037 | 1021 | ||
@@ -1127,16 +1111,16 @@ export class VideoModel extends Model<VideoModel> { | |||
1127 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings | 1111 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings |
1128 | where: { | 1112 | where: { |
1129 | id: { | 1113 | id: { |
1130 | [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') | 1114 | [Op.in]: Sequelize.literal('(' + rawQuery + ')') |
1131 | }, | 1115 | }, |
1132 | [ Op.or ]: [ | 1116 | [Op.or]: [ |
1133 | { privacy: VideoPrivacy.PUBLIC }, | 1117 | { privacy: VideoPrivacy.PUBLIC }, |
1134 | { privacy: VideoPrivacy.UNLISTED } | 1118 | { privacy: VideoPrivacy.UNLISTED } |
1135 | ] | 1119 | ] |
1136 | }, | 1120 | }, |
1137 | include: [ | 1121 | include: [ |
1138 | { | 1122 | { |
1139 | attributes: [ 'language' ], | 1123 | attributes: [ 'language', 'fileUrl' ], |
1140 | model: VideoCaptionModel.unscoped(), | 1124 | model: VideoCaptionModel.unscoped(), |
1141 | required: false | 1125 | required: false |
1142 | }, | 1126 | }, |
@@ -1146,10 +1130,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1146 | required: false, | 1130 | required: false, |
1147 | // We only want videos shared by this actor | 1131 | // We only want videos shared by this actor |
1148 | where: { | 1132 | where: { |
1149 | [ Op.and ]: [ | 1133 | [Op.and]: [ |
1150 | { | 1134 | { |
1151 | id: { | 1135 | id: { |
1152 | [ Op.not ]: null | 1136 | [Op.not]: null |
1153 | } | 1137 | } |
1154 | }, | 1138 | }, |
1155 | { | 1139 | { |
@@ -1199,8 +1183,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1199 | // totals: totalVideos + totalVideoShares | 1183 | // totals: totalVideos + totalVideoShares |
1200 | let totalVideos = 0 | 1184 | let totalVideos = 0 |
1201 | let totalVideoShares = 0 | 1185 | let totalVideoShares = 0 |
1202 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) | 1186 | if (totals[0]) totalVideos = parseInt(totals[0].total, 10) |
1203 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) | 1187 | if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) |
1204 | 1188 | ||
1205 | const total = totalVideos + totalVideoShares | 1189 | const total = totalVideos + totalVideoShares |
1206 | return { | 1190 | return { |
@@ -1243,7 +1227,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1243 | baseQuery = Object.assign(baseQuery, { | 1227 | baseQuery = Object.assign(baseQuery, { |
1244 | where: { | 1228 | where: { |
1245 | name: { | 1229 | name: { |
1246 | [ Op.iLike ]: '%' + search + '%' | 1230 | [Op.iLike]: '%' + search + '%' |
1247 | } | 1231 | } |
1248 | } | 1232 | } |
1249 | }) | 1233 | }) |
@@ -1273,25 +1257,25 @@ export class VideoModel extends Model<VideoModel> { | |||
1273 | } | 1257 | } |
1274 | 1258 | ||
1275 | static async listForApi (options: { | 1259 | static async listForApi (options: { |
1276 | start: number, | 1260 | start: number |
1277 | count: number, | 1261 | count: number |
1278 | sort: string, | 1262 | sort: string |
1279 | nsfw: boolean, | 1263 | nsfw: boolean |
1280 | includeLocalVideos: boolean, | 1264 | includeLocalVideos: boolean |
1281 | withFiles: boolean, | 1265 | withFiles: boolean |
1282 | categoryOneOf?: number[], | 1266 | categoryOneOf?: number[] |
1283 | licenceOneOf?: number[], | 1267 | licenceOneOf?: number[] |
1284 | languageOneOf?: string[], | 1268 | languageOneOf?: string[] |
1285 | tagsOneOf?: string[], | 1269 | tagsOneOf?: string[] |
1286 | tagsAllOf?: string[], | 1270 | tagsAllOf?: string[] |
1287 | filter?: VideoFilter, | 1271 | filter?: VideoFilter |
1288 | accountId?: number, | 1272 | accountId?: number |
1289 | videoChannelId?: number, | 1273 | videoChannelId?: number |
1290 | followerActorId?: number | 1274 | followerActorId?: number |
1291 | videoPlaylistId?: number, | 1275 | videoPlaylistId?: number |
1292 | trendingDays?: number, | 1276 | trendingDays?: number |
1293 | user?: MUserAccountId, | 1277 | user?: MUserAccountId |
1294 | historyOfUser?: MUserId, | 1278 | historyOfUser?: MUserId |
1295 | countVideos?: boolean | 1279 | countVideos?: boolean |
1296 | }) { | 1280 | }) { |
1297 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1281 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
@@ -1357,7 +1341,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1357 | tagsAllOf?: string[] | 1341 | tagsAllOf?: string[] |
1358 | durationMin?: number // seconds | 1342 | durationMin?: number // seconds |
1359 | durationMax?: number // seconds | 1343 | durationMax?: number // seconds |
1360 | user?: MUserAccountId, | 1344 | user?: MUserAccountId |
1361 | filter?: VideoFilter | 1345 | filter?: VideoFilter |
1362 | }) { | 1346 | }) { |
1363 | const whereAnd = [] | 1347 | const whereAnd = [] |
@@ -1365,8 +1349,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1365 | if (options.startDate || options.endDate) { | 1349 | if (options.startDate || options.endDate) { |
1366 | const publishedAtRange = {} | 1350 | const publishedAtRange = {} |
1367 | 1351 | ||
1368 | if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate | 1352 | if (options.startDate) publishedAtRange[Op.gte] = options.startDate |
1369 | if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate | 1353 | if (options.endDate) publishedAtRange[Op.lte] = options.endDate |
1370 | 1354 | ||
1371 | whereAnd.push({ publishedAt: publishedAtRange }) | 1355 | whereAnd.push({ publishedAt: publishedAtRange }) |
1372 | } | 1356 | } |
@@ -1374,8 +1358,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1374 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { | 1358 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { |
1375 | const originallyPublishedAtRange = {} | 1359 | const originallyPublishedAtRange = {} |
1376 | 1360 | ||
1377 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate | 1361 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[Op.gte] = options.originallyPublishedStartDate |
1378 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate | 1362 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[Op.lte] = options.originallyPublishedEndDate |
1379 | 1363 | ||
1380 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) | 1364 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) |
1381 | } | 1365 | } |
@@ -1383,8 +1367,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1383 | if (options.durationMin || options.durationMax) { | 1367 | if (options.durationMin || options.durationMax) { |
1384 | const durationRange = {} | 1368 | const durationRange = {} |
1385 | 1369 | ||
1386 | if (options.durationMin) durationRange[ Op.gte ] = options.durationMin | 1370 | if (options.durationMin) durationRange[Op.gte] = options.durationMin |
1387 | if (options.durationMax) durationRange[ Op.lte ] = options.durationMax | 1371 | if (options.durationMax) durationRange[Op.lte] = options.durationMax |
1388 | 1372 | ||
1389 | whereAnd.push({ duration: durationRange }) | 1373 | whereAnd.push({ duration: durationRange }) |
1390 | } | 1374 | } |
@@ -1395,7 +1379,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1395 | if (options.search) { | 1379 | if (options.search) { |
1396 | const trigramSearch = { | 1380 | const trigramSearch = { |
1397 | id: { | 1381 | id: { |
1398 | [ Op.in ]: Sequelize.literal( | 1382 | [Op.in]: Sequelize.literal( |
1399 | '(' + | 1383 | '(' + |
1400 | 'SELECT "video"."id" FROM "video" ' + | 1384 | 'SELECT "video"."id" FROM "video" ' + |
1401 | 'WHERE ' + | 1385 | 'WHERE ' + |
@@ -1593,8 +1577,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1593 | } | 1577 | } |
1594 | 1578 | ||
1595 | static loadForGetAPI (parameters: { | 1579 | static loadForGetAPI (parameters: { |
1596 | id: number | string, | 1580 | id: number | string |
1597 | t?: Transaction, | 1581 | t?: Transaction |
1598 | userId?: number | 1582 | userId?: number |
1599 | }): Bluebird<MVideoDetails> { | 1583 | }): Bluebird<MVideoDetails> { |
1600 | const { id, t, userId } = parameters | 1584 | const { id, t, userId } = parameters |
@@ -1660,9 +1644,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1660 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { | 1644 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { |
1661 | // Instances only share videos | 1645 | // Instances only share videos |
1662 | const query = 'SELECT 1 FROM "videoShare" ' + | 1646 | const query = 'SELECT 1 FROM "videoShare" ' + |
1663 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 1647 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
1664 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + | 1648 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + |
1665 | 'LIMIT 1' | 1649 | 'LIMIT 1' |
1666 | 1650 | ||
1667 | const options = { | 1651 | const options = { |
1668 | type: QueryTypes.SELECT as QueryTypes.SELECT, | 1652 | type: QueryTypes.SELECT as QueryTypes.SELECT, |
@@ -1694,7 +1678,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1694 | } | 1678 | } |
1695 | 1679 | ||
1696 | return VideoModel.findAll(query) | 1680 | return VideoModel.findAll(query) |
1697 | .then(videos => videos.map(v => v.id)) | 1681 | .then(videos => videos.map(v => v.id)) |
1698 | } | 1682 | } |
1699 | 1683 | ||
1700 | // threshold corresponds to how many video the field should have to be returned | 1684 | // threshold corresponds to how many video the field should have to be returned |
@@ -1714,14 +1698,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1714 | limit: count, | 1698 | limit: count, |
1715 | group: field, | 1699 | group: field, |
1716 | having: Sequelize.where( | 1700 | having: Sequelize.where( |
1717 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } | 1701 | Sequelize.fn('COUNT', Sequelize.col(field)), { [Op.gte]: threshold } |
1718 | ), | 1702 | ), |
1719 | order: [ (this.sequelize as any).random() ] | 1703 | order: [ (this.sequelize as any).random() ] |
1720 | } | 1704 | } |
1721 | 1705 | ||
1722 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) | 1706 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) |
1723 | .findAll(query) | 1707 | .findAll(query) |
1724 | .then(rows => rows.map(r => r[ field ])) | 1708 | .then(rows => rows.map(r => r[field])) |
1725 | } | 1709 | } |
1726 | 1710 | ||
1727 | static buildTrendingQuery (trendingDays: number) { | 1711 | static buildTrendingQuery (trendingDays: number) { |
@@ -1732,7 +1716,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1732 | required: false, | 1716 | required: false, |
1733 | where: { | 1717 | where: { |
1734 | startDate: { | 1718 | startDate: { |
1735 | [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) | 1719 | [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) |
1736 | } | 1720 | } |
1737 | } | 1721 | } |
1738 | } | 1722 | } |
@@ -1815,23 +1799,23 @@ export class VideoModel extends Model<VideoModel> { | |||
1815 | } | 1799 | } |
1816 | 1800 | ||
1817 | static getCategoryLabel (id: number) { | 1801 | static getCategoryLabel (id: number) { |
1818 | return VIDEO_CATEGORIES[ id ] || 'Misc' | 1802 | return VIDEO_CATEGORIES[id] || 'Misc' |
1819 | } | 1803 | } |
1820 | 1804 | ||
1821 | static getLicenceLabel (id: number) { | 1805 | static getLicenceLabel (id: number) { |
1822 | return VIDEO_LICENCES[ id ] || 'Unknown' | 1806 | return VIDEO_LICENCES[id] || 'Unknown' |
1823 | } | 1807 | } |
1824 | 1808 | ||
1825 | static getLanguageLabel (id: string) { | 1809 | static getLanguageLabel (id: string) { |
1826 | return VIDEO_LANGUAGES[ id ] || 'Unknown' | 1810 | return VIDEO_LANGUAGES[id] || 'Unknown' |
1827 | } | 1811 | } |
1828 | 1812 | ||
1829 | static getPrivacyLabel (id: number) { | 1813 | static getPrivacyLabel (id: number) { |
1830 | return VIDEO_PRIVACIES[ id ] || 'Unknown' | 1814 | return VIDEO_PRIVACIES[id] || 'Unknown' |
1831 | } | 1815 | } |
1832 | 1816 | ||
1833 | static getStateLabel (id: number) { | 1817 | static getStateLabel (id: number) { |
1834 | return VIDEO_STATES[ id ] || 'Unknown' | 1818 | return VIDEO_STATES[id] || 'Unknown' |
1835 | } | 1819 | } |
1836 | 1820 | ||
1837 | isBlacklisted () { | 1821 | isBlacklisted () { |
@@ -1843,7 +1827,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1843 | this.VideoChannel.Account.isBlocked() | 1827 | this.VideoChannel.Account.isBlocked() |
1844 | } | 1828 | } |
1845 | 1829 | ||
1846 | getQualityFileBy <T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { | 1830 | getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { |
1847 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { | 1831 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { |
1848 | const file = fun(this.VideoFiles, file => file.resolution) | 1832 | const file = fun(this.VideoFiles, file => file.resolution) |
1849 | 1833 | ||
@@ -1861,15 +1845,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1861 | return undefined | 1845 | return undefined |
1862 | } | 1846 | } |
1863 | 1847 | ||
1864 | getMaxQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1848 | getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1865 | return this.getQualityFileBy(maxBy) | 1849 | return this.getQualityFileBy(maxBy) |
1866 | } | 1850 | } |
1867 | 1851 | ||
1868 | getMinQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1852 | getMinQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1869 | return this.getQualityFileBy(minBy) | 1853 | return this.getQualityFileBy(minBy) |
1870 | } | 1854 | } |
1871 | 1855 | ||
1872 | getWebTorrentFile <T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { | 1856 | getWebTorrentFile<T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { |
1873 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1857 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1874 | 1858 | ||
1875 | const file = this.VideoFiles.find(f => f.resolution === resolution) | 1859 | const file = this.VideoFiles.find(f => f.resolution === resolution) |
@@ -1992,8 +1976,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1992 | } | 1976 | } |
1993 | 1977 | ||
1994 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists | 1978 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists |
1995 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) | 1979 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) |
1996 | .concat(toAdd) | 1980 | .concat(toAdd) |
1997 | } | 1981 | } |
1998 | 1982 | ||
1999 | removeFile (videoFile: MVideoFile, isRedundancy = false) { | 1983 | removeFile (videoFile: MVideoFile, isRedundancy = false) { |
@@ -2014,7 +1998,7 @@ export class VideoModel extends Model<VideoModel> { | |||
2014 | await remove(directoryPath) | 1998 | await remove(directoryPath) |
2015 | 1999 | ||
2016 | if (isRedundancy !== true) { | 2000 | if (isRedundancy !== true) { |
2017 | let streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo | 2001 | const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo |
2018 | streamingPlaylistWithFiles.Video = this | 2002 | streamingPlaylistWithFiles.Video = this |
2019 | 2003 | ||
2020 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { | 2004 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { |