diff options
Diffstat (limited to 'server/models/video')
-rw-r--r-- | server/models/video/thumbnail.ts | 15 | ||||
-rw-r--r-- | server/models/video/video-abuse.ts | 6 | ||||
-rw-r--r-- | server/models/video/video-caption.ts | 27 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 33 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 20 | ||||
-rw-r--r-- | server/models/video/video-format-utils.ts | 34 | ||||
-rw-r--r-- | server/models/video/video-playlist-element.ts | 8 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 36 | ||||
-rw-r--r-- | server/models/video/video.ts | 368 |
9 files changed, 298 insertions, 249 deletions
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index 3b011b1d2..e396784d2 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -19,6 +19,8 @@ import { CONFIG } from '../../initializers/config' | |||
19 | import { VideoModel } from './video' | 19 | import { VideoModel } from './video' |
20 | import { VideoPlaylistModel } from './video-playlist' | 20 | import { VideoPlaylistModel } from './video-playlist' |
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
22 | import { MVideoAccountLight } from '@server/typings/models' | ||
23 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
22 | 24 | ||
23 | @Table({ | 25 | @Table({ |
24 | tableName: 'thumbnail', | 26 | tableName: 'thumbnail', |
@@ -90,7 +92,7 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
90 | @UpdatedAt | 92 | @UpdatedAt |
91 | updatedAt: Date | 93 | updatedAt: Date |
92 | 94 | ||
93 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { | 95 | private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { |
94 | [ThumbnailType.MINIATURE]: { | 96 | [ThumbnailType.MINIATURE]: { |
95 | label: 'miniature', | 97 | label: 'miniature', |
96 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | 98 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, |
@@ -126,11 +128,14 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
126 | return videoUUID + '.jpg' | 128 | return videoUUID + '.jpg' |
127 | } | 129 | } |
128 | 130 | ||
129 | getFileUrl (isLocal: boolean) { | 131 | getFileUrl (video: MVideoAccountLight) { |
130 | if (isLocal === false) return this.fileUrl | 132 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename |
131 | 133 | ||
132 | const staticPath = ThumbnailModel.types[this.type].staticPath | 134 | if (video.isOwned()) return WEBSERVER.URL + staticPath |
133 | return WEBSERVER.URL + staticPath + this.filename | 135 | if (this.fileUrl) return this.fileUrl |
136 | |||
137 | // Fallback if we don't have a file URL | ||
138 | return buildRemoteVideoBaseUrl(video, staticPath) | ||
134 | } | 139 | } |
135 | 140 | ||
136 | getPath () { | 141 | getPath () { |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 3636db18d..da8c1577c 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -87,9 +87,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
87 | } | 87 | } |
88 | 88 | ||
89 | static listForApi (parameters: { | 89 | static listForApi (parameters: { |
90 | start: number, | 90 | start: number |
91 | count: number, | 91 | count: number |
92 | sort: string, | 92 | sort: string |
93 | serverAccountId: number | 93 | serverAccountId: number |
94 | user?: MUserAccountId | 94 | user?: MUserAccountId |
95 | }) { | 95 | }) { |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index eeb2a4afd..59d3e1050 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -5,6 +5,7 @@ import { | |||
5 | BelongsTo, | 5 | BelongsTo, |
6 | Column, | 6 | Column, |
7 | CreatedAt, | 7 | CreatedAt, |
8 | DataType, | ||
8 | ForeignKey, | 9 | ForeignKey, |
9 | Is, | 10 | Is, |
10 | Model, | 11 | Model, |
@@ -16,13 +17,14 @@ import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' | |||
16 | import { VideoModel } from './video' | 17 | import { VideoModel } from './video' |
17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 18 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 19 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
19 | import { LAZY_STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' | 20 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' |
20 | import { join } from 'path' | 21 | import { join } from 'path' |
21 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
22 | import { remove } from 'fs-extra' | 23 | import { remove } from 'fs-extra' |
23 | import { CONFIG } from '../../initializers/config' | 24 | import { CONFIG } from '../../initializers/config' |
24 | import * as Bluebird from 'bluebird' | 25 | import * as Bluebird from 'bluebird' |
25 | import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' | 26 | import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' |
27 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
26 | 28 | ||
27 | export enum ScopeNames { | 29 | export enum ScopeNames { |
28 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 30 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -64,6 +66,10 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
64 | @Column | 66 | @Column |
65 | language: string | 67 | language: string |
66 | 68 | ||
69 | @AllowNull(true) | ||
70 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) | ||
71 | fileUrl: string | ||
72 | |||
67 | @ForeignKey(() => VideoModel) | 73 | @ForeignKey(() => VideoModel) |
68 | @Column | 74 | @Column |
69 | videoId: number | 75 | videoId: number |
@@ -114,13 +120,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
114 | return VideoCaptionModel.findOne(query) | 120 | return VideoCaptionModel.findOne(query) |
115 | } | 121 | } |
116 | 122 | ||
117 | static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { | 123 | static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) { |
118 | const values = { | 124 | const values = { |
119 | videoId, | 125 | videoId, |
120 | language | 126 | language, |
127 | fileUrl | ||
121 | } | 128 | } |
122 | 129 | ||
123 | return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings | 130 | return VideoCaptionModel.upsert(values, { transaction, returning: true }) |
124 | .then(([ caption ]) => caption) | 131 | .then(([ caption ]) => caption) |
125 | } | 132 | } |
126 | 133 | ||
@@ -175,4 +182,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
175 | removeCaptionFile (this: MVideoCaptionFormattable) { | 182 | removeCaptionFile (this: MVideoCaptionFormattable) { |
176 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) | 183 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) |
177 | } | 184 | } |
185 | |||
186 | getFileUrl (video: MVideoAccountLight) { | ||
187 | if (!this.Video) this.Video = video as VideoModel | ||
188 | |||
189 | if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() | ||
190 | if (this.fileUrl) return this.fileUrl | ||
191 | |||
192 | // Fallback if we don't have a file URL | ||
193 | return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath()) | ||
194 | } | ||
178 | } | 195 | } |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index e10adcb3a..835216671 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -30,7 +30,7 @@ import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttr | |||
30 | import { VideoModel } from './video' | 30 | import { VideoModel } from './video' |
31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
32 | import { ServerModel } from '../server/server' | 32 | import { ServerModel } from '../server/server' |
33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' | 33 | import { FindOptions, Op } from 'sequelize' |
34 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
35 | import { VideoPlaylistModel } from './video-playlist' | 35 | import { VideoPlaylistModel } from './video-playlist' |
36 | import * as Bluebird from 'bluebird' | 36 | import * as Bluebird from 'bluebird' |
@@ -43,18 +43,6 @@ import { | |||
43 | MChannelSummaryFormattable | 43 | MChannelSummaryFormattable |
44 | } from '../../typings/models/video' | 44 | } from '../../typings/models/video' |
45 | 45 | ||
46 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | ||
47 | const indexes: ModelIndexesOptions[] = [ | ||
48 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), | ||
49 | |||
50 | { | ||
51 | fields: [ 'accountId' ] | ||
52 | }, | ||
53 | { | ||
54 | fields: [ 'actorId' ] | ||
55 | } | ||
56 | ] | ||
57 | |||
58 | export enum ScopeNames { | 46 | export enum ScopeNames { |
59 | FOR_API = 'FOR_API', | 47 | FOR_API = 'FOR_API', |
60 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 48 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -133,7 +121,7 @@ export type SummaryOptions = { | |||
133 | }, | 121 | }, |
134 | { | 122 | { |
135 | serverId: { | 123 | serverId: { |
136 | [ Op.in ]: Sequelize.literal(inQueryInstanceFollow) | 124 | [Op.in]: Sequelize.literal(inQueryInstanceFollow) |
137 | } | 125 | } |
138 | } | 126 | } |
139 | ] | 127 | ] |
@@ -176,7 +164,16 @@ export type SummaryOptions = { | |||
176 | })) | 164 | })) |
177 | @Table({ | 165 | @Table({ |
178 | tableName: 'videoChannel', | 166 | tableName: 'videoChannel', |
179 | indexes | 167 | indexes: [ |
168 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), | ||
169 | |||
170 | { | ||
171 | fields: [ 'accountId' ] | ||
172 | }, | ||
173 | { | ||
174 | fields: [ 'actorId' ] | ||
175 | } | ||
176 | ] | ||
180 | }) | 177 | }) |
181 | export class VideoChannelModel extends Model<VideoChannelModel> { | 178 | export class VideoChannelModel extends Model<VideoChannelModel> { |
182 | 179 | ||
@@ -351,9 +348,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
351 | } | 348 | } |
352 | 349 | ||
353 | static listByAccount (options: { | 350 | static listByAccount (options: { |
354 | accountId: number, | 351 | accountId: number |
355 | start: number, | 352 | start: number |
356 | count: number, | 353 | count: number |
357 | sort: string | 354 | sort: string |
358 | }) { | 355 | }) { |
359 | const query = { | 356 | const query = { |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index fb4d16b4d..b33c33d5e 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -257,10 +257,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
257 | } | 257 | } |
258 | 258 | ||
259 | static async listThreadsForApi (parameters: { | 259 | static async listThreadsForApi (parameters: { |
260 | videoId: number, | 260 | videoId: number |
261 | start: number, | 261 | start: number |
262 | count: number, | 262 | count: number |
263 | sort: string, | 263 | sort: string |
264 | user?: MUserAccountId | 264 | user?: MUserAccountId |
265 | }) { | 265 | }) { |
266 | const { videoId, start, count, sort, user } = parameters | 266 | const { videoId, start, count, sort, user } = parameters |
@@ -300,8 +300,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
300 | } | 300 | } |
301 | 301 | ||
302 | static async listThreadCommentsForApi (parameters: { | 302 | static async listThreadCommentsForApi (parameters: { |
303 | videoId: number, | 303 | videoId: number |
304 | threadId: number, | 304 | threadId: number |
305 | user?: MUserAccountId | 305 | user?: MUserAccountId |
306 | }) { | 306 | }) { |
307 | const { videoId, threadId, user } = parameters | 307 | const { videoId, threadId, user } = parameters |
@@ -314,7 +314,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
314 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, | 314 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, |
315 | where: { | 315 | where: { |
316 | videoId, | 316 | videoId, |
317 | [ Op.or ]: [ | 317 | [Op.or]: [ |
318 | { id: threadId }, | 318 | { id: threadId }, |
319 | { originCommentId: threadId } | 319 | { originCommentId: threadId } |
320 | ], | 320 | ], |
@@ -346,7 +346,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
346 | order: [ [ 'createdAt', order ] ] as Order, | 346 | order: [ [ 'createdAt', order ] ] as Order, |
347 | where: { | 347 | where: { |
348 | id: { | 348 | id: { |
349 | [ Op.in ]: Sequelize.literal('(' + | 349 | [Op.in]: Sequelize.literal('(' + |
350 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + | 350 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + |
351 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + | 351 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + |
352 | 'UNION ' + | 352 | 'UNION ' + |
@@ -355,7 +355,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
355 | ') ' + | 355 | ') ' + |
356 | 'SELECT id FROM children' + | 356 | 'SELECT id FROM children' + |
357 | ')'), | 357 | ')'), |
358 | [ Op.ne ]: comment.id | 358 | [Op.ne]: comment.id |
359 | } | 359 | } |
360 | }, | 360 | }, |
361 | transaction: t | 361 | transaction: t |
@@ -461,7 +461,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
461 | } | 461 | } |
462 | 462 | ||
463 | isDeleted () { | 463 | isDeleted () { |
464 | return null !== this.deletedAt | 464 | return this.deletedAt !== null |
465 | } | 465 | } |
466 | 466 | ||
467 | extractMentions () { | 467 | extractMentions () { |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 67395e5c0..1fa66fd63 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -27,12 +27,13 @@ import { generateMagnetUri } from '@server/helpers/webtorrent' | |||
27 | export type VideoFormattingJSONOptions = { | 27 | export type VideoFormattingJSONOptions = { |
28 | completeDescription?: boolean | 28 | completeDescription?: boolean |
29 | additionalAttributes: { | 29 | additionalAttributes: { |
30 | state?: boolean, | 30 | state?: boolean |
31 | waitTranscoding?: boolean, | 31 | waitTranscoding?: boolean |
32 | scheduledUpdate?: boolean, | 32 | scheduledUpdate?: boolean |
33 | blacklistInfo?: boolean | 33 | blacklistInfo?: boolean |
34 | } | 34 | } |
35 | } | 35 | } |
36 | |||
36 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { | 37 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
37 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | 38 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined |
38 | 39 | ||
@@ -181,12 +182,10 @@ function videoFilesModelToFormattedJSON ( | |||
181 | ): VideoFile[] { | 182 | ): VideoFile[] { |
182 | return videoFiles | 183 | return videoFiles |
183 | .map(videoFile => { | 184 | .map(videoFile => { |
184 | let resolutionLabel = videoFile.resolution + 'p' | ||
185 | |||
186 | return { | 185 | return { |
187 | resolution: { | 186 | resolution: { |
188 | id: videoFile.resolution, | 187 | id: videoFile.resolution, |
189 | label: resolutionLabel | 188 | label: videoFile.resolution + 'p' |
190 | }, | 189 | }, |
191 | magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), | 190 | magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), |
192 | size: videoFile.size, | 191 | size: videoFile.size, |
@@ -214,7 +213,7 @@ function addVideoFilesInAPAcc ( | |||
214 | for (const file of files) { | 213 | for (const file of files) { |
215 | acc.push({ | 214 | acc.push({ |
216 | type: 'Link', | 215 | type: 'Link', |
217 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, | 216 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any, |
218 | href: model.getVideoFileUrl(file, baseUrlHttp), | 217 | href: model.getVideoFileUrl(file, baseUrlHttp), |
219 | height: file.resolution, | 218 | height: file.resolution, |
220 | size: file.size, | 219 | size: file.size, |
@@ -282,10 +281,8 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
282 | addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) | 281 | addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) |
283 | 282 | ||
284 | for (const playlist of (video.VideoStreamingPlaylists || [])) { | 283 | for (const playlist of (video.VideoStreamingPlaylists || [])) { |
285 | let tag: ActivityTagObject[] | 284 | const tag = playlist.p2pMediaLoaderInfohashes |
286 | 285 | .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) as ActivityTagObject[] | |
287 | tag = playlist.p2pMediaLoaderInfohashes | ||
288 | .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) | ||
289 | tag.push({ | 286 | tag.push({ |
290 | type: 'Link', | 287 | type: 'Link', |
291 | name: 'sha256', | 288 | name: 'sha256', |
@@ -308,11 +305,12 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
308 | for (const caption of video.VideoCaptions) { | 305 | for (const caption of video.VideoCaptions) { |
309 | subtitleLanguage.push({ | 306 | subtitleLanguage.push({ |
310 | identifier: caption.language, | 307 | identifier: caption.language, |
311 | name: VideoCaptionModel.getLanguageLabel(caption.language) | 308 | name: VideoCaptionModel.getLanguageLabel(caption.language), |
309 | url: caption.getFileUrl(video) | ||
312 | }) | 310 | }) |
313 | } | 311 | } |
314 | 312 | ||
315 | const miniature = video.getMiniature() | 313 | const icons = [ video.getMiniature(), video.getPreview() ] |
316 | 314 | ||
317 | return { | 315 | return { |
318 | type: 'Video' as 'Video', | 316 | type: 'Video' as 'Video', |
@@ -337,13 +335,13 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
337 | content: video.getTruncatedDescription(), | 335 | content: video.getTruncatedDescription(), |
338 | support: video.support, | 336 | support: video.support, |
339 | subtitleLanguage, | 337 | subtitleLanguage, |
340 | icon: { | 338 | icon: icons.map(i => ({ |
341 | type: 'Image', | 339 | type: 'Image', |
342 | url: miniature.getFileUrl(video.isOwned()), | 340 | url: i.getFileUrl(video), |
343 | mediaType: 'image/jpeg', | 341 | mediaType: 'image/jpeg', |
344 | width: miniature.width, | 342 | width: i.width, |
345 | height: miniature.height | 343 | height: i.height |
346 | }, | 344 | })), |
347 | url, | 345 | url, |
348 | likes: getVideoLikesActivityPubUrl(video), | 346 | likes: getVideoLikesActivityPubUrl(video), |
349 | dislikes: getVideoDislikesActivityPubUrl(video), | 347 | dislikes: getVideoDislikesActivityPubUrl(video), |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index f2d71357f..4ba16f5fd 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -120,10 +120,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
120 | } | 120 | } |
121 | 121 | ||
122 | static listForApi (options: { | 122 | static listForApi (options: { |
123 | start: number, | 123 | start: number |
124 | count: number, | 124 | count: number |
125 | videoPlaylistId: number, | 125 | videoPlaylistId: number |
126 | serverAccount: AccountModel, | 126 | serverAccount: AccountModel |
127 | user?: MUserAccountId | 127 | user?: MUserAccountId |
128 | }) { | 128 | }) { |
129 | const accountIds = [ options.serverAccount.id ] | 129 | const accountIds = [ options.serverAccount.id ] |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index bcdda36e5..4ca17ebec 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -68,12 +68,12 @@ type AvailableForListOptions = { | |||
68 | type?: VideoPlaylistType | 68 | type?: VideoPlaylistType |
69 | accountId?: number | 69 | accountId?: number |
70 | videoChannelId?: number | 70 | videoChannelId?: number |
71 | listMyPlaylists?: boolean, | 71 | listMyPlaylists?: boolean |
72 | search?: string | 72 | search?: string |
73 | } | 73 | } |
74 | 74 | ||
75 | @Scopes(() => ({ | 75 | @Scopes(() => ({ |
76 | [ ScopeNames.WITH_THUMBNAIL ]: { | 76 | [ScopeNames.WITH_THUMBNAIL]: { |
77 | include: [ | 77 | include: [ |
78 | { | 78 | { |
79 | model: ThumbnailModel, | 79 | model: ThumbnailModel, |
@@ -81,7 +81,7 @@ type AvailableForListOptions = { | |||
81 | } | 81 | } |
82 | ] | 82 | ] |
83 | }, | 83 | }, |
84 | [ ScopeNames.WITH_VIDEOS_LENGTH ]: { | 84 | [ScopeNames.WITH_VIDEOS_LENGTH]: { |
85 | attributes: { | 85 | attributes: { |
86 | include: [ | 86 | include: [ |
87 | [ | 87 | [ |
@@ -91,7 +91,7 @@ type AvailableForListOptions = { | |||
91 | ] | 91 | ] |
92 | } | 92 | } |
93 | } as FindOptions, | 93 | } as FindOptions, |
94 | [ ScopeNames.WITH_ACCOUNT ]: { | 94 | [ScopeNames.WITH_ACCOUNT]: { |
95 | include: [ | 95 | include: [ |
96 | { | 96 | { |
97 | model: AccountModel, | 97 | model: AccountModel, |
@@ -99,7 +99,7 @@ type AvailableForListOptions = { | |||
99 | } | 99 | } |
100 | ] | 100 | ] |
101 | }, | 101 | }, |
102 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { | 102 | [ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY]: { |
103 | include: [ | 103 | include: [ |
104 | { | 104 | { |
105 | model: AccountModel.scope(AccountScopeNames.SUMMARY), | 105 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
@@ -111,7 +111,7 @@ type AvailableForListOptions = { | |||
111 | } | 111 | } |
112 | ] | 112 | ] |
113 | }, | 113 | }, |
114 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { | 114 | [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: { |
115 | include: [ | 115 | include: [ |
116 | { | 116 | { |
117 | model: AccountModel, | 117 | model: AccountModel, |
@@ -123,7 +123,7 @@ type AvailableForListOptions = { | |||
123 | } | 123 | } |
124 | ] | 124 | ] |
125 | }, | 125 | }, |
126 | [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { | 126 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { |
127 | 127 | ||
128 | let whereActor: WhereOptions = {} | 128 | let whereActor: WhereOptions = {} |
129 | 129 | ||
@@ -138,13 +138,13 @@ type AvailableForListOptions = { | |||
138 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) | 138 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) |
139 | 139 | ||
140 | whereActor = { | 140 | whereActor = { |
141 | [ Op.or ]: [ | 141 | [Op.or]: [ |
142 | { | 142 | { |
143 | serverId: null | 143 | serverId: null |
144 | }, | 144 | }, |
145 | { | 145 | { |
146 | serverId: { | 146 | serverId: { |
147 | [ Op.in ]: literal(inQueryInstanceFollow) | 147 | [Op.in]: literal(inQueryInstanceFollow) |
148 | } | 148 | } |
149 | } | 149 | } |
150 | ] | 150 | ] |
@@ -172,7 +172,7 @@ type AvailableForListOptions = { | |||
172 | if (options.search) { | 172 | if (options.search) { |
173 | whereAnd.push({ | 173 | whereAnd.push({ |
174 | name: { | 174 | name: { |
175 | [ Op.iLike ]: '%' + options.search + '%' | 175 | [Op.iLike]: '%' + options.search + '%' |
176 | } | 176 | } |
177 | }) | 177 | }) |
178 | } | 178 | } |
@@ -299,13 +299,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
299 | 299 | ||
300 | static listForApi (options: { | 300 | static listForApi (options: { |
301 | followerActorId: number | 301 | followerActorId: number |
302 | start: number, | 302 | start: number |
303 | count: number, | 303 | count: number |
304 | sort: string, | 304 | sort: string |
305 | type?: VideoPlaylistType, | 305 | type?: VideoPlaylistType |
306 | accountId?: number, | 306 | accountId?: number |
307 | videoChannelId?: number, | 307 | videoChannelId?: number |
308 | listMyPlaylists?: boolean, | 308 | listMyPlaylists?: boolean |
309 | search?: string | 309 | search?: string |
310 | }) { | 310 | }) { |
311 | const query = { | 311 | const query = { |
@@ -369,7 +369,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
369 | model: VideoPlaylistElementModel.unscoped(), | 369 | model: VideoPlaylistElementModel.unscoped(), |
370 | where: { | 370 | where: { |
371 | videoId: { | 371 | videoId: { |
372 | [Op.in]: videoIds // FIXME: sequelize ANY seems broken | 372 | [Op.in]: videoIds |
373 | } | 373 | } |
374 | }, | 374 | }, |
375 | required: true | 375 | required: true |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index eacffe186..5964526a9 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, |
@@ -131,87 +120,19 @@ import { | |||
131 | MVideoFormattableDetails, | 120 | MVideoFormattableDetails, |
132 | MVideoForUser, | 121 | MVideoForUser, |
133 | MVideoFullLight, | 122 | MVideoFullLight, |
134 | MVideoIdThumbnail, | 123 | MVideoIdThumbnail, MVideoImmutable, |
135 | MVideoThumbnail, | 124 | MVideoThumbnail, |
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' |
144 | import { VideoFile } from '@shared/models/videos/video-file.model' | 132 | 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 | import { ModelCache } from '@server/models/model-cache' | |
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 | 136 | ||
216 | export enum ScopeNames { | 137 | export enum ScopeNames { |
217 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 138 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -224,6 +145,7 @@ export enum ScopeNames { | |||
224 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', | 145 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
225 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | 146 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', |
226 | WITH_USER_ID = 'WITH_USER_ID', | 147 | WITH_USER_ID = 'WITH_USER_ID', |
148 | WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES', | ||
227 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' | 149 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' |
228 | } | 150 | } |
229 | 151 | ||
@@ -267,7 +189,10 @@ export type AvailableForListIDsOptions = { | |||
267 | } | 189 | } |
268 | 190 | ||
269 | @Scopes(() => ({ | 191 | @Scopes(() => ({ |
270 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 192 | [ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: { |
193 | attributes: [ 'id', 'url', 'uuid', 'remote' ] | ||
194 | }, | ||
195 | [ScopeNames.FOR_API]: (options: ForAPIOptions) => { | ||
271 | const query: FindOptions = { | 196 | const query: FindOptions = { |
272 | include: [ | 197 | include: [ |
273 | { | 198 | { |
@@ -292,7 +217,7 @@ export type AvailableForListIDsOptions = { | |||
292 | if (options.ids) { | 217 | if (options.ids) { |
293 | query.where = { | 218 | query.where = { |
294 | id: { | 219 | id: { |
295 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken | 220 | [Op.in]: options.ids |
296 | } | 221 | } |
297 | } | 222 | } |
298 | } | 223 | } |
@@ -316,7 +241,7 @@ export type AvailableForListIDsOptions = { | |||
316 | 241 | ||
317 | return query | 242 | return query |
318 | }, | 243 | }, |
319 | [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { | 244 | [ScopeNames.AVAILABLE_FOR_LIST_IDS]: (options: AvailableForListIDsOptions) => { |
320 | const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] | 245 | const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] |
321 | 246 | ||
322 | const query: FindOptions = { | 247 | const query: FindOptions = { |
@@ -327,11 +252,11 @@ export type AvailableForListIDsOptions = { | |||
327 | const attributesType = options.attributesType || 'id' | 252 | const attributesType = options.attributesType || 'id' |
328 | 253 | ||
329 | if (attributesType === 'id') query.attributes = [ 'id' ] | 254 | if (attributesType === 'id') query.attributes = [ 'id' ] |
330 | else if (attributesType === 'none') query.attributes = [ ] | 255 | else if (attributesType === 'none') query.attributes = [] |
331 | 256 | ||
332 | whereAnd.push({ | 257 | whereAnd.push({ |
333 | id: { | 258 | id: { |
334 | [ Op.notIn ]: Sequelize.literal( | 259 | [Op.notIn]: Sequelize.literal( |
335 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | 260 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' |
336 | ) | 261 | ) |
337 | } | 262 | } |
@@ -340,7 +265,7 @@ export type AvailableForListIDsOptions = { | |||
340 | if (options.serverAccountId) { | 265 | if (options.serverAccountId) { |
341 | whereAnd.push({ | 266 | whereAnd.push({ |
342 | channelId: { | 267 | channelId: { |
343 | [ Op.notIn ]: Sequelize.literal( | 268 | [Op.notIn]: Sequelize.literal( |
344 | '(' + | 269 | '(' + |
345 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | 270 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + |
346 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | 271 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + |
@@ -353,15 +278,14 @@ export type AvailableForListIDsOptions = { | |||
353 | 278 | ||
354 | // Only list public/published videos | 279 | // Only list public/published videos |
355 | if (!options.filter || options.filter !== 'all-local') { | 280 | if (!options.filter || options.filter !== 'all-local') { |
356 | |||
357 | const publishWhere = { | 281 | const publishWhere = { |
358 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | 282 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding |
359 | [ Op.or ]: [ | 283 | [Op.or]: [ |
360 | { | 284 | { |
361 | state: VideoState.PUBLISHED | 285 | state: VideoState.PUBLISHED |
362 | }, | 286 | }, |
363 | { | 287 | { |
364 | [ Op.and ]: { | 288 | [Op.and]: { |
365 | state: VideoState.TO_TRANSCODE, | 289 | state: VideoState.TO_TRANSCODE, |
366 | waitTranscoding: false | 290 | waitTranscoding: false |
367 | } | 291 | } |
@@ -448,7 +372,7 @@ export type AvailableForListIDsOptions = { | |||
448 | [Op.or]: [ | 372 | [Op.or]: [ |
449 | { | 373 | { |
450 | id: { | 374 | id: { |
451 | [ Op.in ]: Sequelize.literal( | 375 | [Op.in]: Sequelize.literal( |
452 | '(' + | 376 | '(' + |
453 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | 377 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + |
454 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 378 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
@@ -459,7 +383,7 @@ export type AvailableForListIDsOptions = { | |||
459 | }, | 383 | }, |
460 | { | 384 | { |
461 | id: { | 385 | id: { |
462 | [ Op.in ]: Sequelize.literal( | 386 | [Op.in]: Sequelize.literal( |
463 | '(' + | 387 | '(' + |
464 | 'SELECT "video"."id" AS "id" FROM "video" ' + | 388 | 'SELECT "video"."id" AS "id" FROM "video" ' + |
465 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 389 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
@@ -479,7 +403,7 @@ export type AvailableForListIDsOptions = { | |||
479 | if (options.withFiles === true) { | 403 | if (options.withFiles === true) { |
480 | whereAnd.push({ | 404 | whereAnd.push({ |
481 | id: { | 405 | id: { |
482 | [ Op.in ]: Sequelize.literal( | 406 | [Op.in]: Sequelize.literal( |
483 | '(SELECT "videoId" FROM "videoFile")' | 407 | '(SELECT "videoId" FROM "videoFile")' |
484 | ) | 408 | ) |
485 | } | 409 | } |
@@ -493,7 +417,7 @@ export type AvailableForListIDsOptions = { | |||
493 | 417 | ||
494 | whereAnd.push({ | 418 | whereAnd.push({ |
495 | id: { | 419 | id: { |
496 | [ Op.in ]: Sequelize.literal( | 420 | [Op.in]: Sequelize.literal( |
497 | '(' + | 421 | '(' + |
498 | 'SELECT "videoId" FROM "videoTag" ' + | 422 | 'SELECT "videoId" FROM "videoTag" ' + |
499 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 423 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -509,7 +433,7 @@ export type AvailableForListIDsOptions = { | |||
509 | 433 | ||
510 | whereAnd.push({ | 434 | whereAnd.push({ |
511 | id: { | 435 | id: { |
512 | [ Op.in ]: Sequelize.literal( | 436 | [Op.in]: Sequelize.literal( |
513 | '(' + | 437 | '(' + |
514 | 'SELECT "videoId" FROM "videoTag" ' + | 438 | 'SELECT "videoId" FROM "videoTag" ' + |
515 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 439 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -529,7 +453,7 @@ export type AvailableForListIDsOptions = { | |||
529 | if (options.categoryOneOf) { | 453 | if (options.categoryOneOf) { |
530 | whereAnd.push({ | 454 | whereAnd.push({ |
531 | category: { | 455 | category: { |
532 | [ Op.or ]: options.categoryOneOf | 456 | [Op.or]: options.categoryOneOf |
533 | } | 457 | } |
534 | }) | 458 | }) |
535 | } | 459 | } |
@@ -537,7 +461,7 @@ export type AvailableForListIDsOptions = { | |||
537 | if (options.licenceOneOf) { | 461 | if (options.licenceOneOf) { |
538 | whereAnd.push({ | 462 | whereAnd.push({ |
539 | licence: { | 463 | licence: { |
540 | [ Op.or ]: options.licenceOneOf | 464 | [Op.or]: options.licenceOneOf |
541 | } | 465 | } |
542 | }) | 466 | }) |
543 | } | 467 | } |
@@ -552,12 +476,12 @@ export type AvailableForListIDsOptions = { | |||
552 | [Op.or]: [ | 476 | [Op.or]: [ |
553 | { | 477 | { |
554 | language: { | 478 | language: { |
555 | [ Op.or ]: videoLanguages | 479 | [Op.or]: videoLanguages |
556 | } | 480 | } |
557 | }, | 481 | }, |
558 | { | 482 | { |
559 | id: { | 483 | id: { |
560 | [ Op.in ]: Sequelize.literal( | 484 | [Op.in]: Sequelize.literal( |
561 | '(' + | 485 | '(' + |
562 | 'SELECT "videoId" FROM "videoCaption" ' + | 486 | 'SELECT "videoId" FROM "videoCaption" ' + |
563 | 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + | 487 | 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + |
@@ -591,12 +515,12 @@ export type AvailableForListIDsOptions = { | |||
591 | } | 515 | } |
592 | 516 | ||
593 | query.where = { | 517 | query.where = { |
594 | [ Op.and ]: whereAnd | 518 | [Op.and]: whereAnd |
595 | } | 519 | } |
596 | 520 | ||
597 | return query | 521 | return query |
598 | }, | 522 | }, |
599 | [ ScopeNames.WITH_THUMBNAILS ]: { | 523 | [ScopeNames.WITH_THUMBNAILS]: { |
600 | include: [ | 524 | include: [ |
601 | { | 525 | { |
602 | model: ThumbnailModel, | 526 | model: ThumbnailModel, |
@@ -604,7 +528,7 @@ export type AvailableForListIDsOptions = { | |||
604 | } | 528 | } |
605 | ] | 529 | ] |
606 | }, | 530 | }, |
607 | [ ScopeNames.WITH_USER_ID ]: { | 531 | [ScopeNames.WITH_USER_ID]: { |
608 | include: [ | 532 | include: [ |
609 | { | 533 | { |
610 | attributes: [ 'accountId' ], | 534 | attributes: [ 'accountId' ], |
@@ -620,7 +544,7 @@ export type AvailableForListIDsOptions = { | |||
620 | } | 544 | } |
621 | ] | 545 | ] |
622 | }, | 546 | }, |
623 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 547 | [ScopeNames.WITH_ACCOUNT_DETAILS]: { |
624 | include: [ | 548 | include: [ |
625 | { | 549 | { |
626 | model: VideoChannelModel.unscoped(), | 550 | model: VideoChannelModel.unscoped(), |
@@ -672,10 +596,10 @@ export type AvailableForListIDsOptions = { | |||
672 | } | 596 | } |
673 | ] | 597 | ] |
674 | }, | 598 | }, |
675 | [ ScopeNames.WITH_TAGS ]: { | 599 | [ScopeNames.WITH_TAGS]: { |
676 | include: [ TagModel ] | 600 | include: [ TagModel ] |
677 | }, | 601 | }, |
678 | [ ScopeNames.WITH_BLACKLISTED ]: { | 602 | [ScopeNames.WITH_BLACKLISTED]: { |
679 | include: [ | 603 | include: [ |
680 | { | 604 | { |
681 | attributes: [ 'id', 'reason', 'unfederated' ], | 605 | attributes: [ 'id', 'reason', 'unfederated' ], |
@@ -684,7 +608,7 @@ export type AvailableForListIDsOptions = { | |||
684 | } | 608 | } |
685 | ] | 609 | ] |
686 | }, | 610 | }, |
687 | [ ScopeNames.WITH_WEBTORRENT_FILES ]: (withRedundancies = false) => { | 611 | [ScopeNames.WITH_WEBTORRENT_FILES]: (withRedundancies = false) => { |
688 | let subInclude: any[] = [] | 612 | let subInclude: any[] = [] |
689 | 613 | ||
690 | if (withRedundancies === true) { | 614 | if (withRedundancies === true) { |
@@ -708,7 +632,7 @@ export type AvailableForListIDsOptions = { | |||
708 | ] | 632 | ] |
709 | } | 633 | } |
710 | }, | 634 | }, |
711 | [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { | 635 | [ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => { |
712 | const subInclude: IncludeOptions[] = [ | 636 | const subInclude: IncludeOptions[] = [ |
713 | { | 637 | { |
714 | model: VideoFileModel.unscoped(), | 638 | model: VideoFileModel.unscoped(), |
@@ -735,7 +659,7 @@ export type AvailableForListIDsOptions = { | |||
735 | ] | 659 | ] |
736 | } | 660 | } |
737 | }, | 661 | }, |
738 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 662 | [ScopeNames.WITH_SCHEDULED_UPDATE]: { |
739 | include: [ | 663 | include: [ |
740 | { | 664 | { |
741 | model: ScheduleVideoUpdateModel.unscoped(), | 665 | model: ScheduleVideoUpdateModel.unscoped(), |
@@ -743,7 +667,7 @@ export type AvailableForListIDsOptions = { | |||
743 | } | 667 | } |
744 | ] | 668 | ] |
745 | }, | 669 | }, |
746 | [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { | 670 | [ScopeNames.WITH_USER_HISTORY]: (userId: number) => { |
747 | return { | 671 | return { |
748 | include: [ | 672 | include: [ |
749 | { | 673 | { |
@@ -760,7 +684,72 @@ export type AvailableForListIDsOptions = { | |||
760 | })) | 684 | })) |
761 | @Table({ | 685 | @Table({ |
762 | tableName: 'video', | 686 | tableName: 'video', |
763 | indexes | 687 | indexes: [ |
688 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
689 | |||
690 | { fields: [ 'createdAt' ] }, | ||
691 | { | ||
692 | fields: [ | ||
693 | { name: 'publishedAt', order: 'DESC' }, | ||
694 | { name: 'id', order: 'ASC' } | ||
695 | ] | ||
696 | }, | ||
697 | { fields: [ 'duration' ] }, | ||
698 | { fields: [ 'views' ] }, | ||
699 | { fields: [ 'channelId' ] }, | ||
700 | { | ||
701 | fields: [ 'originallyPublishedAt' ], | ||
702 | where: { | ||
703 | originallyPublishedAt: { | ||
704 | [Op.ne]: null | ||
705 | } | ||
706 | } | ||
707 | }, | ||
708 | { | ||
709 | fields: [ 'category' ], // We don't care videos with an unknown category | ||
710 | where: { | ||
711 | category: { | ||
712 | [Op.ne]: null | ||
713 | } | ||
714 | } | ||
715 | }, | ||
716 | { | ||
717 | fields: [ 'licence' ], // We don't care videos with an unknown licence | ||
718 | where: { | ||
719 | licence: { | ||
720 | [Op.ne]: null | ||
721 | } | ||
722 | } | ||
723 | }, | ||
724 | { | ||
725 | fields: [ 'language' ], // We don't care videos with an unknown language | ||
726 | where: { | ||
727 | language: { | ||
728 | [Op.ne]: null | ||
729 | } | ||
730 | } | ||
731 | }, | ||
732 | { | ||
733 | fields: [ 'nsfw' ], // Most of the videos are not NSFW | ||
734 | where: { | ||
735 | nsfw: true | ||
736 | } | ||
737 | }, | ||
738 | { | ||
739 | fields: [ 'remote' ], // Only index local videos | ||
740 | where: { | ||
741 | remote: false | ||
742 | } | ||
743 | }, | ||
744 | { | ||
745 | fields: [ 'uuid' ], | ||
746 | unique: true | ||
747 | }, | ||
748 | { | ||
749 | fields: [ 'url' ], | ||
750 | unique: true | ||
751 | } | ||
752 | ] | ||
764 | }) | 753 | }) |
765 | export class VideoModel extends Model<VideoModel> { | 754 | export class VideoModel extends Model<VideoModel> { |
766 | 755 | ||
@@ -1031,7 +1020,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1031 | }, | 1020 | }, |
1032 | onDelete: 'cascade', | 1021 | onDelete: 'cascade', |
1033 | hooks: true, | 1022 | hooks: true, |
1034 | [ 'separate' as any ]: true | 1023 | ['separate' as any]: true |
1035 | }) | 1024 | }) |
1036 | VideoCaptions: VideoCaptionModel[] | 1025 | VideoCaptions: VideoCaptionModel[] |
1037 | 1026 | ||
@@ -1090,6 +1079,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1090 | return undefined | 1079 | return undefined |
1091 | } | 1080 | } |
1092 | 1081 | ||
1082 | @BeforeDestroy | ||
1083 | static invalidateCache (instance: VideoModel) { | ||
1084 | ModelCache.Instance.invalidateCache('video', instance.id) | ||
1085 | } | ||
1086 | |||
1093 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { | 1087 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
1094 | const query = { | 1088 | const query = { |
1095 | where: { | 1089 | where: { |
@@ -1127,16 +1121,16 @@ export class VideoModel extends Model<VideoModel> { | |||
1127 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings | 1121 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings |
1128 | where: { | 1122 | where: { |
1129 | id: { | 1123 | id: { |
1130 | [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') | 1124 | [Op.in]: Sequelize.literal('(' + rawQuery + ')') |
1131 | }, | 1125 | }, |
1132 | [ Op.or ]: [ | 1126 | [Op.or]: [ |
1133 | { privacy: VideoPrivacy.PUBLIC }, | 1127 | { privacy: VideoPrivacy.PUBLIC }, |
1134 | { privacy: VideoPrivacy.UNLISTED } | 1128 | { privacy: VideoPrivacy.UNLISTED } |
1135 | ] | 1129 | ] |
1136 | }, | 1130 | }, |
1137 | include: [ | 1131 | include: [ |
1138 | { | 1132 | { |
1139 | attributes: [ 'language' ], | 1133 | attributes: [ 'language', 'fileUrl' ], |
1140 | model: VideoCaptionModel.unscoped(), | 1134 | model: VideoCaptionModel.unscoped(), |
1141 | required: false | 1135 | required: false |
1142 | }, | 1136 | }, |
@@ -1146,10 +1140,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1146 | required: false, | 1140 | required: false, |
1147 | // We only want videos shared by this actor | 1141 | // We only want videos shared by this actor |
1148 | where: { | 1142 | where: { |
1149 | [ Op.and ]: [ | 1143 | [Op.and]: [ |
1150 | { | 1144 | { |
1151 | id: { | 1145 | id: { |
1152 | [ Op.not ]: null | 1146 | [Op.not]: null |
1153 | } | 1147 | } |
1154 | }, | 1148 | }, |
1155 | { | 1149 | { |
@@ -1199,8 +1193,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1199 | // totals: totalVideos + totalVideoShares | 1193 | // totals: totalVideos + totalVideoShares |
1200 | let totalVideos = 0 | 1194 | let totalVideos = 0 |
1201 | let totalVideoShares = 0 | 1195 | let totalVideoShares = 0 |
1202 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) | 1196 | if (totals[0]) totalVideos = parseInt(totals[0].total, 10) |
1203 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) | 1197 | if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) |
1204 | 1198 | ||
1205 | const total = totalVideos + totalVideoShares | 1199 | const total = totalVideos + totalVideoShares |
1206 | return { | 1200 | return { |
@@ -1243,7 +1237,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1243 | baseQuery = Object.assign(baseQuery, { | 1237 | baseQuery = Object.assign(baseQuery, { |
1244 | where: { | 1238 | where: { |
1245 | name: { | 1239 | name: { |
1246 | [ Op.iLike ]: '%' + search + '%' | 1240 | [Op.iLike]: '%' + search + '%' |
1247 | } | 1241 | } |
1248 | } | 1242 | } |
1249 | }) | 1243 | }) |
@@ -1273,25 +1267,25 @@ export class VideoModel extends Model<VideoModel> { | |||
1273 | } | 1267 | } |
1274 | 1268 | ||
1275 | static async listForApi (options: { | 1269 | static async listForApi (options: { |
1276 | start: number, | 1270 | start: number |
1277 | count: number, | 1271 | count: number |
1278 | sort: string, | 1272 | sort: string |
1279 | nsfw: boolean, | 1273 | nsfw: boolean |
1280 | includeLocalVideos: boolean, | 1274 | includeLocalVideos: boolean |
1281 | withFiles: boolean, | 1275 | withFiles: boolean |
1282 | categoryOneOf?: number[], | 1276 | categoryOneOf?: number[] |
1283 | licenceOneOf?: number[], | 1277 | licenceOneOf?: number[] |
1284 | languageOneOf?: string[], | 1278 | languageOneOf?: string[] |
1285 | tagsOneOf?: string[], | 1279 | tagsOneOf?: string[] |
1286 | tagsAllOf?: string[], | 1280 | tagsAllOf?: string[] |
1287 | filter?: VideoFilter, | 1281 | filter?: VideoFilter |
1288 | accountId?: number, | 1282 | accountId?: number |
1289 | videoChannelId?: number, | 1283 | videoChannelId?: number |
1290 | followerActorId?: number | 1284 | followerActorId?: number |
1291 | videoPlaylistId?: number, | 1285 | videoPlaylistId?: number |
1292 | trendingDays?: number, | 1286 | trendingDays?: number |
1293 | user?: MUserAccountId, | 1287 | user?: MUserAccountId |
1294 | historyOfUser?: MUserId, | 1288 | historyOfUser?: MUserId |
1295 | countVideos?: boolean | 1289 | countVideos?: boolean |
1296 | }) { | 1290 | }) { |
1297 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1291 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
@@ -1357,7 +1351,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1357 | tagsAllOf?: string[] | 1351 | tagsAllOf?: string[] |
1358 | durationMin?: number // seconds | 1352 | durationMin?: number // seconds |
1359 | durationMax?: number // seconds | 1353 | durationMax?: number // seconds |
1360 | user?: MUserAccountId, | 1354 | user?: MUserAccountId |
1361 | filter?: VideoFilter | 1355 | filter?: VideoFilter |
1362 | }) { | 1356 | }) { |
1363 | const whereAnd = [] | 1357 | const whereAnd = [] |
@@ -1365,8 +1359,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1365 | if (options.startDate || options.endDate) { | 1359 | if (options.startDate || options.endDate) { |
1366 | const publishedAtRange = {} | 1360 | const publishedAtRange = {} |
1367 | 1361 | ||
1368 | if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate | 1362 | if (options.startDate) publishedAtRange[Op.gte] = options.startDate |
1369 | if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate | 1363 | if (options.endDate) publishedAtRange[Op.lte] = options.endDate |
1370 | 1364 | ||
1371 | whereAnd.push({ publishedAt: publishedAtRange }) | 1365 | whereAnd.push({ publishedAt: publishedAtRange }) |
1372 | } | 1366 | } |
@@ -1374,8 +1368,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1374 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { | 1368 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { |
1375 | const originallyPublishedAtRange = {} | 1369 | const originallyPublishedAtRange = {} |
1376 | 1370 | ||
1377 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate | 1371 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[Op.gte] = options.originallyPublishedStartDate |
1378 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate | 1372 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[Op.lte] = options.originallyPublishedEndDate |
1379 | 1373 | ||
1380 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) | 1374 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) |
1381 | } | 1375 | } |
@@ -1383,8 +1377,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1383 | if (options.durationMin || options.durationMax) { | 1377 | if (options.durationMin || options.durationMax) { |
1384 | const durationRange = {} | 1378 | const durationRange = {} |
1385 | 1379 | ||
1386 | if (options.durationMin) durationRange[ Op.gte ] = options.durationMin | 1380 | if (options.durationMin) durationRange[Op.gte] = options.durationMin |
1387 | if (options.durationMax) durationRange[ Op.lte ] = options.durationMax | 1381 | if (options.durationMax) durationRange[Op.lte] = options.durationMax |
1388 | 1382 | ||
1389 | whereAnd.push({ duration: durationRange }) | 1383 | whereAnd.push({ duration: durationRange }) |
1390 | } | 1384 | } |
@@ -1395,7 +1389,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1395 | if (options.search) { | 1389 | if (options.search) { |
1396 | const trigramSearch = { | 1390 | const trigramSearch = { |
1397 | id: { | 1391 | id: { |
1398 | [ Op.in ]: Sequelize.literal( | 1392 | [Op.in]: Sequelize.literal( |
1399 | '(' + | 1393 | '(' + |
1400 | 'SELECT "video"."id" FROM "video" ' + | 1394 | 'SELECT "video"."id" FROM "video" ' + |
1401 | 'WHERE ' + | 1395 | 'WHERE ' + |
@@ -1484,6 +1478,24 @@ export class VideoModel extends Model<VideoModel> { | |||
1484 | ]).findOne(options) | 1478 | ]).findOne(options) |
1485 | } | 1479 | } |
1486 | 1480 | ||
1481 | static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> { | ||
1482 | const fun = () => { | ||
1483 | const query = { | ||
1484 | where: buildWhereIdOrUUID(id), | ||
1485 | transaction: t | ||
1486 | } | ||
1487 | |||
1488 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1489 | } | ||
1490 | |||
1491 | return ModelCache.Instance.doCache({ | ||
1492 | cacheType: 'load-video-immutable-id', | ||
1493 | key: '' + id, | ||
1494 | deleteKey: 'video', | ||
1495 | fun | ||
1496 | }) | ||
1497 | } | ||
1498 | |||
1487 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { | 1499 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { |
1488 | const where = buildWhereIdOrUUID(id) | 1500 | const where = buildWhereIdOrUUID(id) |
1489 | const options = { | 1501 | const options = { |
@@ -1547,6 +1559,26 @@ export class VideoModel extends Model<VideoModel> { | |||
1547 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) | 1559 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1548 | } | 1560 | } |
1549 | 1561 | ||
1562 | static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> { | ||
1563 | const fun = () => { | ||
1564 | const query: FindOptions = { | ||
1565 | where: { | ||
1566 | url | ||
1567 | }, | ||
1568 | transaction | ||
1569 | } | ||
1570 | |||
1571 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1572 | } | ||
1573 | |||
1574 | return ModelCache.Instance.doCache({ | ||
1575 | cacheType: 'load-video-immutable-url', | ||
1576 | key: url, | ||
1577 | deleteKey: 'video', | ||
1578 | fun | ||
1579 | }) | ||
1580 | } | ||
1581 | |||
1550 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { | 1582 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { |
1551 | const query: FindOptions = { | 1583 | const query: FindOptions = { |
1552 | where: { | 1584 | where: { |
@@ -1593,8 +1625,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1593 | } | 1625 | } |
1594 | 1626 | ||
1595 | static loadForGetAPI (parameters: { | 1627 | static loadForGetAPI (parameters: { |
1596 | id: number | string, | 1628 | id: number | string |
1597 | t?: Transaction, | 1629 | t?: Transaction |
1598 | userId?: number | 1630 | userId?: number |
1599 | }): Bluebird<MVideoDetails> { | 1631 | }): Bluebird<MVideoDetails> { |
1600 | const { id, t, userId } = parameters | 1632 | const { id, t, userId } = parameters |
@@ -1660,9 +1692,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1660 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { | 1692 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { |
1661 | // Instances only share videos | 1693 | // Instances only share videos |
1662 | const query = 'SELECT 1 FROM "videoShare" ' + | 1694 | const query = 'SELECT 1 FROM "videoShare" ' + |
1663 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 1695 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
1664 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + | 1696 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + |
1665 | 'LIMIT 1' | 1697 | 'LIMIT 1' |
1666 | 1698 | ||
1667 | const options = { | 1699 | const options = { |
1668 | type: QueryTypes.SELECT as QueryTypes.SELECT, | 1700 | type: QueryTypes.SELECT as QueryTypes.SELECT, |
@@ -1694,7 +1726,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1694 | } | 1726 | } |
1695 | 1727 | ||
1696 | return VideoModel.findAll(query) | 1728 | return VideoModel.findAll(query) |
1697 | .then(videos => videos.map(v => v.id)) | 1729 | .then(videos => videos.map(v => v.id)) |
1698 | } | 1730 | } |
1699 | 1731 | ||
1700 | // threshold corresponds to how many video the field should have to be returned | 1732 | // threshold corresponds to how many video the field should have to be returned |
@@ -1714,14 +1746,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1714 | limit: count, | 1746 | limit: count, |
1715 | group: field, | 1747 | group: field, |
1716 | having: Sequelize.where( | 1748 | having: Sequelize.where( |
1717 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } | 1749 | Sequelize.fn('COUNT', Sequelize.col(field)), { [Op.gte]: threshold } |
1718 | ), | 1750 | ), |
1719 | order: [ (this.sequelize as any).random() ] | 1751 | order: [ (this.sequelize as any).random() ] |
1720 | } | 1752 | } |
1721 | 1753 | ||
1722 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) | 1754 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) |
1723 | .findAll(query) | 1755 | .findAll(query) |
1724 | .then(rows => rows.map(r => r[ field ])) | 1756 | .then(rows => rows.map(r => r[field])) |
1725 | } | 1757 | } |
1726 | 1758 | ||
1727 | static buildTrendingQuery (trendingDays: number) { | 1759 | static buildTrendingQuery (trendingDays: number) { |
@@ -1732,7 +1764,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1732 | required: false, | 1764 | required: false, |
1733 | where: { | 1765 | where: { |
1734 | startDate: { | 1766 | startDate: { |
1735 | [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) | 1767 | [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) |
1736 | } | 1768 | } |
1737 | } | 1769 | } |
1738 | } | 1770 | } |
@@ -1815,23 +1847,23 @@ export class VideoModel extends Model<VideoModel> { | |||
1815 | } | 1847 | } |
1816 | 1848 | ||
1817 | static getCategoryLabel (id: number) { | 1849 | static getCategoryLabel (id: number) { |
1818 | return VIDEO_CATEGORIES[ id ] || 'Misc' | 1850 | return VIDEO_CATEGORIES[id] || 'Misc' |
1819 | } | 1851 | } |
1820 | 1852 | ||
1821 | static getLicenceLabel (id: number) { | 1853 | static getLicenceLabel (id: number) { |
1822 | return VIDEO_LICENCES[ id ] || 'Unknown' | 1854 | return VIDEO_LICENCES[id] || 'Unknown' |
1823 | } | 1855 | } |
1824 | 1856 | ||
1825 | static getLanguageLabel (id: string) { | 1857 | static getLanguageLabel (id: string) { |
1826 | return VIDEO_LANGUAGES[ id ] || 'Unknown' | 1858 | return VIDEO_LANGUAGES[id] || 'Unknown' |
1827 | } | 1859 | } |
1828 | 1860 | ||
1829 | static getPrivacyLabel (id: number) { | 1861 | static getPrivacyLabel (id: number) { |
1830 | return VIDEO_PRIVACIES[ id ] || 'Unknown' | 1862 | return VIDEO_PRIVACIES[id] || 'Unknown' |
1831 | } | 1863 | } |
1832 | 1864 | ||
1833 | static getStateLabel (id: number) { | 1865 | static getStateLabel (id: number) { |
1834 | return VIDEO_STATES[ id ] || 'Unknown' | 1866 | return VIDEO_STATES[id] || 'Unknown' |
1835 | } | 1867 | } |
1836 | 1868 | ||
1837 | isBlacklisted () { | 1869 | isBlacklisted () { |
@@ -1843,7 +1875,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1843 | this.VideoChannel.Account.isBlocked() | 1875 | this.VideoChannel.Account.isBlocked() |
1844 | } | 1876 | } |
1845 | 1877 | ||
1846 | getQualityFileBy <T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { | 1878 | getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { |
1847 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { | 1879 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { |
1848 | const file = fun(this.VideoFiles, file => file.resolution) | 1880 | const file = fun(this.VideoFiles, file => file.resolution) |
1849 | 1881 | ||
@@ -1861,15 +1893,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1861 | return undefined | 1893 | return undefined |
1862 | } | 1894 | } |
1863 | 1895 | ||
1864 | getMaxQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1896 | getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1865 | return this.getQualityFileBy(maxBy) | 1897 | return this.getQualityFileBy(maxBy) |
1866 | } | 1898 | } |
1867 | 1899 | ||
1868 | getMinQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1900 | getMinQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1869 | return this.getQualityFileBy(minBy) | 1901 | return this.getQualityFileBy(minBy) |
1870 | } | 1902 | } |
1871 | 1903 | ||
1872 | getWebTorrentFile <T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { | 1904 | getWebTorrentFile<T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { |
1873 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1905 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1874 | 1906 | ||
1875 | const file = this.VideoFiles.find(f => f.resolution === resolution) | 1907 | const file = this.VideoFiles.find(f => f.resolution === resolution) |
@@ -1992,8 +2024,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1992 | } | 2024 | } |
1993 | 2025 | ||
1994 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists | 2026 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists |
1995 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) | 2027 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) |
1996 | .concat(toAdd) | 2028 | .concat(toAdd) |
1997 | } | 2029 | } |
1998 | 2030 | ||
1999 | removeFile (videoFile: MVideoFile, isRedundancy = false) { | 2031 | removeFile (videoFile: MVideoFile, isRedundancy = false) { |
@@ -2014,7 +2046,7 @@ export class VideoModel extends Model<VideoModel> { | |||
2014 | await remove(directoryPath) | 2046 | await remove(directoryPath) |
2015 | 2047 | ||
2016 | if (isRedundancy !== true) { | 2048 | if (isRedundancy !== true) { |
2017 | let streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo | 2049 | const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo |
2018 | streamingPlaylistWithFiles.Video = this | 2050 | streamingPlaylistWithFiles.Video = this |
2019 | 2051 | ||
2020 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { | 2052 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { |