aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/accounts.ts27
-rw-r--r--server/controllers/api/users/me.ts3
-rw-r--r--server/controllers/api/users/my-subscriptions.ts23
-rw-r--r--server/controllers/api/video-channel.ts23
-rw-r--r--server/controllers/api/videos/index.ts25
-rw-r--r--server/helpers/custom-validators/search.ts4
-rw-r--r--server/initializers/constants.ts5
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts7
-rw-r--r--server/lib/video.ts8
-rw-r--r--server/middlewares/validators/videos/videos.ts8
-rw-r--r--server/models/video/video-query-builder.ts12
-rw-r--r--server/models/video/video.ts52
-rw-r--r--server/tests/api/live/live.ts65
-rw-r--r--server/tests/api/search/search-videos.ts49
-rw-r--r--server/tests/api/videos/single-server.ts4
-rw-r--r--server/tests/api/videos/video-transcoder.ts7
16 files changed, 233 insertions, 89 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index e31924a94..49a8e3195 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,9 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getServerActor } from '@server/models/application/application' 2import { getServerActor } from '@server/models/application/application'
3import { VideosWithSearchCommonQuery } from '@shared/models'
3import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 4import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
4import { getFormattedObjects } from '../../helpers/utils' 5import { getFormattedObjects } from '../../helpers/utils'
5import { Hooks } from '../../lib/plugins/hooks'
6import { JobQueue } from '../../lib/job-queue' 6import { JobQueue } from '../../lib/job-queue'
7import { Hooks } from '../../lib/plugins/hooks'
7import { 8import {
8 asyncMiddleware, 9 asyncMiddleware,
9 authenticate, 10 authenticate,
@@ -158,25 +159,27 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
158 const account = res.locals.account 159 const account = res.locals.account
159 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 160 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
160 const countVideos = getCountVideos(req) 161 const countVideos = getCountVideos(req)
162 const query = req.query as VideosWithSearchCommonQuery
161 163
162 const apiOptions = await Hooks.wrapObject({ 164 const apiOptions = await Hooks.wrapObject({
163 followerActorId, 165 followerActorId,
164 start: req.query.start, 166 start: query.start,
165 count: req.query.count, 167 count: query.count,
166 sort: req.query.sort, 168 sort: query.sort,
167 includeLocalVideos: true, 169 includeLocalVideos: true,
168 categoryOneOf: req.query.categoryOneOf, 170 categoryOneOf: query.categoryOneOf,
169 licenceOneOf: req.query.licenceOneOf, 171 licenceOneOf: query.licenceOneOf,
170 languageOneOf: req.query.languageOneOf, 172 languageOneOf: query.languageOneOf,
171 tagsOneOf: req.query.tagsOneOf, 173 tagsOneOf: query.tagsOneOf,
172 tagsAllOf: req.query.tagsAllOf, 174 tagsAllOf: query.tagsAllOf,
173 filter: req.query.filter, 175 filter: query.filter,
174 nsfw: buildNSFWFilter(res, req.query.nsfw), 176 isLive: query.isLive,
177 nsfw: buildNSFWFilter(res, query.nsfw),
175 withFiles: false, 178 withFiles: false,
176 accountId: account.id, 179 accountId: account.id,
177 user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 180 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
178 countVideos, 181 countVideos,
179 search: req.query.search 182 search: query.search
180 }, 'filter:api.accounts.videos.list.params') 183 }, 'filter:api.accounts.videos.list.params')
181 184
182 const resultList = await Hooks.wrapPromiseFun( 185 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 9f9d2d77f..0763d1900 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -111,7 +111,8 @@ async function getUserVideos (req: express.Request, res: express.Response) {
111 start: req.query.start, 111 start: req.query.start,
112 count: req.query.count, 112 count: req.query.count,
113 sort: req.query.sort, 113 sort: req.query.sort,
114 search: req.query.search 114 search: req.query.search,
115 isLive: req.query.isLive
115 }, 'filter:api.user.me.videos.list.params') 116 }, 'filter:api.user.me.videos.list.params')
116 117
117 const resultList = await Hooks.wrapPromiseFun( 118 const resultList = await Hooks.wrapPromiseFun(
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index e8949ee59..56b93276f 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -2,8 +2,8 @@ import 'multer'
2import * as express from 'express' 2import * as express from 'express'
3import { sendUndoFollow } from '@server/lib/activitypub/send' 3import { sendUndoFollow } from '@server/lib/activitypub/send'
4import { VideoChannelModel } from '@server/models/video/video-channel' 4import { VideoChannelModel } from '@server/models/video/video-channel'
5import { VideosCommonQuery } from '@shared/models'
5import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
6import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
7import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 7import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
8import { getFormattedObjects } from '../../../helpers/utils' 8import { getFormattedObjects } from '../../../helpers/utils'
9import { WEBSERVER } from '../../../initializers/constants' 9import { WEBSERVER } from '../../../initializers/constants'
@@ -170,19 +170,20 @@ async function getUserSubscriptions (req: express.Request, res: express.Response
170async function getUserSubscriptionVideos (req: express.Request, res: express.Response) { 170async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
171 const user = res.locals.oauth.token.User 171 const user = res.locals.oauth.token.User
172 const countVideos = getCountVideos(req) 172 const countVideos = getCountVideos(req)
173 const query = req.query as VideosCommonQuery
173 174
174 const resultList = await VideoModel.listForApi({ 175 const resultList = await VideoModel.listForApi({
175 start: req.query.start, 176 start: query.start,
176 count: req.query.count, 177 count: query.count,
177 sort: req.query.sort, 178 sort: query.sort,
178 includeLocalVideos: false, 179 includeLocalVideos: false,
179 categoryOneOf: req.query.categoryOneOf, 180 categoryOneOf: query.categoryOneOf,
180 licenceOneOf: req.query.licenceOneOf, 181 licenceOneOf: query.licenceOneOf,
181 languageOneOf: req.query.languageOneOf, 182 languageOneOf: query.languageOneOf,
182 tagsOneOf: req.query.tagsOneOf, 183 tagsOneOf: query.tagsOneOf,
183 tagsAllOf: req.query.tagsAllOf, 184 tagsAllOf: query.tagsAllOf,
184 nsfw: buildNSFWFilter(res, req.query.nsfw), 185 nsfw: buildNSFWFilter(res, query.nsfw),
185 filter: req.query.filter as VideoFilter, 186 filter: query.filter,
186 withFiles: false, 187 withFiles: false,
187 followerActorId: user.Account.Actor.id, 188 followerActorId: user.Account.Actor.id,
188 user, 189 user,
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 149d6cfb4..a755d7e57 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import { Hooks } from '@server/lib/plugins/hooks' 2import { Hooks } from '@server/lib/plugins/hooks'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { MChannelBannerAccountDefault } from '@server/types/models' 4import { MChannelBannerAccountDefault } from '@server/types/models'
5import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 5import { ActorImageType, VideoChannelCreate, VideoChannelUpdate, VideosCommonQuery } from '../../../shared'
6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes' 6import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 7import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
8import { resetSequelizeInstance } from '../../helpers/database-utils' 8import { resetSequelizeInstance } from '../../helpers/database-utils'
@@ -312,20 +312,21 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
312 const videoChannelInstance = res.locals.videoChannel 312 const videoChannelInstance = res.locals.videoChannel
313 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 313 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
314 const countVideos = getCountVideos(req) 314 const countVideos = getCountVideos(req)
315 const query = req.query as VideosCommonQuery
315 316
316 const apiOptions = await Hooks.wrapObject({ 317 const apiOptions = await Hooks.wrapObject({
317 followerActorId, 318 followerActorId,
318 start: req.query.start, 319 start: query.start,
319 count: req.query.count, 320 count: query.count,
320 sort: req.query.sort, 321 sort: query.sort,
321 includeLocalVideos: true, 322 includeLocalVideos: true,
322 categoryOneOf: req.query.categoryOneOf, 323 categoryOneOf: query.categoryOneOf,
323 licenceOneOf: req.query.licenceOneOf, 324 licenceOneOf: query.licenceOneOf,
324 languageOneOf: req.query.languageOneOf, 325 languageOneOf: query.languageOneOf,
325 tagsOneOf: req.query.tagsOneOf, 326 tagsOneOf: query.tagsOneOf,
326 tagsAllOf: req.query.tagsAllOf, 327 tagsAllOf: query.tagsAllOf,
327 filter: req.query.filter, 328 filter: query.filter,
328 nsfw: buildNSFWFilter(res, req.query.nsfw), 329 nsfw: buildNSFWFilter(res, query.nsfw),
329 withFiles: false, 330 withFiles: false,
330 videoChannelId: videoChannelInstance.id, 331 videoChannelId: videoChannelInstance.id,
331 user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 332 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 7fee278f2..6ec6478e4 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -10,9 +10,8 @@ import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnail
10import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 10import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
11import { getServerActor } from '@server/models/application/application' 11import { getServerActor } from '@server/models/application/application'
12import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 12import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
13import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared' 13import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared'
14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 14import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
15import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
16import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 15import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
17import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' 16import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
18import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils' 17import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
@@ -494,20 +493,22 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response
494} 493}
495 494
496async function listVideos (req: express.Request, res: express.Response) { 495async function listVideos (req: express.Request, res: express.Response) {
496 const query = req.query as VideosCommonQuery
497 const countVideos = getCountVideos(req) 497 const countVideos = getCountVideos(req)
498 498
499 const apiOptions = await Hooks.wrapObject({ 499 const apiOptions = await Hooks.wrapObject({
500 start: req.query.start, 500 start: query.start,
501 count: req.query.count, 501 count: query.count,
502 sort: req.query.sort, 502 sort: query.sort,
503 includeLocalVideos: true, 503 includeLocalVideos: true,
504 categoryOneOf: req.query.categoryOneOf, 504 categoryOneOf: query.categoryOneOf,
505 licenceOneOf: req.query.licenceOneOf, 505 licenceOneOf: query.licenceOneOf,
506 languageOneOf: req.query.languageOneOf, 506 languageOneOf: query.languageOneOf,
507 tagsOneOf: req.query.tagsOneOf, 507 tagsOneOf: query.tagsOneOf,
508 tagsAllOf: req.query.tagsAllOf, 508 tagsAllOf: query.tagsAllOf,
509 nsfw: buildNSFWFilter(res, req.query.nsfw), 509 nsfw: buildNSFWFilter(res, query.nsfw),
510 filter: req.query.filter as VideoFilter, 510 isLive: query.isLive,
511 filter: query.filter,
511 withFiles: false, 512 withFiles: false,
512 user: res.locals.oauth ? res.locals.oauth.token.User : undefined, 513 user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
513 countVideos 514 countVideos
diff --git a/server/helpers/custom-validators/search.ts b/server/helpers/custom-validators/search.ts
index 429fcafcf..a8f258838 100644
--- a/server/helpers/custom-validators/search.ts
+++ b/server/helpers/custom-validators/search.ts
@@ -11,7 +11,7 @@ function isStringArray (value: any) {
11 return isArray(value) && value.every(v => typeof v === 'string') 11 return isArray(value) && value.every(v => typeof v === 'string')
12} 12}
13 13
14function isNSFWQueryValid (value: any) { 14function isBooleanBothQueryValid (value: any) {
15 return value === 'true' || value === 'false' || value === 'both' 15 return value === 'true' || value === 'false' || value === 'both'
16} 16}
17 17
@@ -32,6 +32,6 @@ function isSearchTargetValid (value: SearchTargetType) {
32export { 32export {
33 isNumberArray, 33 isNumberArray,
34 isStringArray, 34 isStringArray,
35 isNSFWQueryValid, 35 isBooleanBothQueryValid,
36 isSearchTargetValid 36 isSearchTargetValid
37} 37}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 37a963760..d390fd95e 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -188,10 +188,7 @@ const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } =
188 } 188 }
189} 189}
190const JOB_PRIORITY = { 190const JOB_PRIORITY = {
191 TRANSCODING: { 191 TRANSCODING: 100
192 OPTIMIZER: 10,
193 NEW_RESOLUTION: 100
194 }
195} 192}
196 193
197const BROADCAST_CONCURRENCY = 30 // How many requests in parallel we do in activitypub-http-broadcast job 194const BROADCAST_CONCURRENCY = 30 // How many requests in parallel we do in activitypub-http-broadcast job
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 4ee2b2df2..010b95b05 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -1,7 +1,6 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils' 2import { TranscodeOptionsType } from '@server/helpers/ffmpeg-utils'
3import { JOB_PRIORITY } from '@server/initializers/constants' 3import { getTranscodingJobPriority, publishAndFederateIfNeeded } from '@server/lib/video'
4import { getJobTranscodingPriorityMalus, publishAndFederateIfNeeded } from '@server/lib/video'
5import { getVideoFilePath } from '@server/lib/video-paths' 4import { getVideoFilePath } from '@server/lib/video-paths'
6import { UserModel } from '@server/models/account/user' 5import { UserModel } from '@server/models/account/user'
7import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' 6import { MUser, MUserId, MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models'
@@ -215,7 +214,7 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
215 if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return false 214 if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return false
216 215
217 const jobOptions = { 216 const jobOptions = {
218 priority: JOB_PRIORITY.TRANSCODING.NEW_RESOLUTION + await getJobTranscodingPriorityMalus(user) 217 priority: await getTranscodingJobPriority(user)
219 } 218 }
220 219
221 const hlsTranscodingPayload: HLSTranscodingPayload = { 220 const hlsTranscodingPayload: HLSTranscodingPayload = {
@@ -272,7 +271,7 @@ async function createLowerResolutionsJobs (
272 resolutionCreated.push(resolution) 271 resolutionCreated.push(resolution)
273 272
274 const jobOptions = { 273 const jobOptions = {
275 priority: JOB_PRIORITY.TRANSCODING.NEW_RESOLUTION + await getJobTranscodingPriorityMalus(user) 274 priority: await getTranscodingJobPriority(user)
276 } 275 }
277 276
278 JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }, jobOptions) 277 JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }, jobOptions)
diff --git a/server/lib/video.ts b/server/lib/video.ts
index e381e0a69..9469b8178 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -121,19 +121,19 @@ async function addOptimizeOrMergeAudioJob (video: MVideo, videoFile: MVideoFile,
121 } 121 }
122 122
123 const jobOptions = { 123 const jobOptions = {
124 priority: JOB_PRIORITY.TRANSCODING.OPTIMIZER + await getJobTranscodingPriorityMalus(user) 124 priority: await getTranscodingJobPriority(user)
125 } 125 }
126 126
127 return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput }, jobOptions) 127 return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput }, jobOptions)
128} 128}
129 129
130async function getJobTranscodingPriorityMalus (user: MUserId) { 130async function getTranscodingJobPriority (user: MUserId) {
131 const now = new Date() 131 const now = new Date()
132 const lastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7) 132 const lastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7)
133 133
134 const videoUploadedByUser = await VideoModel.countVideosUploadedByUserSince(user.id, lastWeek) 134 const videoUploadedByUser = await VideoModel.countVideosUploadedByUserSince(user.id, lastWeek)
135 135
136 return videoUploadedByUser 136 return JOB_PRIORITY.TRANSCODING + videoUploadedByUser
137} 137}
138 138
139// --------------------------------------------------------------------------- 139// ---------------------------------------------------------------------------
@@ -144,5 +144,5 @@ export {
144 buildVideoThumbnailsFromReq, 144 buildVideoThumbnailsFromReq,
145 setVideoTags, 145 setVideoTags,
146 addOptimizeOrMergeAudioJob, 146 addOptimizeOrMergeAudioJob,
147 getJobTranscodingPriorityMalus 147 getTranscodingJobPriority
148} 148}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 4d31d3dcb..bb617d77c 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -20,7 +20,7 @@ import {
20 toIntOrNull, 20 toIntOrNull,
21 toValueOrNull 21 toValueOrNull
22} from '../../../helpers/custom-validators/misc' 22} from '../../../helpers/custom-validators/misc'
23import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' 23import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
24import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' 24import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
25import { 25import {
26 isScheduleVideoUpdatePrivacyValid, 26 isScheduleVideoUpdatePrivacyValid,
@@ -439,7 +439,11 @@ const commonVideosFiltersValidator = [
439 .custom(isStringArray).withMessage('Should have a valid all of tags array'), 439 .custom(isStringArray).withMessage('Should have a valid all of tags array'),
440 query('nsfw') 440 query('nsfw')
441 .optional() 441 .optional()
442 .custom(isNSFWQueryValid).withMessage('Should have a valid NSFW attribute'), 442 .custom(isBooleanBothQueryValid).withMessage('Should have a valid NSFW attribute'),
443 query('isLive')
444 .optional()
445 .customSanitizer(toBooleanOrNull)
446 .custom(isBooleanValid).withMessage('Should have a valid live boolean'),
443 query('filter') 447 query('filter')
444 .optional() 448 .optional()
445 .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'), 449 .custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index 4d95ddee2..155afe64b 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -16,9 +16,11 @@ export type BuildVideosQueryOptions = {
16 start: number 16 start: number
17 sort: string 17 sort: string
18 18
19 nsfw?: boolean
19 filter?: VideoFilter 20 filter?: VideoFilter
21 isLive?: boolean
22
20 categoryOneOf?: number[] 23 categoryOneOf?: number[]
21 nsfw?: boolean
22 licenceOneOf?: number[] 24 licenceOneOf?: number[]
23 languageOneOf?: string[] 25 languageOneOf?: string[]
24 tagsOneOf?: string[] 26 tagsOneOf?: string[]
@@ -199,10 +201,14 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
199 201
200 if (options.nsfw === true) { 202 if (options.nsfw === true) {
201 and.push('"video"."nsfw" IS TRUE') 203 and.push('"video"."nsfw" IS TRUE')
204 } else if (options.nsfw === false) {
205 and.push('"video"."nsfw" IS FALSE')
202 } 206 }
203 207
204 if (options.nsfw === false) { 208 if (options.isLive === true) {
205 and.push('"video"."nsfw" IS FALSE') 209 and.push('"video"."isLive" IS TRUE')
210 } else if (options.isLive === false) {
211 and.push('"video"."isLive" IS FALSE')
206 } 212 }
207 213
208 if (options.categoryOneOf) { 214 if (options.categoryOneOf) {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 422bf6deb..e55a21a6b 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1021,14 +1021,28 @@ export class VideoModel extends Model {
1021 start: number 1021 start: number
1022 count: number 1022 count: number
1023 sort: string 1023 sort: string
1024 isLive?: boolean
1024 search?: string 1025 search?: string
1025 }) { 1026 }) {
1026 const { accountId, start, count, sort, search } = options 1027 const { accountId, start, count, sort, search, isLive } = options
1027 1028
1028 function buildBaseQuery (): FindOptions { 1029 function buildBaseQuery (): FindOptions {
1029 let baseQuery = { 1030 const where: WhereOptions = {}
1031
1032 if (search) {
1033 where.name = {
1034 [Op.iLike]: '%' + search + '%'
1035 }
1036 }
1037
1038 if (isLive) {
1039 where.isLive = isLive
1040 }
1041
1042 const baseQuery = {
1030 offset: start, 1043 offset: start,
1031 limit: count, 1044 limit: count,
1045 where,
1032 order: getVideoSort(sort), 1046 order: getVideoSort(sort),
1033 include: [ 1047 include: [
1034 { 1048 {
@@ -1047,16 +1061,6 @@ export class VideoModel extends Model {
1047 ] 1061 ]
1048 } 1062 }
1049 1063
1050 if (search) {
1051 baseQuery = Object.assign(baseQuery, {
1052 where: {
1053 name: {
1054 [Op.iLike]: '%' + search + '%'
1055 }
1056 }
1057 })
1058 }
1059
1060 return baseQuery 1064 return baseQuery
1061 } 1065 }
1062 1066
@@ -1084,23 +1088,34 @@ export class VideoModel extends Model {
1084 start: number 1088 start: number
1085 count: number 1089 count: number
1086 sort: string 1090 sort: string
1091
1087 nsfw: boolean 1092 nsfw: boolean
1093 filter?: VideoFilter
1094 isLive?: boolean
1095
1088 includeLocalVideos: boolean 1096 includeLocalVideos: boolean
1089 withFiles: boolean 1097 withFiles: boolean
1098
1090 categoryOneOf?: number[] 1099 categoryOneOf?: number[]
1091 licenceOneOf?: number[] 1100 licenceOneOf?: number[]
1092 languageOneOf?: string[] 1101 languageOneOf?: string[]
1093 tagsOneOf?: string[] 1102 tagsOneOf?: string[]
1094 tagsAllOf?: string[] 1103 tagsAllOf?: string[]
1095 filter?: VideoFilter 1104
1096 accountId?: number 1105 accountId?: number
1097 videoChannelId?: number 1106 videoChannelId?: number
1107
1098 followerActorId?: number 1108 followerActorId?: number
1109
1099 videoPlaylistId?: number 1110 videoPlaylistId?: number
1111
1100 trendingDays?: number 1112 trendingDays?: number
1113
1101 user?: MUserAccountId 1114 user?: MUserAccountId
1102 historyOfUser?: MUserId 1115 historyOfUser?: MUserId
1116
1103 countVideos?: boolean 1117 countVideos?: boolean
1118
1104 search?: string 1119 search?: string
1105 }) { 1120 }) {
1106 if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1121 if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
@@ -1128,6 +1143,7 @@ export class VideoModel extends Model {
1128 followerActorId, 1143 followerActorId,
1129 serverAccountId: serverActor.Account.id, 1144 serverAccountId: serverActor.Account.id,
1130 nsfw: options.nsfw, 1145 nsfw: options.nsfw,
1146 isLive: options.isLive,
1131 categoryOneOf: options.categoryOneOf, 1147 categoryOneOf: options.categoryOneOf,
1132 licenceOneOf: options.licenceOneOf, 1148 licenceOneOf: options.licenceOneOf,
1133 languageOneOf: options.languageOneOf, 1149 languageOneOf: options.languageOneOf,
@@ -1160,6 +1176,7 @@ export class VideoModel extends Model {
1160 originallyPublishedStartDate?: string 1176 originallyPublishedStartDate?: string
1161 originallyPublishedEndDate?: string 1177 originallyPublishedEndDate?: string
1162 nsfw?: boolean 1178 nsfw?: boolean
1179 isLive?: boolean
1163 categoryOneOf?: number[] 1180 categoryOneOf?: number[]
1164 licenceOneOf?: number[] 1181 licenceOneOf?: number[]
1165 languageOneOf?: string[] 1182 languageOneOf?: string[]
@@ -1171,23 +1188,32 @@ export class VideoModel extends Model {
1171 filter?: VideoFilter 1188 filter?: VideoFilter
1172 }) { 1189 }) {
1173 const serverActor = await getServerActor() 1190 const serverActor = await getServerActor()
1191
1174 const queryOptions = { 1192 const queryOptions = {
1175 followerActorId: serverActor.id, 1193 followerActorId: serverActor.id,
1176 serverAccountId: serverActor.Account.id, 1194 serverAccountId: serverActor.Account.id,
1195
1177 includeLocalVideos: options.includeLocalVideos, 1196 includeLocalVideos: options.includeLocalVideos,
1178 nsfw: options.nsfw, 1197 nsfw: options.nsfw,
1198 isLive: options.isLive,
1199
1179 categoryOneOf: options.categoryOneOf, 1200 categoryOneOf: options.categoryOneOf,
1180 licenceOneOf: options.licenceOneOf, 1201 licenceOneOf: options.licenceOneOf,
1181 languageOneOf: options.languageOneOf, 1202 languageOneOf: options.languageOneOf,
1203
1182 tagsOneOf: options.tagsOneOf, 1204 tagsOneOf: options.tagsOneOf,
1183 tagsAllOf: options.tagsAllOf, 1205 tagsAllOf: options.tagsAllOf,
1206
1184 user: options.user, 1207 user: options.user,
1185 filter: options.filter, 1208 filter: options.filter,
1209
1186 start: options.start, 1210 start: options.start,
1187 count: options.count, 1211 count: options.count,
1188 sort: options.sort, 1212 sort: options.sort,
1213
1189 startDate: options.startDate, 1214 startDate: options.startDate,
1190 endDate: options.endDate, 1215 endDate: options.endDate,
1216
1191 originallyPublishedStartDate: options.originallyPublishedStartDate, 1217 originallyPublishedStartDate: options.originallyPublishedStartDate,
1192 originallyPublishedEndDate: options.originallyPublishedEndDate, 1218 originallyPublishedEndDate: options.originallyPublishedEndDate,
1193 1219
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index d48e2a8ee..57fb58150 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -19,10 +19,12 @@ import {
19 doubleFollow, 19 doubleFollow,
20 flushAndRunMultipleServers, 20 flushAndRunMultipleServers,
21 getLive, 21 getLive,
22 getMyVideosWithFilter,
22 getPlaylist, 23 getPlaylist,
23 getVideo, 24 getVideo,
24 getVideoIdFromUUID, 25 getVideoIdFromUUID,
25 getVideosList, 26 getVideosList,
27 getVideosWithFilters,
26 killallServers, 28 killallServers,
27 makeRawRequest, 29 makeRawRequest,
28 removeVideo, 30 removeVideo,
@@ -37,6 +39,7 @@ import {
37 testImage, 39 testImage,
38 updateCustomSubConfig, 40 updateCustomSubConfig,
39 updateLive, 41 updateLive,
42 uploadVideoAndGetId,
40 viewVideo, 43 viewVideo,
41 wait, 44 wait,
42 waitJobs, 45 waitJobs,
@@ -229,6 +232,68 @@ describe('Test live', function () {
229 }) 232 })
230 }) 233 })
231 234
235 describe('Live filters', function () {
236 let command: any
237 let liveVideoId: string
238 let vodVideoId: string
239
240 before(async function () {
241 this.timeout(120000)
242
243 vodVideoId = (await uploadVideoAndGetId({ server: servers[0], videoName: 'vod video' })).uuid
244
245 const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].videoChannel.id }
246 const resLive = await createLive(servers[0].url, servers[0].accessToken, liveOptions)
247 liveVideoId = resLive.body.video.uuid
248
249 command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
250 await waitUntilLivePublishedOnAllServers(liveVideoId)
251 await waitJobs(servers)
252 })
253
254 it('Should only display lives', async function () {
255 const res = await getVideosWithFilters(servers[0].url, { isLive: true })
256
257 expect(res.body.total).to.equal(1)
258 expect(res.body.data).to.have.lengthOf(1)
259 expect(res.body.data[0].name).to.equal('live')
260 })
261
262 it('Should not display lives', async function () {
263 const res = await getVideosWithFilters(servers[0].url, { isLive: false })
264
265 expect(res.body.total).to.equal(1)
266 expect(res.body.data).to.have.lengthOf(1)
267 expect(res.body.data[0].name).to.equal('vod video')
268 })
269
270 it('Should display my lives', async function () {
271 this.timeout(60000)
272
273 await stopFfmpeg(command)
274 await waitJobs(servers)
275
276 const res = await getMyVideosWithFilter(servers[0].url, servers[0].accessToken, { isLive: true })
277 const videos = res.body.data as Video[]
278
279 const result = videos.every(v => v.isLive)
280 expect(result).to.be.true
281 })
282
283 it('Should not display my lives', async function () {
284 const res = await getMyVideosWithFilter(servers[0].url, servers[0].accessToken, { isLive: false })
285 const videos = res.body.data as Video[]
286
287 const result = videos.every(v => !v.isLive)
288 expect(result).to.be.true
289 })
290
291 after(async function () {
292 await removeVideo(servers[0].url, servers[0].accessToken, vodVideoId)
293 await removeVideo(servers[0].url, servers[0].accessToken, liveVideoId)
294 })
295 })
296
232 describe('Stream checks', function () { 297 describe('Stream checks', function () {
233 let liveVideo: LiveVideo & VideoDetails 298 let liveVideo: LiveVideo & VideoDetails
234 let rtmpUrl: string 299 let rtmpUrl: string
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index e05c3a269..5b8907961 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -1,17 +1,24 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
4import * as chai from 'chai'
5import { VideoPrivacy } from '@shared/models'
5import { 6import {
6 advancedVideosSearch, 7 advancedVideosSearch,
7 cleanupTests, 8 cleanupTests,
9 createLive,
8 flushAndRunServer, 10 flushAndRunServer,
9 immutableAssign, 11 immutableAssign,
10 searchVideo, 12 searchVideo,
13 sendRTMPStreamInVideo,
11 ServerInfo, 14 ServerInfo,
12 setAccessTokensToServers, 15 setAccessTokensToServers,
16 setDefaultVideoChannel,
17 stopFfmpeg,
18 updateCustomSubConfig,
13 uploadVideo, 19 uploadVideo,
14 wait 20 wait,
21 waitUntilLivePublished
15} from '../../../../shared/extra-utils' 22} from '../../../../shared/extra-utils'
16import { createVideoCaption } from '../../../../shared/extra-utils/videos/video-captions' 23import { createVideoCaption } from '../../../../shared/extra-utils/videos/video-captions'
17 24
@@ -28,6 +35,7 @@ describe('Test videos search', function () {
28 server = await flushAndRunServer(1) 35 server = await flushAndRunServer(1)
29 36
30 await setAccessTokensToServers([ server ]) 37 await setAccessTokensToServers([ server ])
38 await setDefaultVideoChannel([ server ])
31 39
32 { 40 {
33 const attributes1 = { 41 const attributes1 = {
@@ -449,6 +457,43 @@ describe('Test videos search', function () {
449 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3') 457 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
450 }) 458 })
451 459
460 it('Should search by live', async function () {
461 this.timeout(30000)
462
463 {
464 const options = {
465 search: {
466 searchIndex: { enabled: false }
467 },
468 live: { enabled: true }
469 }
470 await updateCustomSubConfig(server.url, server.accessToken, options)
471 }
472
473 {
474 const res = await advancedVideosSearch(server.url, { isLive: true })
475
476 expect(res.body.total).to.equal(0)
477 expect(res.body.data).to.have.lengthOf(0)
478 }
479
480 {
481 const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: server.videoChannel.id }
482 const resLive = await createLive(server.url, server.accessToken, liveOptions)
483 const liveVideoId = resLive.body.video.uuid
484
485 const command = await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId)
486 await waitUntilLivePublished(server.url, server.accessToken, liveVideoId)
487
488 const res = await advancedVideosSearch(server.url, { isLive: true })
489
490 expect(res.body.total).to.equal(1)
491 expect(res.body.data[0].name).to.equal('live')
492
493 await stopFfmpeg(command)
494 }
495 })
496
452 after(async function () { 497 after(async function () {
453 await cleanupTests([ server ]) 498 await cleanupTests([ server ])
454 }) 499 })
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index da90223b8..a79648bf7 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -387,11 +387,11 @@ describe('Test a single server', function () {
387 }) 387 })
388 388
389 it('Should filter by tags and category', async function () { 389 it('Should filter by tags and category', async function () {
390 const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 4 }) 390 const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] })
391 expect(res1.body.total).to.equal(1) 391 expect(res1.body.total).to.equal(1)
392 expect(res1.body.data[0].name).to.equal('my super video updated') 392 expect(res1.body.data[0].name).to.equal('my super video updated')
393 393
394 const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 3 }) 394 const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] })
395 expect(res2.body.total).to.equal(0) 395 expect(res2.body.total).to.equal(0)
396 }) 396 })
397 397
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 1058baaa3..1c99f26df 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -721,12 +721,7 @@ describe('Test video transcoding', function () {
721 expect(webtorrentJobs).to.have.lengthOf(6) 721 expect(webtorrentJobs).to.have.lengthOf(6)
722 expect(optimizeJobs).to.have.lengthOf(1) 722 expect(optimizeJobs).to.have.lengthOf(1)
723 723
724 for (const j of optimizeJobs) { 724 for (const j of optimizeJobs.concat(hlsJobs.concat(webtorrentJobs))) {
725 expect(j.priority).to.be.greaterThan(11)
726 expect(j.priority).to.be.lessThan(50)
727 }
728
729 for (const j of hlsJobs.concat(webtorrentJobs)) {
730 expect(j.priority).to.be.greaterThan(100) 725 expect(j.priority).to.be.greaterThan(100)
731 expect(j.priority).to.be.lessThan(150) 726 expect(j.priority).to.be.lessThan(150)
732 } 727 }