aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/server-commands/videos
diff options
context:
space:
mode:
Diffstat (limited to 'shared/server-commands/videos')
-rw-r--r--shared/server-commands/videos/index.ts1
-rw-r--r--shared/server-commands/videos/live-command.ts125
-rw-r--r--shared/server-commands/videos/live.ts7
-rw-r--r--shared/server-commands/videos/streaming-playlists-command.ts38
-rw-r--r--shared/server-commands/videos/video-token-command.ts31
-rw-r--r--shared/server-commands/videos/videos-command.ts25
6 files changed, 199 insertions, 28 deletions
diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts
index b4d6fa37b..c17f6ef20 100644
--- a/shared/server-commands/videos/index.ts
+++ b/shared/server-commands/videos/index.ts
@@ -14,5 +14,6 @@ export * from './services-command'
14export * from './streaming-playlists-command' 14export * from './streaming-playlists-command'
15export * from './comments-command' 15export * from './comments-command'
16export * from './video-studio-command' 16export * from './video-studio-command'
17export * from './video-token-command'
17export * from './views-command' 18export * from './views-command'
18export * from './videos-command' 19export * from './videos-command'
diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts
index d804fd883..cc9502c6f 100644
--- a/shared/server-commands/videos/live-command.ts
+++ b/shared/server-commands/videos/live-command.ts
@@ -12,9 +12,11 @@ import {
12 ResultList, 12 ResultList,
13 VideoCreateResult, 13 VideoCreateResult,
14 VideoDetails, 14 VideoDetails,
15 VideoPrivacy,
15 VideoState 16 VideoState
16} from '@shared/models' 17} from '@shared/models'
17import { unwrapBody } from '../requests' 18import { unwrapBody } from '../requests'
19import { ObjectStorageCommand, PeerTubeServer } from '../server'
18import { AbstractCommand, OverrideCommandOptions } from '../shared' 20import { AbstractCommand, OverrideCommandOptions } from '../shared'
19import { sendRTMPStream, testFfmpegStreamError } from './live' 21import { sendRTMPStream, testFfmpegStreamError } from './live'
20 22
@@ -34,6 +36,8 @@ export class LiveCommand extends AbstractCommand {
34 }) 36 })
35 } 37 }
36 38
39 // ---------------------------------------------------------------------------
40
37 listSessions (options: OverrideCommandOptions & { 41 listSessions (options: OverrideCommandOptions & {
38 videoId: number | string 42 videoId: number | string
39 }) { 43 }) {
@@ -70,6 +74,8 @@ export class LiveCommand extends AbstractCommand {
70 }) 74 })
71 } 75 }
72 76
77 // ---------------------------------------------------------------------------
78
73 update (options: OverrideCommandOptions & { 79 update (options: OverrideCommandOptions & {
74 videoId: number | string 80 videoId: number | string
75 fields: LiveVideoUpdate 81 fields: LiveVideoUpdate
@@ -110,6 +116,33 @@ export class LiveCommand extends AbstractCommand {
110 return body.video 116 return body.video
111 } 117 }
112 118
119 async quickCreate (options: OverrideCommandOptions & {
120 saveReplay: boolean
121 permanentLive: boolean
122 privacy?: VideoPrivacy
123 }) {
124 const { saveReplay, permanentLive, privacy } = options
125
126 const { uuid } = await this.create({
127 ...options,
128
129 fields: {
130 name: 'live',
131 permanentLive,
132 saveReplay,
133 channelId: this.server.store.channel.id,
134 privacy
135 }
136 })
137
138 const video = await this.server.videos.getWithToken({ id: uuid })
139 const live = await this.get({ videoId: uuid })
140
141 return { video, live }
142 }
143
144 // ---------------------------------------------------------------------------
145
113 async sendRTMPStreamInVideo (options: OverrideCommandOptions & { 146 async sendRTMPStreamInVideo (options: OverrideCommandOptions & {
114 videoId: number | string 147 videoId: number | string
115 fixtureName?: string 148 fixtureName?: string
@@ -130,6 +163,8 @@ export class LiveCommand extends AbstractCommand {
130 return testFfmpegStreamError(command, options.shouldHaveError) 163 return testFfmpegStreamError(command, options.shouldHaveError)
131 } 164 }
132 165
166 // ---------------------------------------------------------------------------
167
133 waitUntilPublished (options: OverrideCommandOptions & { 168 waitUntilPublished (options: OverrideCommandOptions & {
134 videoId: number | string 169 videoId: number | string
135 }) { 170 }) {
@@ -151,27 +186,77 @@ export class LiveCommand extends AbstractCommand {
151 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED }) 186 return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED })
152 } 187 }
153 188
154 waitUntilSegmentGeneration (options: OverrideCommandOptions & { 189 async waitUntilSegmentGeneration (options: OverrideCommandOptions & {
190 server: PeerTubeServer
155 videoUUID: string 191 videoUUID: string
156 playlistNumber: number 192 playlistNumber: number
157 segment: number 193 segment: number
158 totalSessions?: number 194 objectStorage: boolean
159 }) { 195 }) {
160 const { playlistNumber, segment, videoUUID, totalSessions = 1 } = options 196 const { server, objectStorage, playlistNumber, segment, videoUUID } = options
197
161 const segmentName = `${playlistNumber}-00000${segment}.ts` 198 const segmentName = `${playlistNumber}-00000${segment}.ts`
199 const baseUrl = objectStorage
200 ? ObjectStorageCommand.getMockPlaylistBaseUrl() + 'hls'
201 : server.url + '/static/streaming-playlists/hls'
202
203 let error = true
204
205 while (error) {
206 try {
207 await this.getRawRequest({
208 ...options,
209
210 url: `${baseUrl}/${videoUUID}/${segmentName}`,
211 implicitToken: false,
212 defaultExpectedStatus: HttpStatusCode.OK_200
213 })
214
215 const video = await server.videos.get({ id: videoUUID })
216 const hlsPlaylist = video.streamingPlaylists[0]
217
218 const shaBody = await server.streamingPlaylists.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
219
220 if (!shaBody[segmentName]) {
221 throw new Error('Segment SHA does not exist')
222 }
162 223
163 return this.server.servers.waitUntilLog(`${videoUUID}/${segmentName}`, totalSessions * 2, false) 224 error = false
225 } catch {
226 error = true
227 await wait(100)
228 }
229 }
164 } 230 }
165 231
166 getSegment (options: OverrideCommandOptions & { 232 async waitUntilReplacedByReplay (options: OverrideCommandOptions & {
233 videoId: number | string
234 }) {
235 let video: VideoDetails
236
237 do {
238 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId })
239
240 await wait(500)
241 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED)
242 }
243
244 // ---------------------------------------------------------------------------
245
246 getSegmentFile (options: OverrideCommandOptions & {
167 videoUUID: string 247 videoUUID: string
168 playlistNumber: number 248 playlistNumber: number
169 segment: number 249 segment: number
250 objectStorage?: boolean // default false
170 }) { 251 }) {
171 const { playlistNumber, segment, videoUUID } = options 252 const { playlistNumber, segment, videoUUID, objectStorage = false } = options
172 253
173 const segmentName = `${playlistNumber}-00000${segment}.ts` 254 const segmentName = `${playlistNumber}-00000${segment}.ts`
174 const url = `${this.server.url}/static/streaming-playlists/hls/${videoUUID}/${segmentName}` 255 const baseUrl = objectStorage
256 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
257 : `${this.server.url}/static/streaming-playlists/hls`
258
259 const url = `${baseUrl}/${videoUUID}/${segmentName}`
175 260
176 return this.getRawRequest({ 261 return this.getRawRequest({
177 ...options, 262 ...options,
@@ -182,18 +267,30 @@ export class LiveCommand extends AbstractCommand {
182 }) 267 })
183 } 268 }
184 269
185 async waitUntilReplacedByReplay (options: OverrideCommandOptions & { 270 getPlaylistFile (options: OverrideCommandOptions & {
186 videoId: number | string 271 videoUUID: string
272 playlistName: string
273 objectStorage?: boolean // default false
187 }) { 274 }) {
188 let video: VideoDetails 275 const { playlistName, videoUUID, objectStorage = false } = options
189 276
190 do { 277 const baseUrl = objectStorage
191 video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) 278 ? ObjectStorageCommand.getMockPlaylistBaseUrl()
279 : `${this.server.url}/static/streaming-playlists/hls`
192 280
193 await wait(500) 281 const url = `${baseUrl}/${videoUUID}/${playlistName}`
194 } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED) 282
283 return this.getRawRequest({
284 ...options,
285
286 url,
287 implicitToken: false,
288 defaultExpectedStatus: HttpStatusCode.OK_200
289 })
195 } 290 }
196 291
292 // ---------------------------------------------------------------------------
293
197 async countPlaylists (options: OverrideCommandOptions & { 294 async countPlaylists (options: OverrideCommandOptions & {
198 videoUUID: string 295 videoUUID: string
199 }) { 296 }) {
diff --git a/shared/server-commands/videos/live.ts b/shared/server-commands/videos/live.ts
index 6f180b05f..ee7444b64 100644
--- a/shared/server-commands/videos/live.ts
+++ b/shared/server-commands/videos/live.ts
@@ -1,7 +1,7 @@
1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' 1import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg'
2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' 2import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
3import { VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models'
3import { PeerTubeServer } from '../server/server' 4import { PeerTubeServer } from '../server/server'
4import { VideoDetails, VideoInclude } from '@shared/models'
5 5
6function sendRTMPStream (options: { 6function sendRTMPStream (options: {
7 rtmpBaseUrl: string 7 rtmpBaseUrl: string
@@ -98,7 +98,10 @@ async function waitUntilLiveReplacedByReplayOnAllServers (servers: PeerTubeServe
98} 98}
99 99
100async function findExternalSavedVideo (server: PeerTubeServer, liveDetails: VideoDetails) { 100async function findExternalSavedVideo (server: PeerTubeServer, liveDetails: VideoDetails) {
101 const { data } = await server.videos.list({ token: server.accessToken, sort: '-publishedAt', include: VideoInclude.BLACKLISTED }) 101 const include = VideoInclude.BLACKLISTED
102 const privacyOneOf = [ VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.PUBLIC, VideoPrivacy.UNLISTED ]
103
104 const { data } = await server.videos.list({ token: server.accessToken, sort: '-publishedAt', include, privacyOneOf })
102 105
103 return data.find(v => v.name === liveDetails.name + ' - ' + new Date(liveDetails.publishedAt).toLocaleString()) 106 return data.find(v => v.name === liveDetails.name + ' - ' + new Date(liveDetails.publishedAt).toLocaleString())
104} 107}
diff --git a/shared/server-commands/videos/streaming-playlists-command.ts b/shared/server-commands/videos/streaming-playlists-command.ts
index 5d40d35cb..25e446e72 100644
--- a/shared/server-commands/videos/streaming-playlists-command.ts
+++ b/shared/server-commands/videos/streaming-playlists-command.ts
@@ -1,22 +1,42 @@
1import { wait } from '@shared/core-utils'
1import { HttpStatusCode } from '@shared/models' 2import { HttpStatusCode } from '@shared/models'
2import { unwrapBody, unwrapTextOrDecode, unwrapBodyOrDecodeToJSON } from '../requests' 3import { unwrapBody, unwrapBodyOrDecodeToJSON, unwrapTextOrDecode } from '../requests'
3import { AbstractCommand, OverrideCommandOptions } from '../shared' 4import { AbstractCommand, OverrideCommandOptions } from '../shared'
4 5
5export class StreamingPlaylistsCommand extends AbstractCommand { 6export class StreamingPlaylistsCommand extends AbstractCommand {
6 7
7 get (options: OverrideCommandOptions & { 8 async get (options: OverrideCommandOptions & {
8 url: string 9 url: string
10 withRetry?: boolean // default false
11 currentRetry?: number
9 }) { 12 }) {
10 return unwrapTextOrDecode(this.getRawRequest({ 13 const { withRetry, currentRetry = 1 } = options
11 ...options,
12 14
13 url: options.url, 15 try {
14 implicitToken: false, 16 const result = await unwrapTextOrDecode(this.getRawRequest({
15 defaultExpectedStatus: HttpStatusCode.OK_200 17 ...options,
16 })) 18
19 url: options.url,
20 implicitToken: false,
21 defaultExpectedStatus: HttpStatusCode.OK_200
22 }))
23
24 return result
25 } catch (err) {
26 if (!withRetry || currentRetry > 5) throw err
27
28 await wait(100)
29
30 return this.get({
31 ...options,
32
33 withRetry,
34 currentRetry: currentRetry + 1
35 })
36 }
17 } 37 }
18 38
19 getSegment (options: OverrideCommandOptions & { 39 getFragmentedSegment (options: OverrideCommandOptions & {
20 url: string 40 url: string
21 range?: string 41 range?: string
22 }) { 42 }) {
diff --git a/shared/server-commands/videos/video-token-command.ts b/shared/server-commands/videos/video-token-command.ts
new file mode 100644
index 000000000..0531bee65
--- /dev/null
+++ b/shared/server-commands/videos/video-token-command.ts
@@ -0,0 +1,31 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { HttpStatusCode, VideoToken } from '@shared/models'
4import { unwrapBody } from '../requests'
5import { AbstractCommand, OverrideCommandOptions } from '../shared'
6
7export class VideoTokenCommand extends AbstractCommand {
8
9 create (options: OverrideCommandOptions & {
10 videoId: number | string
11 }) {
12 const { videoId } = options
13 const path = '/api/v1/videos/' + videoId + '/token'
14
15 return unwrapBody<VideoToken>(this.postBodyRequest({
16 ...options,
17
18 path,
19 implicitToken: true,
20 defaultExpectedStatus: HttpStatusCode.OK_200
21 }))
22 }
23
24 async getVideoFileToken (options: OverrideCommandOptions & {
25 videoId: number | string
26 }) {
27 const { files } = await this.create(options)
28
29 return files.token
30 }
31}
diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts
index 168391523..590244484 100644
--- a/shared/server-commands/videos/videos-command.ts
+++ b/shared/server-commands/videos/videos-command.ts
@@ -4,7 +4,7 @@ import { expect } from 'chai'
4import { createReadStream, stat } from 'fs-extra' 4import { createReadStream, stat } from 'fs-extra'
5import got, { Response as GotResponse } from 'got' 5import got, { Response as GotResponse } from 'got'
6import validator from 'validator' 6import validator from 'validator'
7import { buildAbsoluteFixturePath, omit, pick, wait } from '@shared/core-utils' 7import { buildAbsoluteFixturePath, getAllPrivacies, omit, pick, wait } from '@shared/core-utils'
8import { buildUUID } from '@shared/extra-utils' 8import { buildUUID } from '@shared/extra-utils'
9import { 9import {
10 HttpStatusCode, 10 HttpStatusCode,
@@ -15,6 +15,7 @@ import {
15 VideoCreateResult, 15 VideoCreateResult,
16 VideoDetails, 16 VideoDetails,
17 VideoFileMetadata, 17 VideoFileMetadata,
18 VideoInclude,
18 VideoPrivacy, 19 VideoPrivacy,
19 VideosCommonQuery, 20 VideosCommonQuery,
20 VideoTranscodingCreate 21 VideoTranscodingCreate
@@ -234,6 +235,22 @@ export class VideosCommand extends AbstractCommand {
234 }) 235 })
235 } 236 }
236 237
238 listAllForAdmin (options: OverrideCommandOptions & VideosCommonQuery = {}) {
239 const include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER
240 const nsfw = 'both'
241 const privacyOneOf = getAllPrivacies()
242
243 return this.list({
244 ...options,
245
246 include,
247 nsfw,
248 privacyOneOf,
249
250 token: this.buildCommonRequestToken({ ...options, implicitToken: true })
251 })
252 }
253
237 listByAccount (options: OverrideCommandOptions & VideosCommonQuery & { 254 listByAccount (options: OverrideCommandOptions & VideosCommonQuery & {
238 handle: string 255 handle: string
239 }) { 256 }) {
@@ -342,8 +359,9 @@ export class VideosCommand extends AbstractCommand {
342 async upload (options: OverrideCommandOptions & { 359 async upload (options: OverrideCommandOptions & {
343 attributes?: VideoEdit 360 attributes?: VideoEdit
344 mode?: 'legacy' | 'resumable' // default legacy 361 mode?: 'legacy' | 'resumable' // default legacy
362 waitTorrentGeneration?: boolean // default true
345 } = {}) { 363 } = {}) {
346 const { mode = 'legacy' } = options 364 const { mode = 'legacy', waitTorrentGeneration = true } = options
347 let defaultChannelId = 1 365 let defaultChannelId = 1
348 366
349 try { 367 try {
@@ -377,7 +395,7 @@ export class VideosCommand extends AbstractCommand {
377 395
378 // Wait torrent generation 396 // Wait torrent generation
379 const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 }) 397 const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 })
380 if (expectedStatus === HttpStatusCode.OK_200) { 398 if (expectedStatus === HttpStatusCode.OK_200 && waitTorrentGeneration) {
381 let video: VideoDetails 399 let video: VideoDetails
382 400
383 do { 401 do {
@@ -692,6 +710,7 @@ export class VideosCommand extends AbstractCommand {
692 'categoryOneOf', 710 'categoryOneOf',
693 'licenceOneOf', 711 'licenceOneOf',
694 'languageOneOf', 712 'languageOneOf',
713 'privacyOneOf',
695 'tagsOneOf', 714 'tagsOneOf',
696 'tagsAllOf', 715 'tagsAllOf',
697 'isLocal', 716 'isLocal',