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.ts318
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 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { maxBy, minBy } from 'lodash' 2import { maxBy, minBy } from 'lodash'
3import { join } from 'path' 3import { join } from 'path'
4import { 4import { 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'
16import { 5import {
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'
142import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' 130import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file'
143import { MThumbnail } from '../../typings/models/video/thumbnail' 131import { MThumbnail } from '../../typings/models/video/thumbnail'
@@ -145,74 +133,6 @@ import { VideoFile } from '@shared/models/videos/video-file.model'
145import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 133import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
146import validator from 'validator' 134import 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
149const 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
216export enum ScopeNames { 136export 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})
765export class VideoModel extends Model<VideoModel> { 749export 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)) {