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.ts179
1 files changed, 91 insertions, 88 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 329cebd28..18f18795e 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -227,12 +227,12 @@ type AvailableForListIDsOptions = {
227 historyOfUser?: UserModel 227 historyOfUser?: UserModel
228} 228}
229 229
230@Scopes({ 230@Scopes(() => ({
231 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { 231 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
232 const query: FindOptions = { 232 const query: FindOptions = {
233 where: { 233 where: {
234 id: { 234 id: {
235 [ Op.in ]: options.ids // FIXME: sequelize any seems broken 235 [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
236 } 236 }
237 }, 237 },
238 include: [ 238 include: [
@@ -486,7 +486,7 @@ type AvailableForListIDsOptions = {
486 [ ScopeNames.WITH_THUMBNAILS ]: { 486 [ ScopeNames.WITH_THUMBNAILS ]: {
487 include: [ 487 include: [
488 { 488 {
489 model: () => ThumbnailModel, 489 model: ThumbnailModel,
490 required: false 490 required: false
491 } 491 }
492 ] 492 ]
@@ -495,48 +495,48 @@ type AvailableForListIDsOptions = {
495 include: [ 495 include: [
496 { 496 {
497 attributes: [ 'accountId' ], 497 attributes: [ 'accountId' ],
498 model: () => VideoChannelModel.unscoped(), 498 model: VideoChannelModel.unscoped(),
499 required: true, 499 required: true,
500 include: [ 500 include: [
501 { 501 {
502 attributes: [ 'userId' ], 502 attributes: [ 'userId' ],
503 model: () => AccountModel.unscoped(), 503 model: AccountModel.unscoped(),
504 required: true 504 required: true
505 } 505 }
506 ] 506 ]
507 } 507 }
508 ] as any // FIXME: sequelize typings 508 ]
509 }, 509 },
510 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { 510 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
511 include: [ 511 include: [
512 { 512 {
513 model: () => VideoChannelModel.unscoped(), 513 model: VideoChannelModel.unscoped(),
514 required: true, 514 required: true,
515 include: [ 515 include: [
516 { 516 {
517 attributes: { 517 attributes: {
518 exclude: [ 'privateKey', 'publicKey' ] 518 exclude: [ 'privateKey', 'publicKey' ]
519 }, 519 },
520 model: () => ActorModel.unscoped(), 520 model: ActorModel.unscoped(),
521 required: true, 521 required: true,
522 include: [ 522 include: [
523 { 523 {
524 attributes: [ 'host' ], 524 attributes: [ 'host' ],
525 model: () => ServerModel.unscoped(), 525 model: ServerModel.unscoped(),
526 required: false 526 required: false
527 }, 527 },
528 { 528 {
529 model: () => AvatarModel.unscoped(), 529 model: AvatarModel.unscoped(),
530 required: false 530 required: false
531 } 531 }
532 ] 532 ]
533 }, 533 },
534 { 534 {
535 model: () => AccountModel.unscoped(), 535 model: AccountModel.unscoped(),
536 required: true, 536 required: true,
537 include: [ 537 include: [
538 { 538 {
539 model: () => ActorModel.unscoped(), 539 model: ActorModel.unscoped(),
540 attributes: { 540 attributes: {
541 exclude: [ 'privateKey', 'publicKey' ] 541 exclude: [ 'privateKey', 'publicKey' ]
542 }, 542 },
@@ -544,11 +544,11 @@ type AvailableForListIDsOptions = {
544 include: [ 544 include: [
545 { 545 {
546 attributes: [ 'host' ], 546 attributes: [ 'host' ],
547 model: () => ServerModel.unscoped(), 547 model: ServerModel.unscoped(),
548 required: false 548 required: false
549 }, 549 },
550 { 550 {
551 model: () => AvatarModel.unscoped(), 551 model: AvatarModel.unscoped(),
552 required: false 552 required: false
553 } 553 }
554 ] 554 ]
@@ -557,16 +557,16 @@ type AvailableForListIDsOptions = {
557 } 557 }
558 ] 558 ]
559 } 559 }
560 ] as any // FIXME: sequelize typings 560 ]
561 }, 561 },
562 [ ScopeNames.WITH_TAGS ]: { 562 [ ScopeNames.WITH_TAGS ]: {
563 include: [ () => TagModel ] 563 include: [ TagModel ]
564 }, 564 },
565 [ ScopeNames.WITH_BLACKLISTED ]: { 565 [ ScopeNames.WITH_BLACKLISTED ]: {
566 include: [ 566 include: [
567 { 567 {
568 attributes: [ 'id', 'reason' ], 568 attributes: [ 'id', 'reason' ],
569 model: () => VideoBlacklistModel, 569 model: VideoBlacklistModel,
570 required: false 570 required: false
571 } 571 }
572 ] 572 ]
@@ -588,8 +588,7 @@ type AvailableForListIDsOptions = {
588 include: [ 588 include: [
589 { 589 {
590 model: VideoFileModel.unscoped(), 590 model: VideoFileModel.unscoped(),
591 // FIXME: typings 591 separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
592 [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
593 required: false, 592 required: false,
594 include: subInclude 593 include: subInclude
595 } 594 }
@@ -613,8 +612,7 @@ type AvailableForListIDsOptions = {
613 include: [ 612 include: [
614 { 613 {
615 model: VideoStreamingPlaylistModel.unscoped(), 614 model: VideoStreamingPlaylistModel.unscoped(),
616 // FIXME: typings 615 separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
617 [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
618 required: false, 616 required: false,
619 include: subInclude 617 include: subInclude
620 } 618 }
@@ -624,7 +622,7 @@ type AvailableForListIDsOptions = {
624 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { 622 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
625 include: [ 623 include: [
626 { 624 {
627 model: () => ScheduleVideoUpdateModel.unscoped(), 625 model: ScheduleVideoUpdateModel.unscoped(),
628 required: false 626 required: false
629 } 627 }
630 ] 628 ]
@@ -643,7 +641,7 @@ type AvailableForListIDsOptions = {
643 ] 641 ]
644 } 642 }
645 } 643 }
646}) 644}))
647@Table({ 645@Table({
648 tableName: 'video', 646 tableName: 'video',
649 indexes 647 indexes
@@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> {
1075 } 1073 }
1076 1074
1077 return Bluebird.all([ 1075 return Bluebird.all([
1078 // FIXME: typing issue 1076 VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query),
1079 VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any), 1077 VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT })
1080 VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT })
1081 ]).then(([ rows, totals ]) => { 1078 ]).then(([ rows, totals ]) => {
1082 // totals: totalVideos + totalVideoShares 1079 // totals: totalVideos + totalVideoShares
1083 let totalVideos = 0 1080 let totalVideos = 0
1084 let totalVideoShares = 0 1081 let totalVideoShares = 0
1085 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10) 1082 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
1086 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10) 1083 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
1087 1084
1088 const total = totalVideos + totalVideoShares 1085 const total = totalVideos + totalVideoShares
1089 return { 1086 return {
@@ -1094,50 +1091,58 @@ export class VideoModel extends Model<VideoModel> {
1094 } 1091 }
1095 1092
1096 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { 1093 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
1097 const query: FindOptions = { 1094 function buildBaseQuery (): FindOptions {
1098 offset: start, 1095 return {
1099 limit: count, 1096 offset: start,
1100 order: getVideoSort(sort), 1097 limit: count,
1101 include: [ 1098 order: getVideoSort(sort),
1102 { 1099 include: [
1103 model: VideoChannelModel, 1100 {
1104 required: true, 1101 model: VideoChannelModel,
1105 include: [ 1102 required: true,
1106 { 1103 include: [
1107 model: AccountModel, 1104 {
1108 where: { 1105 model: AccountModel,
1109 id: accountId 1106 where: {
1110 }, 1107 id: accountId
1111 required: true 1108 },
1112 } 1109 required: true
1113 ] 1110 }
1114 }, 1111 ]
1115 { 1112 }
1116 model: ScheduleVideoUpdateModel, 1113 ]
1117 required: false 1114 }
1118 },
1119 {
1120 model: VideoBlacklistModel,
1121 required: false
1122 }
1123 ]
1124 } 1115 }
1125 1116
1117 const countQuery = buildBaseQuery()
1118 const findQuery = buildBaseQuery()
1119
1120 findQuery.include.push({
1121 model: ScheduleVideoUpdateModel,
1122 required: false
1123 })
1124
1125 findQuery.include.push({
1126 model: VideoBlacklistModel,
1127 required: false
1128 })
1129
1126 if (withFiles === true) { 1130 if (withFiles === true) {
1127 query.include.push({ 1131 findQuery.include.push({
1128 model: VideoFileModel.unscoped(), 1132 model: VideoFileModel.unscoped(),
1129 required: true 1133 required: true
1130 }) 1134 })
1131 } 1135 }
1132 1136
1133 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS) 1137 return Promise.all([
1134 .findAndCountAll(query) 1138 VideoModel.count(countQuery),
1135 .then(({ rows, count }) => { 1139 VideoModel.findAll(findQuery)
1136 return { 1140 ]).then(([ count, rows ]) => {
1137 data: rows, 1141 return {
1138 total: count 1142 data: rows,
1139 } 1143 total: count
1140 }) 1144 }
1145 })
1141 } 1146 }
1142 1147
1143 static async listForApi (options: { 1148 static async listForApi (options: {
@@ -1404,12 +1409,12 @@ export class VideoModel extends Model<VideoModel> {
1404 const where = buildWhereIdOrUUID(id) 1409 const where = buildWhereIdOrUUID(id)
1405 1410
1406 const options = { 1411 const options = {
1407 order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings 1412 order: [ [ 'Tags', 'name', 'ASC' ] ] as any,
1408 where, 1413 where,
1409 transaction: t 1414 transaction: t
1410 } 1415 }
1411 1416
1412 const scopes = [ 1417 const scopes: (string | ScopeOptions)[] = [
1413 ScopeNames.WITH_TAGS, 1418 ScopeNames.WITH_TAGS,
1414 ScopeNames.WITH_BLACKLISTED, 1419 ScopeNames.WITH_BLACKLISTED,
1415 ScopeNames.WITH_ACCOUNT_DETAILS, 1420 ScopeNames.WITH_ACCOUNT_DETAILS,
@@ -1420,7 +1425,7 @@ export class VideoModel extends Model<VideoModel> {
1420 ] 1425 ]
1421 1426
1422 if (userId) { 1427 if (userId) {
1423 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings 1428 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
1424 } 1429 }
1425 1430
1426 return VideoModel 1431 return VideoModel
@@ -1437,18 +1442,18 @@ export class VideoModel extends Model<VideoModel> {
1437 transaction: t 1442 transaction: t
1438 } 1443 }
1439 1444
1440 const scopes = [ 1445 const scopes: (string | ScopeOptions)[] = [
1441 ScopeNames.WITH_TAGS, 1446 ScopeNames.WITH_TAGS,
1442 ScopeNames.WITH_BLACKLISTED, 1447 ScopeNames.WITH_BLACKLISTED,
1443 ScopeNames.WITH_ACCOUNT_DETAILS, 1448 ScopeNames.WITH_ACCOUNT_DETAILS,
1444 ScopeNames.WITH_SCHEDULED_UPDATE, 1449 ScopeNames.WITH_SCHEDULED_UPDATE,
1445 ScopeNames.WITH_THUMBNAILS, 1450 ScopeNames.WITH_THUMBNAILS,
1446 { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings 1451 { method: [ ScopeNames.WITH_FILES, true ] },
1447 { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings 1452 { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] }
1448 ] 1453 ]
1449 1454
1450 if (userId) { 1455 if (userId) {
1451 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings 1456 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] })
1452 } 1457 }
1453 1458
1454 return VideoModel 1459 return VideoModel
@@ -1520,9 +1525,9 @@ export class VideoModel extends Model<VideoModel> {
1520 attributes: [ field ], 1525 attributes: [ field ],
1521 limit: count, 1526 limit: count,
1522 group: field, 1527 group: field,
1523 having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { 1528 having: Sequelize.where(
1524 [ Op.gte ]: threshold 1529 Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold }
1525 }) as any, // FIXME: typings 1530 ),
1526 order: [ (this.sequelize as any).random() ] 1531 order: [ (this.sequelize as any).random() ]
1527 } 1532 }
1528 1533
@@ -1594,16 +1599,10 @@ export class VideoModel extends Model<VideoModel> {
1594 ] 1599 ]
1595 } 1600 }
1596 1601
1597 // FIXME: typing 1602 const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ]
1598 const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ]
1599 1603
1600 if (options.user) { 1604 if (options.user) {
1601 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) 1605 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
1602
1603 // Even if the relation is n:m, we know that a user only have 0..1 video history
1604 // So we won't have multiple rows for the same video
1605 // A subquery adds some bugs in our query so disable it
1606 secondQuery.subQuery = false
1607 } 1606 }
1608 1607
1609 apiScope.push({ 1608 apiScope.push({
@@ -1651,13 +1650,17 @@ export class VideoModel extends Model<VideoModel> {
1651 return maxBy(this.VideoFiles, file => file.resolution) 1650 return maxBy(this.VideoFiles, file => file.resolution)
1652 } 1651 }
1653 1652
1654 addThumbnail (thumbnail: ThumbnailModel) { 1653 async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) {
1654 thumbnail.videoId = this.id
1655
1656 const savedThumbnail = await thumbnail.save({ transaction })
1657
1655 if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] 1658 if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = []
1656 1659
1657 // Already have this thumbnail, skip 1660 // Already have this thumbnail, skip
1658 if (this.Thumbnails.find(t => t.id === thumbnail.id)) return 1661 if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return
1659 1662
1660 this.Thumbnails.push(thumbnail) 1663 this.Thumbnails.push(savedThumbnail)
1661 } 1664 }
1662 1665
1663 getVideoFilename (videoFile: VideoFileModel) { 1666 getVideoFilename (videoFile: VideoFileModel) {
@@ -1668,10 +1671,10 @@ export class VideoModel extends Model<VideoModel> {
1668 return this.uuid + '.jpg' 1671 return this.uuid + '.jpg'
1669 } 1672 }
1670 1673
1671 getThumbnail () { 1674 getMiniature () {
1672 if (Array.isArray(this.Thumbnails) === false) return undefined 1675 if (Array.isArray(this.Thumbnails) === false) return undefined
1673 1676
1674 return this.Thumbnails.find(t => t.type === ThumbnailType.THUMBNAIL) 1677 return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE)
1675 } 1678 }
1676 1679
1677 generatePreviewName () { 1680 generatePreviewName () {
@@ -1732,8 +1735,8 @@ export class VideoModel extends Model<VideoModel> {
1732 return '/videos/embed/' + this.uuid 1735 return '/videos/embed/' + this.uuid
1733 } 1736 }
1734 1737
1735 getThumbnailStaticPath () { 1738 getMiniatureStaticPath () {
1736 const thumbnail = this.getThumbnail() 1739 const thumbnail = this.getMiniature()
1737 if (!thumbnail) return null 1740 if (!thumbnail) return null
1738 1741
1739 return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) 1742 return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename)