aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/sql/video/shared/abstract-video-query-builder.ts7
-rw-r--r--server/models/video/sql/video/shared/video-file-query-builder.ts30
-rw-r--r--server/models/video/sql/video/video-model-get-query-builder.ts15
-rw-r--r--server/models/video/sql/video/videos-model-list-query-builder.ts41
-rw-r--r--server/models/video/video-channel.ts13
-rw-r--r--server/models/video/video-live-session.ts2
-rw-r--r--server/models/video/video-source.ts55
-rw-r--r--server/models/video/video.ts10
8 files changed, 148 insertions, 25 deletions
diff --git a/server/models/video/sql/video/shared/abstract-video-query-builder.ts b/server/models/video/sql/video/shared/abstract-video-query-builder.ts
index b79d20ade..3c74b0ea6 100644
--- a/server/models/video/sql/video/shared/abstract-video-query-builder.ts
+++ b/server/models/video/sql/video/shared/abstract-video-query-builder.ts
@@ -313,7 +313,12 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery {
313 return result 313 return result
314 } 314 }
315 315
316 protected whereId (options: { id?: string | number, url?: string }) { 316 protected whereId (options: { ids?: number[], id?: string | number, url?: string }) {
317 if (options.ids) {
318 this.where = `WHERE "video"."id" IN (${createSafeIn(this.sequelize, options.ids)})`
319 return
320 }
321
317 if (options.url) { 322 if (options.url) {
318 this.where = 'WHERE "video"."url" = :videoUrl' 323 this.where = 'WHERE "video"."url" = :videoUrl'
319 this.replacements.videoUrl = options.url 324 this.replacements.videoUrl = options.url
diff --git a/server/models/video/sql/video/shared/video-file-query-builder.ts b/server/models/video/sql/video/shared/video-file-query-builder.ts
index 50c12f627..cc53a4860 100644
--- a/server/models/video/sql/video/shared/video-file-query-builder.ts
+++ b/server/models/video/sql/video/shared/video-file-query-builder.ts
@@ -1,7 +1,17 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize, Transaction } from 'sequelize'
2import { BuildVideoGetQueryOptions } from '../video-model-get-query-builder'
3import { AbstractVideoQueryBuilder } from './abstract-video-query-builder' 2import { AbstractVideoQueryBuilder } from './abstract-video-query-builder'
4 3
4export type FileQueryOptions = {
5 id?: string | number
6 url?: string
7
8 includeRedundancy: boolean
9
10 transaction?: Transaction
11
12 logging?: boolean
13}
14
5/** 15/**
6 * 16 *
7 * Fetch files (webtorrent and streaming playlist) according to a video 17 * Fetch files (webtorrent and streaming playlist) according to a video
@@ -15,26 +25,26 @@ export class VideoFileQueryBuilder extends AbstractVideoQueryBuilder {
15 super(sequelize, 'get') 25 super(sequelize, 'get')
16 } 26 }
17 27
18 queryWebTorrentVideos (options: BuildVideoGetQueryOptions) { 28 queryWebTorrentVideos (options: FileQueryOptions) {
19 this.buildWebtorrentFilesQuery(options) 29 this.buildWebtorrentFilesQuery(options)
20 30
21 return this.runQuery(options) 31 return this.runQuery(options)
22 } 32 }
23 33
24 queryStreamingPlaylistVideos (options: BuildVideoGetQueryOptions) { 34 queryStreamingPlaylistVideos (options: FileQueryOptions) {
25 this.buildVideoStreamingPlaylistFilesQuery(options) 35 this.buildVideoStreamingPlaylistFilesQuery(options)
26 36
27 return this.runQuery(options) 37 return this.runQuery(options)
28 } 38 }
29 39
30 private buildWebtorrentFilesQuery (options: BuildVideoGetQueryOptions) { 40 private buildWebtorrentFilesQuery (options: FileQueryOptions) {
31 this.attributes = { 41 this.attributes = {
32 '"video"."id"': '' 42 '"video"."id"': ''
33 } 43 }
34 44
35 this.includeWebtorrentFiles() 45 this.includeWebtorrentFiles()
36 46
37 if (this.shouldIncludeRedundancies(options)) { 47 if (options.includeRedundancy) {
38 this.includeWebTorrentRedundancies() 48 this.includeWebTorrentRedundancies()
39 } 49 }
40 50
@@ -43,14 +53,14 @@ export class VideoFileQueryBuilder extends AbstractVideoQueryBuilder {
43 this.query = this.buildQuery() 53 this.query = this.buildQuery()
44 } 54 }
45 55
46 private buildVideoStreamingPlaylistFilesQuery (options: BuildVideoGetQueryOptions) { 56 private buildVideoStreamingPlaylistFilesQuery (options: FileQueryOptions) {
47 this.attributes = { 57 this.attributes = {
48 '"video"."id"': '' 58 '"video"."id"': ''
49 } 59 }
50 60
51 this.includeStreamingPlaylistFiles() 61 this.includeStreamingPlaylistFiles()
52 62
53 if (this.shouldIncludeRedundancies(options)) { 63 if (options.includeRedundancy) {
54 this.includeStreamingPlaylistRedundancies() 64 this.includeStreamingPlaylistRedundancies()
55 } 65 }
56 66
@@ -62,8 +72,4 @@ export class VideoFileQueryBuilder extends AbstractVideoQueryBuilder {
62 private buildQuery () { 72 private buildQuery () {
63 return `${this.buildSelect()} FROM "video" ${this.joins} ${this.where}` 73 return `${this.buildSelect()} FROM "video" ${this.joins} ${this.where}`
64 } 74 }
65
66 private shouldIncludeRedundancies (options: BuildVideoGetQueryOptions) {
67 return options.type === 'api'
68 }
69} 75}
diff --git a/server/models/video/sql/video/video-model-get-query-builder.ts b/server/models/video/sql/video/video-model-get-query-builder.ts
index b0879c9ac..32e5c4ff7 100644
--- a/server/models/video/sql/video/video-model-get-query-builder.ts
+++ b/server/models/video/sql/video/video-model-get-query-builder.ts
@@ -1,3 +1,4 @@
1import { pick } from 'lodash'
1import { Sequelize, Transaction } from 'sequelize' 2import { Sequelize, Transaction } from 'sequelize'
2import { AbstractVideoQueryBuilder } from './shared/abstract-video-query-builder' 3import { AbstractVideoQueryBuilder } from './shared/abstract-video-query-builder'
3import { VideoFileQueryBuilder } from './shared/video-file-query-builder' 4import { VideoFileQueryBuilder } from './shared/video-file-query-builder'
@@ -50,15 +51,21 @@ export class VideoModelGetQueryBuilder {
50 } 51 }
51 52
52 async queryVideo (options: BuildVideoGetQueryOptions) { 53 async queryVideo (options: BuildVideoGetQueryOptions) {
54 const fileQueryOptions = {
55 ...pick(options, [ 'id', 'url', 'transaction', 'logging' ]),
56
57 includeRedundancy: this.shouldIncludeRedundancies(options)
58 }
59
53 const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([ 60 const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
54 this.videoQueryBuilder.queryVideos(options), 61 this.videoQueryBuilder.queryVideos(options),
55 62
56 VideoModelGetQueryBuilder.videoFilesInclude.has(options.type) 63 VideoModelGetQueryBuilder.videoFilesInclude.has(options.type)
57 ? this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options) 64 ? this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(fileQueryOptions)
58 : Promise.resolve(undefined), 65 : Promise.resolve(undefined),
59 66
60 VideoModelGetQueryBuilder.videoFilesInclude.has(options.type) 67 VideoModelGetQueryBuilder.videoFilesInclude.has(options.type)
61 ? this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options) 68 ? this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(fileQueryOptions)
62 : Promise.resolve(undefined) 69 : Promise.resolve(undefined)
63 ]) 70 ])
64 71
@@ -76,6 +83,10 @@ export class VideoModelGetQueryBuilder {
76 83
77 return videos[0] 84 return videos[0]
78 } 85 }
86
87 private shouldIncludeRedundancies (options: BuildVideoGetQueryOptions) {
88 return options.type === 'api'
89 }
79} 90}
80 91
81export class VideosModelGetQuerySubBuilder extends AbstractVideoQueryBuilder { 92export class VideosModelGetQuerySubBuilder extends AbstractVideoQueryBuilder {
diff --git a/server/models/video/sql/video/videos-model-list-query-builder.ts b/server/models/video/sql/video/videos-model-list-query-builder.ts
index 2a4afc389..4fe6bc321 100644
--- a/server/models/video/sql/video/videos-model-list-query-builder.ts
+++ b/server/models/video/sql/video/videos-model-list-query-builder.ts
@@ -1,6 +1,8 @@
1import { VideoInclude } from '@shared/models' 1import { pick } from 'lodash'
2import { Sequelize } from 'sequelize' 2import { Sequelize } from 'sequelize'
3import { VideoInclude } from '@shared/models'
3import { AbstractVideoQueryBuilder } from './shared/abstract-video-query-builder' 4import { AbstractVideoQueryBuilder } from './shared/abstract-video-query-builder'
5import { VideoFileQueryBuilder } from './shared/video-file-query-builder'
4import { VideoModelBuilder } from './shared/video-model-builder' 6import { VideoModelBuilder } from './shared/video-model-builder'
5import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './videos-id-list-query-builder' 7import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './videos-id-list-query-builder'
6 8
@@ -16,20 +18,46 @@ export class VideosModelListQueryBuilder extends AbstractVideoQueryBuilder {
16 private innerQuery: string 18 private innerQuery: string
17 private innerSort: string 19 private innerSort: string
18 20
21 webtorrentFilesQueryBuilder: VideoFileQueryBuilder
22 streamingPlaylistFilesQueryBuilder: VideoFileQueryBuilder
23
19 private readonly videoModelBuilder: VideoModelBuilder 24 private readonly videoModelBuilder: VideoModelBuilder
20 25
21 constructor (protected readonly sequelize: Sequelize) { 26 constructor (protected readonly sequelize: Sequelize) {
22 super(sequelize, 'list') 27 super(sequelize, 'list')
23 28
24 this.videoModelBuilder = new VideoModelBuilder(this.mode, this.tables) 29 this.videoModelBuilder = new VideoModelBuilder(this.mode, this.tables)
30 this.webtorrentFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
31 this.streamingPlaylistFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
25 } 32 }
26 33
27 queryVideos (options: BuildVideosListQueryOptions) { 34 async queryVideos (options: BuildVideosListQueryOptions) {
28 this.buildInnerQuery(options) 35 this.buildInnerQuery(options)
29 this.buildMainQuery(options) 36 this.buildMainQuery(options)
30 37
31 return this.runQuery() 38 const rows = await this.runQuery()
32 .then(rows => this.videoModelBuilder.buildVideosFromRows({ rows, include: options.include })) 39
40 if (options.include & VideoInclude.FILES) {
41 const videoIds = Array.from(new Set(rows.map(r => r.id)))
42
43 if (videoIds.length !== 0) {
44 const fileQueryOptions = {
45 ...pick(options, [ 'transaction', 'logging' ]),
46
47 ids: videoIds,
48 includeRedundancy: false
49 }
50
51 const [ rowsWebTorrentFiles, rowsStreamingPlaylist ] = await Promise.all([
52 this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(fileQueryOptions),
53 this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(fileQueryOptions)
54 ])
55
56 return this.videoModelBuilder.buildVideosFromRows({ rows, include: options.include, rowsStreamingPlaylist, rowsWebTorrentFiles })
57 }
58 }
59
60 return this.videoModelBuilder.buildVideosFromRows({ rows, include: options.include })
33 } 61 }
34 62
35 private buildInnerQuery (options: BuildVideosListQueryOptions) { 63 private buildInnerQuery (options: BuildVideosListQueryOptions) {
@@ -52,11 +80,6 @@ export class VideosModelListQueryBuilder extends AbstractVideoQueryBuilder {
52 this.includeAccounts() 80 this.includeAccounts()
53 this.includeThumbnails() 81 this.includeThumbnails()
54 82
55 if (options.include & VideoInclude.FILES) {
56 this.includeWebtorrentFiles()
57 this.includeStreamingPlaylistFiles()
58 }
59
60 if (options.user) { 83 if (options.user) {
61 this.includeUserHistory(options.user.id) 84 this.includeUserHistory(options.user.id)
62 } 85 }
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index d6dd1b8bb..91dafbcf1 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -311,6 +311,16 @@ export type SummaryOptions = {
311 ')' 311 ')'
312 ), 312 ),
313 'viewsPerDay' 313 'viewsPerDay'
314 ],
315 [
316 literal(
317 '(' +
318 'SELECT COALESCE(SUM("video".views), 0) AS totalViews ' +
319 'FROM "video" ' +
320 'WHERE "video"."channelId" = "VideoChannelModel"."id"' +
321 ')'
322 ),
323 'totalViews'
314 ] 324 ]
315 ] 325 ]
316 } 326 }
@@ -766,6 +776,8 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
766 }) 776 })
767 } 777 }
768 778
779 const totalViews = this.get('totalViews') as number
780
769 const actor = this.Actor.toFormattedJSON() 781 const actor = this.Actor.toFormattedJSON()
770 const videoChannel = { 782 const videoChannel = {
771 id: this.id, 783 id: this.id,
@@ -779,6 +791,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
779 791
780 videosCount, 792 videosCount,
781 viewsPerDay, 793 viewsPerDay,
794 totalViews,
782 795
783 avatars: actor.avatars, 796 avatars: actor.avatars,
784 797
diff --git a/server/models/video/video-live-session.ts b/server/models/video/video-live-session.ts
index 2b4cde9f8..836620872 100644
--- a/server/models/video/video-live-session.ts
+++ b/server/models/video/video-live-session.ts
@@ -110,7 +110,7 @@ export class VideoLiveSessionModel extends Model<Partial<AttributesOnly<VideoLiv
110 static listSessionsOfLiveForAPI (options: { videoId: number }) { 110 static listSessionsOfLiveForAPI (options: { videoId: number }) {
111 const { videoId } = options 111 const { videoId } = options
112 112
113 const query: FindOptions<VideoLiveSessionModel> = { 113 const query: FindOptions<AttributesOnly<VideoLiveSessionModel>> = {
114 where: { 114 where: {
115 liveVideoId: videoId 115 liveVideoId: videoId
116 }, 116 },
diff --git a/server/models/video/video-source.ts b/server/models/video/video-source.ts
new file mode 100644
index 000000000..e306b160d
--- /dev/null
+++ b/server/models/video/video-source.ts
@@ -0,0 +1,55 @@
1import { Op } from 'sequelize'
2import {
3 AllowNull,
4 BelongsTo,
5 Column,
6 CreatedAt,
7 ForeignKey,
8 Model,
9 Table,
10 UpdatedAt
11} from 'sequelize-typescript'
12import { AttributesOnly } from '@shared/typescript-utils'
13import { VideoModel } from './video'
14
15@Table({
16 tableName: 'videoSource',
17 indexes: [
18 {
19 fields: [ 'videoId' ],
20 where: {
21 videoId: {
22 [Op.ne]: null
23 }
24 }
25 }
26 ]
27})
28export class VideoSourceModel extends Model<Partial<AttributesOnly<VideoSourceModel>>> {
29 @CreatedAt
30 createdAt: Date
31
32 @UpdatedAt
33 updatedAt: Date
34
35 @AllowNull(false)
36 @Column
37 filename: string
38
39 @ForeignKey(() => VideoModel)
40 @Column
41 videoId: number
42
43 @BelongsTo(() => VideoModel)
44 Video: VideoModel
45
46 static loadByVideoId (videoId) {
47 return VideoSourceModel.findOne({ where: { videoId } })
48 }
49
50 toFormattedJSON () {
51 return {
52 filename: this.filename
53 }
54 }
55}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e6a8d3f95..08adbced6 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -136,6 +136,7 @@ import { VideoPlaylistElementModel } from './video-playlist-element'
136import { VideoShareModel } from './video-share' 136import { VideoShareModel } from './video-share'
137import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 137import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
138import { VideoTagModel } from './video-tag' 138import { VideoTagModel } from './video-tag'
139import { VideoSourceModel } from './video-source'
139 140
140export enum ScopeNames { 141export enum ScopeNames {
141 FOR_API = 'FOR_API', 142 FOR_API = 'FOR_API',
@@ -597,6 +598,15 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
597 }) 598 })
598 VideoPlaylistElements: VideoPlaylistElementModel[] 599 VideoPlaylistElements: VideoPlaylistElementModel[]
599 600
601 @HasOne(() => VideoSourceModel, {
602 foreignKey: {
603 name: 'videoId',
604 allowNull: true
605 },
606 onDelete: 'CASCADE'
607 })
608 VideoSource: VideoSourceModel
609
600 @HasMany(() => VideoAbuseModel, { 610 @HasMany(() => VideoAbuseModel, {
601 foreignKey: { 611 foreignKey: {
602 name: 'videoId', 612 name: 'videoId',