diff options
-rw-r--r-- | client/src/app/+admin/overview/videos/video-admin.service.ts | 36 | ||||
-rw-r--r-- | client/src/app/+admin/overview/videos/video-list.component.html | 6 | ||||
-rw-r--r-- | client/src/app/+admin/overview/videos/video-list.component.ts | 12 | ||||
-rw-r--r-- | server/helpers/query.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/videos.ts | 21 | ||||
-rw-r--r-- | server/models/video/sql/videos-id-list-query-builder.ts | 45 | ||||
-rw-r--r-- | server/models/video/video.ts | 44 | ||||
-rw-r--r-- | server/tests/api/videos/videos-common-filters.ts | 80 | ||||
-rw-r--r-- | shared/models/search/videos-common-query.model.ts | 3 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 28 |
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 |