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.ts157
1 files changed, 120 insertions, 37 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 217ca8e50..7f94e834a 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1,5 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { maxBy, minBy } from 'lodash' 2import { maxBy, minBy, pick } from 'lodash'
3import { join } from 'path' 3import { join } from 'path'
4import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' 4import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
5import { 5import {
@@ -124,7 +124,7 @@ import { MThumbnail } from '../../typings/models/video/thumbnail'
124import { VideoFile } from '@shared/models/videos/video-file.model' 124import { VideoFile } from '@shared/models/videos/video-file.model'
125import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 125import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
126import { ModelCache } from '@server/models/model-cache' 126import { ModelCache } from '@server/models/model-cache'
127import { buildListQuery, BuildVideosQueryOptions } from './video-query-builder' 127import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder'
128 128
129export enum ScopeNames { 129export enum ScopeNames {
130 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', 130 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
@@ -1408,19 +1408,25 @@ export class VideoModel extends Model<VideoModel> {
1408 options: BuildVideosQueryOptions, 1408 options: BuildVideosQueryOptions,
1409 countVideos = true 1409 countVideos = true
1410 ) { 1410 ) {
1411 const { query, replacements } = buildListQuery(VideoModel, options) 1411 function getCount () {
1412 const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel, Object.assign({}, options, { isCount: true })) 1412 if (countVideos !== true) return Promise.resolve(undefined)
1413 1413
1414 const [ count, rows ] = await Promise.all([ 1414 const countOptions = Object.assign({}, options, { isCount: true })
1415 countVideos 1415 const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel, countOptions)
1416 ? this.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT })
1417 .then(rows => rows.length !== 0 ? rows[0].total : 0)
1418 : Promise.resolve<number>(undefined),
1419 1416
1420 this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT }) 1417 return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT })
1421 .then(rows => rows.map(r => r.id)) 1418 .then(rows => rows.length !== 0 ? rows[0].total : 0)
1422 .then(ids => VideoModel.loadCompleteVideosForApi(ids, options)) 1419 }
1423 ]) 1420
1421 function getModels () {
1422 const { query, replacements, order } = buildListQuery(VideoModel, options)
1423 const queryModels = wrapForAPIResults(query, replacements, options, order)
1424
1425 return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true })
1426 .then(rows => VideoModel.buildAPIResult(rows))
1427 }
1428
1429 const [ count, rows ] = await Promise.all([ getCount(), getModels() ])
1424 1430
1425 return { 1431 return {
1426 data: rows, 1432 data: rows,
@@ -1428,36 +1434,113 @@ export class VideoModel extends Model<VideoModel> {
1428 } 1434 }
1429 } 1435 }
1430 1436
1431 private static loadCompleteVideosForApi (ids: number[], options: BuildVideosQueryOptions) { 1437 private static buildAPIResult (rows: any[]) {
1432 if (ids.length === 0) return [] 1438 const memo: { [ id: number ]: VideoModel } = {}
1439
1440 const thumbnailsDone = new Set<number>()
1441 const historyDone = new Set<number>()
1442 const videoFilesDone = new Set<number>()
1443
1444 const videos: VideoModel[] = []
1445
1446 const avatarKeys = [ 'id', 'filename', 'fileUrl', 'onDisk', 'createdAt', 'updatedAt' ]
1447 const actorKeys = [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ]
1448 const serverKeys = [ 'id', 'host' ]
1449 const videoFileKeys = [ 'id', 'createdAt', 'updatedAt', 'resolution', 'size', 'extname', 'infoHash', 'fps', 'videoId' ]
1450 const videoKeys = [
1451 'id',
1452 'uuid',
1453 'name',
1454 'category',
1455 'licence',
1456 'language',
1457 'privacy',
1458 'nsfw',
1459 'description',
1460 'support',
1461 'duration',
1462 'views',
1463 'likes',
1464 'dislikes',
1465 'remote',
1466 'url',
1467 'commentsEnabled',
1468 'downloadEnabled',
1469 'waitTranscoding',
1470 'state',
1471 'publishedAt',
1472 'originallyPublishedAt',
1473 'channelId',
1474 'createdAt',
1475 'updatedAt'
1476 ]
1433 1477
1434 const secondQuery: FindOptions = { 1478 function buildActor (rowActor: any) {
1435 offset: 0, 1479 const avatarModel = rowActor.Avatar.id !== null
1436 limit: options.count, 1480 ? new AvatarModel(pick(rowActor.Avatar, avatarKeys))
1437 order: [ // Keep original order 1481 : null
1438 Sequelize.literal(
1439 ids.map(id => `"VideoModel".id = ${id} DESC`).join(', ')
1440 )
1441 ]
1442 }
1443 1482
1444 const apiScope: (string | ScopeOptions)[] = [] 1483 const serverModel = rowActor.Server.id !== null
1484 ? new ServerModel(pick(rowActor.Server, serverKeys))
1485 : null
1445 1486
1446 if (options.user) { 1487 const actorModel = new ActorModel(pick(rowActor, actorKeys))
1447 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) 1488 actorModel.Avatar = avatarModel
1489 actorModel.Server = serverModel
1490
1491 return actorModel
1448 } 1492 }
1449 1493
1450 apiScope.push({ 1494 for (const row of rows) {
1451 method: [ 1495 if (!memo[row.id]) {
1452 ScopeNames.FOR_API, { 1496 // Build Channel
1453 ids, 1497 const channel = row.VideoChannel
1454 withFiles: options.withFiles, 1498 const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ]))
1455 videoPlaylistId: options.videoPlaylistId 1499 channelModel.Actor = buildActor(channel.Actor)
1456 } as ForAPIOptions 1500
1457 ] 1501 const account = row.VideoChannel.Account
1458 }) 1502 const accountModel = new AccountModel(pick(account, [ 'id', 'name' ]))
1503 accountModel.Actor = buildActor(account.Actor)
1504
1505 channelModel.Account = accountModel
1506
1507 const videoModel = new VideoModel(pick(row, videoKeys))
1508 videoModel.VideoChannel = channelModel
1509
1510 videoModel.UserVideoHistories = []
1511 videoModel.Thumbnails = []
1512 videoModel.VideoFiles = []
1513
1514 memo[row.id] = videoModel
1515 // Don't take object value to have a sorted array
1516 videos.push(videoModel)
1517 }
1518
1519 const videoModel = memo[row.id]
1520
1521 if (row.userVideoHistory?.id && !historyDone.has(row.userVideoHistory.id)) {
1522 const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ]))
1523 videoModel.UserVideoHistories.push(historyModel)
1524
1525 historyDone.add(row.userVideoHistory.id)
1526 }
1527
1528 if (row.Thumbnails?.id && !thumbnailsDone.has(row.Thumbnails.id)) {
1529 const thumbnailModel = new ThumbnailModel(pick(row.Thumbnails, [ 'id', 'type', 'filename' ]))
1530 videoModel.Thumbnails.push(thumbnailModel)
1531
1532 thumbnailsDone.add(row.Thumbnails.id)
1533 }
1534
1535 if (row.VideoFiles?.id && !videoFilesDone.has(row.VideoFiles.id)) {
1536 const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys))
1537 videoModel.VideoFiles.push(videoFileModel)
1538
1539 videoFilesDone.add(row.VideoFiles.id)
1540 }
1541 }
1459 1542
1460 return VideoModel.scope(apiScope).findAll(secondQuery) 1543 return videos
1461 } 1544 }
1462 1545
1463 private static isPrivacyForFederation (privacy: VideoPrivacy) { 1546 private static isPrivacyForFederation (privacy: VideoPrivacy) {