aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-11-03 11:32:41 +0100
committerChocobozzz <me@florianbigard.com>2021-11-03 11:32:41 +0100
commitd324756edb836672f12284cd18e642a658b273d8 (patch)
tree3b323682bd7380491ad904daaeaea10be606e0f9
parentd5d9c5b79edf613e97a752a3d59062fb42045275 (diff)
downloadPeerTube-d324756edb836672f12284cd18e642a658b273d8.tar.gz
PeerTube-d324756edb836672f12284cd18e642a658b273d8.tar.zst
PeerTube-d324756edb836672f12284cd18e642a658b273d8.zip
Add ability to filter by file type
-rw-r--r--client/src/app/+admin/overview/videos/video-admin.service.ts36
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html6
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts12
-rw-r--r--server/helpers/query.ts2
-rw-r--r--server/middlewares/validators/videos/videos.ts21
-rw-r--r--server/models/video/sql/videos-id-list-query-builder.ts45
-rw-r--r--server/models/video/video.ts44
-rw-r--r--server/tests/api/videos/videos-common-filters.ts80
-rw-r--r--shared/models/search/videos-common-query.model.ts3
-rw-r--r--support/doc/api/openapi.yaml28
10 files changed, 237 insertions, 40 deletions
diff --git a/client/src/app/+admin/overview/videos/video-admin.service.ts b/client/src/app/+admin/overview/videos/video-admin.service.ts
index d0854a2dc..b90fe22d8 100644
--- a/client/src/app/+admin/overview/videos/video-admin.service.ts
+++ b/client/src/app/+admin/overview/videos/video-admin.service.ts
@@ -45,11 +45,33 @@ export class VideoAdminService {
45 children: [ 45 children: [
46 { 46 {
47 queryParams: { search: 'isLive:false' }, 47 queryParams: { search: 'isLive:false' },
48 label: $localize`VOD videos` 48 label: $localize`VOD`
49 }, 49 },
50 { 50 {
51 queryParams: { search: 'isLive:true' }, 51 queryParams: { search: 'isLive:true' },
52 label: $localize`Live videos` 52 label: $localize`Live`
53 }
54 ]
55 },
56
57 {
58 title: $localize`Video files`,
59 children: [
60 {
61 queryParams: { search: 'webtorrent:true' },
62 label: $localize`With WebTorrent`
63 },
64 {
65 queryParams: { search: 'webtorrent:false' },
66 label: $localize`Without WebTorrent`
67 },
68 {
69 queryParams: { search: 'hls:true' },
70 label: $localize`With HLS`
71 },
72 {
73 queryParams: { search: 'hls:false' },
74 label: $localize`Without HLS`
53 } 75 }
54 ] 76 ]
55 }, 77 },
@@ -69,7 +91,7 @@ export class VideoAdminService {
69 }, 91 },
70 92
71 { 93 {
72 title: $localize`Include/Exclude`, 94 title: $localize`Exclude`,
73 children: [ 95 children: [
74 { 96 {
75 queryParams: { search: 'excludeMuted' }, 97 queryParams: { search: 'excludeMuted' },
@@ -94,6 +116,14 @@ export class VideoAdminService {
94 prefix: 'isLocal:', 116 prefix: 'isLocal:',
95 isBoolean: true 117 isBoolean: true
96 }, 118 },
119 hasHLSFiles: {
120 prefix: 'hls:',
121 isBoolean: true
122 },
123 hasWebtorrentFiles: {
124 prefix: 'webtorrent:',
125 isBoolean: true
126 },
97 isLive: { 127 isLive: {
98 prefix: 'isLive:', 128 prefix: 'isLive:',
99 isBoolean: true 129 isBoolean: true
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
index 67b554aaf..134f64632 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.html
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -66,11 +66,11 @@
66 </td> 66 </td>
67 67
68 <td> 68 <td>
69 <span [ngClass]="getPrivacyBadgeClass(video.privacy.id)" class="badge">{{ video.privacy.label }}</span> 69 <span [ngClass]="getPrivacyBadgeClass(video)" class="badge">{{ video.privacy.label }}</span>
70 70
71 <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span> 71 <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
72 72
73 <span *ngIf="isUnpublished(video.state.id)" class="badge badge-yellow" i18n>{{ video.state.label }}</span> 73 <span *ngIf="isUnpublished(video)" class="badge badge-yellow" i18n>{{ video.state.label }}</span>
74 74
75 <span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span> 75 <span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span>
76 <span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span> 76 <span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span>
@@ -83,7 +83,7 @@
83 <span *ngIf="isWebTorrent(video)" class="badge badge-blue">WebTorrent</span> 83 <span *ngIf="isWebTorrent(video)" class="badge badge-blue">WebTorrent</span>
84 <span *ngIf="video.isLive" class="badge badge-blue">Live</span> 84 <span *ngIf="video.isLive" class="badge badge-blue">Live</span>
85 85
86 <span *ngIf="!video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span> 86 <span *ngIf="!isImport(video) && !video.isLive && video.isLocal">{{ getFilesSize(video) | bytes: 1 }}</span>
87 </td> 87 </td>
88 88
89 <td> 89 <td>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
index 8a15e8426..635552cf5 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.ts
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -85,14 +85,14 @@ export class VideoListComponent extends RestTable implements OnInit {
85 this.reloadData() 85 this.reloadData()
86 } 86 }
87 87
88 getPrivacyBadgeClass (privacy: VideoPrivacy) { 88 getPrivacyBadgeClass (video: Video) {
89 if (privacy === VideoPrivacy.PUBLIC) return 'badge-blue' 89 if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-blue'
90 90
91 return 'badge-yellow' 91 return 'badge-yellow'
92 } 92 }
93 93
94 isUnpublished (state: VideoState) { 94 isUnpublished (video: Video) {
95 return state !== VideoState.LIVE_ENDED && state !== VideoState.PUBLISHED 95 return video.state.id !== VideoState.LIVE_ENDED && video.state.id !== VideoState.PUBLISHED
96 } 96 }
97 97
98 isAccountBlocked (video: Video) { 98 isAccountBlocked (video: Video) {
@@ -107,6 +107,10 @@ export class VideoListComponent extends RestTable implements OnInit {
107 return video.blacklisted 107 return video.blacklisted
108 } 108 }
109 109
110 isImport (video: Video) {
111 return video.state.id === VideoState.TO_IMPORT
112 }
113
110 isHLS (video: Video) { 114 isHLS (video: Video) {
111 const p = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) 115 const p = video.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
112 if (!p) return false 116 if (!p) return false
diff --git a/server/helpers/query.ts b/server/helpers/query.ts
index 79cf076d1..97bbdfc65 100644
--- a/server/helpers/query.ts
+++ b/server/helpers/query.ts
@@ -21,6 +21,8 @@ function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) {
21 'isLocal', 21 'isLocal',
22 'include', 22 'include',
23 'skipCount', 23 'skipCount',
24 'hasHLSFiles',
25 'hasWebtorrentFiles',
24 'search' 26 'search'
25 ]) 27 ])
26} 28}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 44233b653..5f1234379 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -496,6 +496,14 @@ const commonVideosFiltersValidator = [
496 .optional() 496 .optional()
497 .customSanitizer(toBooleanOrNull) 497 .customSanitizer(toBooleanOrNull)
498 .custom(isBooleanValid).withMessage('Should have a valid local boolean'), 498 .custom(isBooleanValid).withMessage('Should have a valid local boolean'),
499 query('hasHLSFiles')
500 .optional()
501 .customSanitizer(toBooleanOrNull)
502 .custom(isBooleanValid).withMessage('Should have a valid has hls boolean'),
503 query('hasWebtorrentFiles')
504 .optional()
505 .customSanitizer(toBooleanOrNull)
506 .custom(isBooleanValid).withMessage('Should have a valid has webtorrent boolean'),
499 query('skipCount') 507 query('skipCount')
500 .optional() 508 .optional()
501 .customSanitizer(toBooleanOrNull) 509 .customSanitizer(toBooleanOrNull)
@@ -525,12 +533,13 @@ const commonVideosFiltersValidator = [
525 533
526 const user = res.locals.oauth?.token.User 534 const user = res.locals.oauth?.token.User
527 535
528 if (req.query.include && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) { 536 if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) {
529 res.fail({ 537 if (req.query.include) {
530 status: HttpStatusCode.UNAUTHORIZED_401, 538 return res.fail({
531 message: 'You are not allowed to see all local videos.' 539 status: HttpStatusCode.UNAUTHORIZED_401,
532 }) 540 message: 'You are not allowed to see all videos.'
533 return 541 })
542 }
534 } 543 }
535 544
536 return next() 545 return next()
diff --git a/server/models/video/sql/videos-id-list-query-builder.ts b/server/models/video/sql/videos-id-list-query-builder.ts
index 5064afafe..4a882e790 100644
--- a/server/models/video/sql/videos-id-list-query-builder.ts
+++ b/server/models/video/sql/videos-id-list-query-builder.ts
@@ -44,6 +44,8 @@ export type BuildVideosListQueryOptions = {
44 uuids?: string[] 44 uuids?: string[]
45 45
46 hasFiles?: boolean 46 hasFiles?: boolean
47 hasHLSFiles?: boolean
48 hasWebtorrentFiles?: boolean
47 49
48 accountId?: number 50 accountId?: number
49 videoChannelId?: number 51 videoChannelId?: number
@@ -169,6 +171,14 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
169 this.whereFileExists() 171 this.whereFileExists()
170 } 172 }
171 173
174 if (exists(options.hasWebtorrentFiles)) {
175 this.whereWebTorrentFileExists(options.hasWebtorrentFiles)
176 }
177
178 if (exists(options.hasHLSFiles)) {
179 this.whereHLSFileExists(options.hasHLSFiles)
180 }
181
172 if (options.tagsOneOf) { 182 if (options.tagsOneOf) {
173 this.whereTagsOneOf(options.tagsOneOf) 183 this.whereTagsOneOf(options.tagsOneOf)
174 } 184 }
@@ -371,16 +381,31 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
371 } 381 }
372 382
373 private whereFileExists () { 383 private whereFileExists () {
374 this.and.push( 384 this.and.push(`(${this.buildWebTorrentFileExistsQuery(true)} OR ${this.buildHLSFileExistsQuery(true)})`)
375 '(' + 385 }
376 ' EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id") ' + 386
377 ' OR EXISTS (' + 387 private whereWebTorrentFileExists (exists: boolean) {
378 ' SELECT 1 FROM "videoStreamingPlaylist" ' + 388 this.and.push(this.buildWebTorrentFileExistsQuery(exists))
379 ' INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ' + 389 }
380 ' WHERE "videoStreamingPlaylist"."videoId" = "video"."id"' + 390
381 ' )' + 391 private whereHLSFileExists (exists: boolean) {
382 ')' 392 this.and.push(this.buildHLSFileExistsQuery(exists))
383 ) 393 }
394
395 private buildWebTorrentFileExistsQuery (exists: boolean) {
396 const prefix = exists ? '' : 'NOT '
397
398 return prefix + 'EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id")'
399 }
400
401 private buildHLSFileExistsQuery (exists: boolean) {
402 const prefix = exists ? '' : 'NOT '
403
404 return prefix + 'EXISTS (' +
405 ' SELECT 1 FROM "videoStreamingPlaylist" ' +
406 ' INNER JOIN "videoFile" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist"."id" ' +
407 ' WHERE "videoStreamingPlaylist"."videoId" = "video"."id"' +
408 ')'
384 } 409 }
385 410
386 private whereTagsOneOf (tagsOneOf: string[]) { 411 private whereTagsOneOf (tagsOneOf: string[]) {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index f9618c102..aef4fd20a 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1030,6 +1030,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1030 include?: VideoInclude 1030 include?: VideoInclude
1031 1031
1032 hasFiles?: boolean // default false 1032 hasFiles?: boolean // default false
1033 hasWebtorrentFiles?: boolean
1034 hasHLSFiles?: boolean
1033 1035
1034 categoryOneOf?: number[] 1036 categoryOneOf?: number[]
1035 licenceOneOf?: number[] 1037 licenceOneOf?: number[]
@@ -1053,9 +1055,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1053 1055
1054 search?: string 1056 search?: string
1055 }) { 1057 }) {
1056 if (VideoModel.isPrivateInclude(options.include) && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1058 VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
1057 throw new Error('Try to filter all-local but no user has not the see all videos right')
1058 }
1059 1059
1060 const trendingDays = options.sort.endsWith('trending') 1060 const trendingDays = options.sort.endsWith('trending')
1061 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 1061 ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
@@ -1088,6 +1088,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1088 'videoPlaylistId', 1088 'videoPlaylistId',
1089 'user', 1089 'user',
1090 'historyOfUser', 1090 'historyOfUser',
1091 'hasHLSFiles',
1092 'hasWebtorrentFiles',
1091 'search' 1093 'search'
1092 ]), 1094 ]),
1093 1095
@@ -1103,27 +1105,39 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1103 start: number 1105 start: number
1104 count: number 1106 count: number
1105 sort: string 1107 sort: string
1106 search?: string 1108
1107 host?: string
1108 startDate?: string // ISO 8601
1109 endDate?: string // ISO 8601
1110 originallyPublishedStartDate?: string
1111 originallyPublishedEndDate?: string
1112 nsfw?: boolean 1109 nsfw?: boolean
1113 isLive?: boolean 1110 isLive?: boolean
1114 isLocal?: boolean 1111 isLocal?: boolean
1115 include?: VideoInclude 1112 include?: VideoInclude
1113
1116 categoryOneOf?: number[] 1114 categoryOneOf?: number[]
1117 licenceOneOf?: number[] 1115 licenceOneOf?: number[]
1118 languageOneOf?: string[] 1116 languageOneOf?: string[]
1119 tagsOneOf?: string[] 1117 tagsOneOf?: string[]
1120 tagsAllOf?: string[] 1118 tagsAllOf?: string[]
1119
1120 displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
1121
1122 user?: MUserAccountId
1123
1124 hasWebtorrentFiles?: boolean
1125 hasHLSFiles?: boolean
1126
1127 search?: string
1128
1129 host?: string
1130 startDate?: string // ISO 8601
1131 endDate?: string // ISO 8601
1132 originallyPublishedStartDate?: string
1133 originallyPublishedEndDate?: string
1134
1121 durationMin?: number // seconds 1135 durationMin?: number // seconds
1122 durationMax?: number // seconds 1136 durationMax?: number // seconds
1123 user?: MUserAccountId
1124 uuids?: string[] 1137 uuids?: string[]
1125 displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
1126 }) { 1138 }) {
1139 VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
1140
1127 const serverActor = await getServerActor() 1141 const serverActor = await getServerActor()
1128 1142
1129 const queryOptions = { 1143 const queryOptions = {
@@ -1148,6 +1162,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1148 'originallyPublishedEndDate', 1162 'originallyPublishedEndDate',
1149 'durationMin', 1163 'durationMin',
1150 'durationMax', 1164 'durationMax',
1165 'hasHLSFiles',
1166 'hasWebtorrentFiles',
1151 'uuids', 1167 'uuids',
1152 'search', 1168 'search',
1153 'displayOnlyForFollower' 1169 'displayOnlyForFollower'
@@ -1489,6 +1505,12 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1489 } 1505 }
1490 } 1506 }
1491 1507
1508 private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) {
1509 if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) {
1510 throw new Error('Try to filter all-local but no user has not the see all videos right')
1511 }
1512 }
1513
1492 private static isPrivateInclude (include: VideoInclude) { 1514 private static isPrivateInclude (include: VideoInclude) {
1493 return include & VideoInclude.BLACKLISTED || 1515 return include & VideoInclude.BLACKLISTED ||
1494 include & VideoInclude.BLOCKED_OWNER || 1516 include & VideoInclude.BLOCKED_OWNER ||
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
index 03c5c3b3f..4f22d4ac3 100644
--- a/server/tests/api/videos/videos-common-filters.ts
+++ b/server/tests/api/videos/videos-common-filters.ts
@@ -135,6 +135,8 @@ describe('Test videos filter', function () {
135 server: PeerTubeServer 135 server: PeerTubeServer
136 path: string 136 path: string
137 isLocal?: boolean 137 isLocal?: boolean
138 hasWebtorrentFiles?: boolean
139 hasHLSFiles?: boolean
138 include?: VideoInclude 140 include?: VideoInclude
139 category?: number 141 category?: number
140 tagsAllOf?: string[] 142 tagsAllOf?: string[]
@@ -146,7 +148,7 @@ describe('Test videos filter', function () {
146 path: options.path, 148 path: options.path,
147 token: options.token ?? options.server.accessToken, 149 token: options.token ?? options.server.accessToken,
148 query: { 150 query: {
149 ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf' ]), 151 ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf', 'hasWebtorrentFiles', 'hasHLSFiles' ]),
150 152
151 sort: 'createdAt' 153 sort: 'createdAt'
152 }, 154 },
@@ -397,11 +399,9 @@ describe('Test videos filter', function () {
397 399
398 for (const path of paths) { 400 for (const path of paths) {
399 { 401 {
400
401 const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag2' ] }) 402 const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag2' ] })
402 expect(videos).to.have.lengthOf(1) 403 expect(videos).to.have.lengthOf(1)
403 expect(videos[0].name).to.equal('tag filter') 404 expect(videos[0].name).to.equal('tag filter')
404
405 } 405 }
406 406
407 { 407 {
@@ -421,6 +421,80 @@ describe('Test videos filter', function () {
421 } 421 }
422 } 422 }
423 }) 423 })
424
425 it('Should filter by HLS or WebTorrent files', async function () {
426 this.timeout(360000)
427
428 const finderFactory = (name: string) => (videos: Video[]) => videos.some(v => v.name === name)
429
430 await servers[0].config.enableTranscoding(true, false)
431 await servers[0].videos.upload({ attributes: { name: 'webtorrent video' } })
432 const hasWebtorrent = finderFactory('webtorrent video')
433
434 await waitJobs(servers)
435
436 await servers[0].config.enableTranscoding(false, true)
437 await servers[0].videos.upload({ attributes: { name: 'hls video' } })
438 const hasHLS = finderFactory('hls video')
439
440 await waitJobs(servers)
441
442 await servers[0].config.enableTranscoding(true, true)
443 await servers[0].videos.upload({ attributes: { name: 'hls and webtorrent video' } })
444 const hasBoth = finderFactory('hls and webtorrent video')
445
446 await waitJobs(servers)
447
448 for (const path of paths) {
449 {
450 const videos = await listVideos({ server: servers[0], path, hasWebtorrentFiles: true })
451
452 expect(hasWebtorrent(videos)).to.be.true
453 expect(hasHLS(videos)).to.be.false
454 expect(hasBoth(videos)).to.be.true
455 }
456
457 {
458 const videos = await listVideos({ server: servers[0], path, hasWebtorrentFiles: false })
459
460 expect(hasWebtorrent(videos)).to.be.false
461 expect(hasHLS(videos)).to.be.true
462 expect(hasBoth(videos)).to.be.false
463 }
464
465 {
466 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true })
467
468 expect(hasWebtorrent(videos)).to.be.false
469 expect(hasHLS(videos)).to.be.true
470 expect(hasBoth(videos)).to.be.true
471 }
472
473 {
474 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false })
475
476 expect(hasWebtorrent(videos)).to.be.true
477 expect(hasHLS(videos)).to.be.false
478 expect(hasBoth(videos)).to.be.false
479 }
480
481 {
482 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: false, hasWebtorrentFiles: false })
483
484 expect(hasWebtorrent(videos)).to.be.false
485 expect(hasHLS(videos)).to.be.false
486 expect(hasBoth(videos)).to.be.false
487 }
488
489 {
490 const videos = await listVideos({ server: servers[0], path, hasHLSFiles: true, hasWebtorrentFiles: true })
491
492 expect(hasWebtorrent(videos)).to.be.false
493 expect(hasHLS(videos)).to.be.false
494 expect(hasBoth(videos)).to.be.true
495 }
496 }
497 })
424 }) 498 })
425 499
426 after(async function () { 500 after(async function () {
diff --git a/shared/models/search/videos-common-query.model.ts b/shared/models/search/videos-common-query.model.ts
index 55a98e302..e9edb91b0 100644
--- a/shared/models/search/videos-common-query.model.ts
+++ b/shared/models/search/videos-common-query.model.ts
@@ -26,6 +26,9 @@ export interface VideosCommonQuery {
26 tagsOneOf?: string[] 26 tagsOneOf?: string[]
27 tagsAllOf?: string[] 27 tagsAllOf?: string[]
28 28
29 hasHLSFiles?: boolean
30 hasWebtorrentFiles?: boolean
31
29 skipCount?: boolean 32 skipCount?: boolean
30 33
31 search?: string 34 search?: string
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index e9e7e1757..ec246bca0 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -369,6 +369,8 @@ paths:
369 - $ref: '#/components/parameters/nsfw' 369 - $ref: '#/components/parameters/nsfw'
370 - $ref: '#/components/parameters/isLocal' 370 - $ref: '#/components/parameters/isLocal'
371 - $ref: '#/components/parameters/include' 371 - $ref: '#/components/parameters/include'
372 - $ref: '#/components/parameters/hasHLSFiles'
373 - $ref: '#/components/parameters/hasWebtorrentFiles'
372 - $ref: '#/components/parameters/skipCount' 374 - $ref: '#/components/parameters/skipCount'
373 - $ref: '#/components/parameters/start' 375 - $ref: '#/components/parameters/start'
374 - $ref: '#/components/parameters/count' 376 - $ref: '#/components/parameters/count'
@@ -1303,6 +1305,8 @@ paths:
1303 - $ref: '#/components/parameters/nsfw' 1305 - $ref: '#/components/parameters/nsfw'
1304 - $ref: '#/components/parameters/isLocal' 1306 - $ref: '#/components/parameters/isLocal'
1305 - $ref: '#/components/parameters/include' 1307 - $ref: '#/components/parameters/include'
1308 - $ref: '#/components/parameters/hasHLSFiles'
1309 - $ref: '#/components/parameters/hasWebtorrentFiles'
1306 - $ref: '#/components/parameters/skipCount' 1310 - $ref: '#/components/parameters/skipCount'
1307 - $ref: '#/components/parameters/start' 1311 - $ref: '#/components/parameters/start'
1308 - $ref: '#/components/parameters/count' 1312 - $ref: '#/components/parameters/count'
@@ -1624,6 +1628,8 @@ paths:
1624 - $ref: '#/components/parameters/nsfw' 1628 - $ref: '#/components/parameters/nsfw'
1625 - $ref: '#/components/parameters/isLocal' 1629 - $ref: '#/components/parameters/isLocal'
1626 - $ref: '#/components/parameters/include' 1630 - $ref: '#/components/parameters/include'
1631 - $ref: '#/components/parameters/hasHLSFiles'
1632 - $ref: '#/components/parameters/hasWebtorrentFiles'
1627 - $ref: '#/components/parameters/skipCount' 1633 - $ref: '#/components/parameters/skipCount'
1628 - $ref: '#/components/parameters/start' 1634 - $ref: '#/components/parameters/start'
1629 - $ref: '#/components/parameters/count' 1635 - $ref: '#/components/parameters/count'
@@ -2861,6 +2867,8 @@ paths:
2861 - $ref: '#/components/parameters/nsfw' 2867 - $ref: '#/components/parameters/nsfw'
2862 - $ref: '#/components/parameters/isLocal' 2868 - $ref: '#/components/parameters/isLocal'
2863 - $ref: '#/components/parameters/include' 2869 - $ref: '#/components/parameters/include'
2870 - $ref: '#/components/parameters/hasHLSFiles'
2871 - $ref: '#/components/parameters/hasWebtorrentFiles'
2864 - $ref: '#/components/parameters/skipCount' 2872 - $ref: '#/components/parameters/skipCount'
2865 - $ref: '#/components/parameters/start' 2873 - $ref: '#/components/parameters/start'
2866 - $ref: '#/components/parameters/count' 2874 - $ref: '#/components/parameters/count'
@@ -3582,6 +3590,8 @@ paths:
3582 - $ref: '#/components/parameters/nsfw' 3590 - $ref: '#/components/parameters/nsfw'
3583 - $ref: '#/components/parameters/isLocal' 3591 - $ref: '#/components/parameters/isLocal'
3584 - $ref: '#/components/parameters/include' 3592 - $ref: '#/components/parameters/include'
3593 - $ref: '#/components/parameters/hasHLSFiles'
3594 - $ref: '#/components/parameters/hasWebtorrentFiles'
3585 - $ref: '#/components/parameters/skipCount' 3595 - $ref: '#/components/parameters/skipCount'
3586 - $ref: '#/components/parameters/start' 3596 - $ref: '#/components/parameters/start'
3587 - $ref: '#/components/parameters/count' 3597 - $ref: '#/components/parameters/count'
@@ -4085,6 +4095,8 @@ paths:
4085 - $ref: '#/components/parameters/nsfw' 4095 - $ref: '#/components/parameters/nsfw'
4086 - $ref: '#/components/parameters/isLocal' 4096 - $ref: '#/components/parameters/isLocal'
4087 - $ref: '#/components/parameters/include' 4097 - $ref: '#/components/parameters/include'
4098 - $ref: '#/components/parameters/hasHLSFiles'
4099 - $ref: '#/components/parameters/hasWebtorrentFiles'
4088 responses: 4100 responses:
4089 '204': 4101 '204':
4090 description: successful operation 4102 description: successful operation
@@ -4167,6 +4179,8 @@ paths:
4167 - $ref: '#/components/parameters/nsfw' 4179 - $ref: '#/components/parameters/nsfw'
4168 - $ref: '#/components/parameters/isLocal' 4180 - $ref: '#/components/parameters/isLocal'
4169 - $ref: '#/components/parameters/include' 4181 - $ref: '#/components/parameters/include'
4182 - $ref: '#/components/parameters/hasHLSFiles'
4183 - $ref: '#/components/parameters/hasWebtorrentFiles'
4170 responses: 4184 responses:
4171 '204': 4185 '204':
4172 description: successful operation 4186 description: successful operation
@@ -4806,6 +4820,20 @@ components:
4806 schema: 4820 schema:
4807 type: boolean 4821 type: boolean
4808 description: '**PeerTube >= 4.0** Display only local or remote videos' 4822 description: '**PeerTube >= 4.0** Display only local or remote videos'
4823 hasHLSFiles:
4824 name: hasHLSFiles
4825 in: query
4826 required: false
4827 schema:
4828 type: boolean
4829 description: '**PeerTube >= 4.0** Display only videos that have HLS files'
4830 hasWebtorrentFiles:
4831 name: hasWebtorrentFiles
4832 in: query
4833 required: false
4834 schema:
4835 type: boolean
4836 description: '**PeerTube >= 4.0** Display only videos that have WebTorrent files'
4809 include: 4837 include:
4810 name: include 4838 name: include
4811 in: query 4839 in: query