aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/common/array.ts14
-rw-r--r--shared/core-utils/videos/common.ts2
-rw-r--r--shared/ffmpeg/ffmpeg-images.ts61
-rw-r--r--shared/models/activitypub/activity.ts56
-rw-r--r--shared/models/activitypub/objects/activitypub-object.ts17
-rw-r--r--shared/models/activitypub/objects/common-objects.ts5
-rw-r--r--shared/models/activitypub/objects/dislike-object.ts6
-rw-r--r--shared/models/activitypub/objects/index.ts5
-rw-r--r--shared/models/activitypub/objects/object.model.ts1
-rw-r--r--shared/models/activitypub/objects/playlist-object.ts4
-rw-r--r--shared/models/activitypub/objects/video-comment-object.ts4
-rw-r--r--shared/models/activitypub/objects/video-object.ts (renamed from shared/models/activitypub/objects/video-torrent-object.ts)16
-rw-r--r--shared/models/metrics/playback-metric-create.model.ts2
-rw-r--r--shared/models/plugins/client/client-hook.model.ts4
-rw-r--r--shared/models/search/videos-common-query.model.ts4
-rw-r--r--shared/models/server/custom-config.model.ts6
-rw-r--r--shared/models/server/job.model.ts20
-rw-r--r--shared/models/server/server-config.model.ts2
-rw-r--r--shared/models/server/server-error-code.enum.ts5
-rw-r--r--shared/models/users/user-update-me.model.ts2
-rw-r--r--shared/models/users/user.model.ts2
-rw-r--r--shared/models/videos/index.ts2
-rw-r--r--shared/models/videos/storyboard.model.ts11
-rw-r--r--shared/models/videos/transcoding/video-transcoding-create.model.ts2
-rw-r--r--shared/models/videos/video-create.model.ts1
-rw-r--r--shared/models/videos/video-password.model.ts7
-rw-r--r--shared/models/videos/video-privacy.enum.ts3
-rw-r--r--shared/models/videos/video-update.model.ts1
-rw-r--r--shared/models/videos/video.model.ts24
-rw-r--r--shared/server-commands/requests/requests.ts4
-rw-r--r--shared/server-commands/server/config-command.ts21
-rw-r--r--shared/server-commands/server/jobs.ts15
-rw-r--r--shared/server-commands/server/object-storage-command.ts6
-rw-r--r--shared/server-commands/server/server.ts13
-rw-r--r--shared/server-commands/server/servers-command.ts4
-rw-r--r--shared/server-commands/shared/abstract-command.ts18
-rw-r--r--shared/server-commands/videos/captions-command.ts4
-rw-r--r--shared/server-commands/videos/comments-command.ts12
-rw-r--r--shared/server-commands/videos/index.ts2
-rw-r--r--shared/server-commands/videos/live-command.ts12
-rw-r--r--shared/server-commands/videos/storyboard-command.ts19
-rw-r--r--shared/server-commands/videos/video-passwords-command.ts55
-rw-r--r--shared/server-commands/videos/video-studio-command.ts2
-rw-r--r--shared/server-commands/videos/video-token-command.ts5
-rw-r--r--shared/server-commands/videos/videos-command.ts33
45 files changed, 397 insertions, 117 deletions
diff --git a/shared/core-utils/common/array.ts b/shared/core-utils/common/array.ts
index e1b422165..878ed1ffe 100644
--- a/shared/core-utils/common/array.ts
+++ b/shared/core-utils/common/array.ts
@@ -20,8 +20,22 @@ function uniqify <T> (elements: T[]) {
20 return Array.from(new Set(elements)) 20 return Array.from(new Set(elements))
21} 21}
22 22
23// Thanks: https://stackoverflow.com/a/12646864
24function shuffle <T> (elements: T[]) {
25 const shuffled = [ ...elements ]
26
27 for (let i = shuffled.length - 1; i > 0; i--) {
28 const j = Math.floor(Math.random() * (i + 1));
29
30 [ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ]
31 }
32
33 return shuffled
34}
35
23export { 36export {
24 uniqify, 37 uniqify,
25 findCommonElement, 38 findCommonElement,
39 shuffle,
26 arrayify 40 arrayify
27} 41}
diff --git a/shared/core-utils/videos/common.ts b/shared/core-utils/videos/common.ts
index 2c6efdb7f..0431edaaf 100644
--- a/shared/core-utils/videos/common.ts
+++ b/shared/core-utils/videos/common.ts
@@ -3,7 +3,7 @@ import { VideoPrivacy } from '../../models/videos/video-privacy.enum'
3import { VideoDetails } from '../../models/videos/video.model' 3import { VideoDetails } from '../../models/videos/video.model'
4 4
5function getAllPrivacies () { 5function getAllPrivacies () {
6 return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED ] 6 return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED, VideoPrivacy.PASSWORD_PROTECTED ]
7} 7}
8 8
9function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlaylists'>>) { 9function getAllFiles (video: Partial<Pick<VideoDetails, 'files' | 'streamingPlaylists'>>) {
diff --git a/shared/ffmpeg/ffmpeg-images.ts b/shared/ffmpeg/ffmpeg-images.ts
index 2db63bd8b..618fac7d1 100644
--- a/shared/ffmpeg/ffmpeg-images.ts
+++ b/shared/ffmpeg/ffmpeg-images.ts
@@ -1,4 +1,5 @@
1import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper' 1import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper'
2import { getVideoStreamDuration } from './ffprobe'
2 3
3export class FFmpegImage { 4export class FFmpegImage {
4 private readonly commandWrapper: FFmpegCommandWrapper 5 private readonly commandWrapper: FFmpegCommandWrapper
@@ -36,24 +37,56 @@ export class FFmpegImage {
36 37
37 async generateThumbnailFromVideo (options: { 38 async generateThumbnailFromVideo (options: {
38 fromPath: string 39 fromPath: string
39 folder: string 40 output: string
40 imageName: string
41 }) { 41 }) {
42 const { fromPath, folder, imageName } = options 42 const { fromPath, output } = options
43 43
44 const pendingImageName = 'pending-' + imageName 44 let duration = await getVideoStreamDuration(fromPath)
45 if (isNaN(duration)) duration = 0
45 46
46 const thumbnailOptions = { 47 this.commandWrapper.buildCommand(fromPath)
47 filename: pendingImageName, 48 .seekInput(duration / 2)
48 count: 1, 49 .videoFilter('thumbnail=500')
49 folder 50 .outputOption('-frames:v 1')
51 .output(output)
52
53 return this.commandWrapper.runCommand()
54 }
55
56 async generateStoryboardFromVideo (options: {
57 path: string
58 destination: string
59
60 sprites: {
61 size: {
62 width: number
63 height: number
64 }
65
66 count: {
67 width: number
68 height: number
69 }
70
71 duration: number
50 } 72 }
73 }) {
74 const { path, destination, sprites } = options
75
76 const command = this.commandWrapper.buildCommand(path)
51 77
52 return new Promise<string>((res, rej) => { 78 const filter = [
53 this.commandWrapper.buildCommand(fromPath) 79 `setpts=N/round(FRAME_RATE)/TB`,
54 .on('error', rej) 80 `select='not(mod(t,${options.sprites.duration}))'`,
55 .on('end', () => res(imageName)) 81 `scale=${sprites.size.width}:${sprites.size.height}`,
56 .thumbnail(thumbnailOptions) 82 `tile=layout=${sprites.count.width}x${sprites.count.height}`
57 }) 83 ].join(',')
84
85 command.outputOption('-filter_complex', filter)
86 command.outputOption('-frames:v', '1')
87 command.outputOption('-q:v', '2')
88 command.output(destination)
89
90 return this.commandWrapper.runCommand()
58 } 91 }
59} 92}
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index fd5d38316..10cf53ead 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -1,20 +1,34 @@
1import { ActivityPubActor } from './activitypub-actor' 1import { ActivityPubActor } from './activitypub-actor'
2import { ActivityPubSignature } from './activitypub-signature' 2import { ActivityPubSignature } from './activitypub-signature'
3import { ActivityFlagReasonObject, CacheFileObject, VideoObject, WatchActionObject } from './objects' 3import {
4import { AbuseObject } from './objects/abuse-object' 4 ActivityFlagReasonObject,
5import { DislikeObject } from './objects/dislike-object' 5 ActivityObject,
6import { APObject } from './objects/object.model' 6 APObjectId,
7import { PlaylistObject } from './objects/playlist-object' 7 CacheFileObject,
8import { VideoCommentObject } from './objects/video-comment-object' 8 PlaylistObject,
9 VideoCommentObject,
10 VideoObject,
11 WatchActionObject
12} from './objects'
13
14export type ActivityUpdateObject =
15 Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string> | ActivityPubActor
16
17// Cannot Extract from Activity because of circular reference
18export type ActivityUndoObject =
19 ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate<CacheFileObject | string> | ActivityAnnounce
20
21export type ActivityCreateObject =
22 Extract<ActivityObject, VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string>
9 23
10export type Activity = 24export type Activity =
11 ActivityCreate | 25 ActivityCreate<ActivityCreateObject> |
12 ActivityUpdate | 26 ActivityUpdate<ActivityUpdateObject> |
13 ActivityDelete | 27 ActivityDelete |
14 ActivityFollow | 28 ActivityFollow |
15 ActivityAccept | 29 ActivityAccept |
16 ActivityAnnounce | 30 ActivityAnnounce |
17 ActivityUndo | 31 ActivityUndo<ActivityUndoObject> |
18 ActivityLike | 32 ActivityLike |
19 ActivityReject | 33 ActivityReject |
20 ActivityView | 34 ActivityView |
@@ -50,19 +64,19 @@ export interface BaseActivity {
50 signature?: ActivityPubSignature 64 signature?: ActivityPubSignature
51} 65}
52 66
53export interface ActivityCreate extends BaseActivity { 67export interface ActivityCreate <T extends ActivityCreateObject> extends BaseActivity {
54 type: 'Create' 68 type: 'Create'
55 object: VideoObject | AbuseObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject | WatchActionObject 69 object: T
56} 70}
57 71
58export interface ActivityUpdate extends BaseActivity { 72export interface ActivityUpdate <T extends ActivityUpdateObject> extends BaseActivity {
59 type: 'Update' 73 type: 'Update'
60 object: VideoObject | ActivityPubActor | CacheFileObject | PlaylistObject 74 object: T
61} 75}
62 76
63export interface ActivityDelete extends BaseActivity { 77export interface ActivityDelete extends BaseActivity {
64 type: 'Delete' 78 type: 'Delete'
65 object: string | { id: string } 79 object: APObjectId
66} 80}
67 81
68export interface ActivityFollow extends BaseActivity { 82export interface ActivityFollow extends BaseActivity {
@@ -82,23 +96,23 @@ export interface ActivityReject extends BaseActivity {
82 96
83export interface ActivityAnnounce extends BaseActivity { 97export interface ActivityAnnounce extends BaseActivity {
84 type: 'Announce' 98 type: 'Announce'
85 object: APObject 99 object: APObjectId
86} 100}
87 101
88export interface ActivityUndo extends BaseActivity { 102export interface ActivityUndo <T extends ActivityUndoObject> extends BaseActivity {
89 type: 'Undo' 103 type: 'Undo'
90 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce 104 object: T
91} 105}
92 106
93export interface ActivityLike extends BaseActivity { 107export interface ActivityLike extends BaseActivity {
94 type: 'Like' 108 type: 'Like'
95 object: APObject 109 object: APObjectId
96} 110}
97 111
98export interface ActivityView extends BaseActivity { 112export interface ActivityView extends BaseActivity {
99 type: 'View' 113 type: 'View'
100 actor: string 114 actor: string
101 object: APObject 115 object: APObjectId
102 116
103 // If sending a "viewer" event 117 // If sending a "viewer" event
104 expires?: string 118 expires?: string
@@ -108,13 +122,13 @@ export interface ActivityDislike extends BaseActivity {
108 id: string 122 id: string
109 type: 'Dislike' 123 type: 'Dislike'
110 actor: string 124 actor: string
111 object: APObject 125 object: APObjectId
112} 126}
113 127
114export interface ActivityFlag extends BaseActivity { 128export interface ActivityFlag extends BaseActivity {
115 type: 'Flag' 129 type: 'Flag'
116 content: string 130 content: string
117 object: APObject | APObject[] 131 object: APObjectId | APObjectId[]
118 tag?: ActivityFlagReasonObject[] 132 tag?: ActivityFlagReasonObject[]
119 startAt?: number 133 startAt?: number
120 endAt?: number 134 endAt?: number
diff --git a/shared/models/activitypub/objects/activitypub-object.ts b/shared/models/activitypub/objects/activitypub-object.ts
new file mode 100644
index 000000000..faeac2618
--- /dev/null
+++ b/shared/models/activitypub/objects/activitypub-object.ts
@@ -0,0 +1,17 @@
1import { AbuseObject } from './abuse-object'
2import { CacheFileObject } from './cache-file-object'
3import { PlaylistObject } from './playlist-object'
4import { VideoCommentObject } from './video-comment-object'
5import { VideoObject } from './video-object'
6import { WatchActionObject } from './watch-action-object'
7
8export type ActivityObject =
9 VideoObject |
10 AbuseObject |
11 VideoCommentObject |
12 CacheFileObject |
13 PlaylistObject |
14 WatchActionObject |
15 string
16
17export type APObjectId = string | { id: string }
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index 9bf994379..db9c73658 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -114,10 +114,7 @@ export type ActivityUrlObject =
114 | ActivityVideoFileMetadataUrlObject 114 | ActivityVideoFileMetadataUrlObject
115 | ActivityTrackerUrlObject 115 | ActivityTrackerUrlObject
116 116
117export interface ActivityPubAttributedTo { 117export type ActivityPubAttributedTo = { type: 'Group' | 'Person', id: string } | string
118 type: 'Group' | 'Person'
119 id: string
120}
121 118
122export interface ActivityTombstoneObject { 119export interface ActivityTombstoneObject {
123 '@context'?: any 120 '@context'?: any
diff --git a/shared/models/activitypub/objects/dislike-object.ts b/shared/models/activitypub/objects/dislike-object.ts
deleted file mode 100644
index 7218fb784..000000000
--- a/shared/models/activitypub/objects/dislike-object.ts
+++ /dev/null
@@ -1,6 +0,0 @@
1export interface DislikeObject {
2 id: string
3 type: 'Dislike'
4 actor: string
5 object: string
6}
diff --git a/shared/models/activitypub/objects/index.ts b/shared/models/activitypub/objects/index.ts
index 9aa3c462c..753e02003 100644
--- a/shared/models/activitypub/objects/index.ts
+++ b/shared/models/activitypub/objects/index.ts
@@ -1,10 +1,9 @@
1export * from './abuse-object' 1export * from './abuse-object'
2export * from './activitypub-object'
2export * from './cache-file-object' 3export * from './cache-file-object'
3export * from './common-objects' 4export * from './common-objects'
4export * from './dislike-object'
5export * from './object.model'
6export * from './playlist-element-object' 5export * from './playlist-element-object'
7export * from './playlist-object' 6export * from './playlist-object'
8export * from './video-comment-object' 7export * from './video-comment-object'
9export * from './video-torrent-object' 8export * from './video-object'
10export * from './watch-action-object' 9export * from './watch-action-object'
diff --git a/shared/models/activitypub/objects/object.model.ts b/shared/models/activitypub/objects/object.model.ts
deleted file mode 100644
index 3fd33800a..000000000
--- a/shared/models/activitypub/objects/object.model.ts
+++ /dev/null
@@ -1 +0,0 @@
1export type APObject = string | { id: string }
diff --git a/shared/models/activitypub/objects/playlist-object.ts b/shared/models/activitypub/objects/playlist-object.ts
index 842c03790..0ccb71828 100644
--- a/shared/models/activitypub/objects/playlist-object.ts
+++ b/shared/models/activitypub/objects/playlist-object.ts
@@ -1,4 +1,4 @@
1import { ActivityIconObject } from './common-objects' 1import { ActivityIconObject, ActivityPubAttributedTo } from './common-objects'
2 2
3export interface PlaylistObject { 3export interface PlaylistObject {
4 id: string 4 id: string
@@ -12,7 +12,7 @@ export interface PlaylistObject {
12 uuid: string 12 uuid: string
13 13
14 totalItems: number 14 totalItems: number
15 attributedTo: string[] 15 attributedTo: ActivityPubAttributedTo[]
16 16
17 icon?: ActivityIconObject 17 icon?: ActivityIconObject
18 18
diff --git a/shared/models/activitypub/objects/video-comment-object.ts b/shared/models/activitypub/objects/video-comment-object.ts
index ba9001730..fb1e6f8db 100644
--- a/shared/models/activitypub/objects/video-comment-object.ts
+++ b/shared/models/activitypub/objects/video-comment-object.ts
@@ -1,4 +1,4 @@
1import { ActivityTagObject } from './common-objects' 1import { ActivityPubAttributedTo, ActivityTagObject } from './common-objects'
2 2
3export interface VideoCommentObject { 3export interface VideoCommentObject {
4 type: 'Note' 4 type: 'Note'
@@ -11,6 +11,6 @@ export interface VideoCommentObject {
11 published: string 11 published: string
12 updated: string 12 updated: string
13 url: string 13 url: string
14 attributedTo: string 14 attributedTo: ActivityPubAttributedTo
15 tag: ActivityTagObject[] 15 tag: ActivityTagObject[]
16} 16}
diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-object.ts
index 23d54bdbd..a252a2df0 100644
--- a/shared/models/activitypub/objects/video-torrent-object.ts
+++ b/shared/models/activitypub/objects/video-object.ts
@@ -51,6 +51,22 @@ export interface VideoObject {
51 51
52 attributedTo: ActivityPubAttributedTo[] 52 attributedTo: ActivityPubAttributedTo[]
53 53
54 preview?: ActivityPubStoryboard[]
55
54 to?: string[] 56 to?: string[]
55 cc?: string[] 57 cc?: string[]
56} 58}
59
60export interface ActivityPubStoryboard {
61 type: 'Image'
62 rel: [ 'storyboard' ]
63 url: {
64 href: string
65 mediaType: string
66 width: number
67 height: number
68 tileWidth: number
69 tileHeight: number
70 tileDuration: string
71 }[]
72}
diff --git a/shared/models/metrics/playback-metric-create.model.ts b/shared/models/metrics/playback-metric-create.model.ts
index d669ab690..3a8f328c8 100644
--- a/shared/models/metrics/playback-metric-create.model.ts
+++ b/shared/models/metrics/playback-metric-create.model.ts
@@ -1,7 +1,7 @@
1import { VideoResolution } from '../videos' 1import { VideoResolution } from '../videos'
2 2
3export interface PlaybackMetricCreate { 3export interface PlaybackMetricCreate {
4 playerMode: 'p2p-media-loader' | 'webtorrent' 4 playerMode: 'p2p-media-loader' | 'webtorrent' | 'web-video' // FIXME: remove webtorrent player mode not used anymore in PeerTube v6
5 5
6 resolution?: VideoResolution 6 resolution?: VideoResolution
7 fps?: number 7 fps?: number
diff --git a/shared/models/plugins/client/client-hook.model.ts b/shared/models/plugins/client/client-hook.model.ts
index bc3f5dd9f..4a0818c99 100644
--- a/shared/models/plugins/client/client-hook.model.ts
+++ b/shared/models/plugins/client/client-hook.model.ts
@@ -59,6 +59,10 @@ export const clientFilterHookObject = {
59 'filter:internal.video-watch.player.build-options.params': true, 59 'filter:internal.video-watch.player.build-options.params': true,
60 'filter:internal.video-watch.player.build-options.result': true, 60 'filter:internal.video-watch.player.build-options.result': true,
61 61
62 // Filter the options to load a new video in our player
63 'filter:internal.video-watch.player.load-options.params': true,
64 'filter:internal.video-watch.player.load-options.result': true,
65
62 // Filter our SVG icons content 66 // Filter our SVG icons content
63 'filter:internal.common.svg-icons.get-content.params': true, 67 'filter:internal.common.svg-icons.get-content.params': true,
64 'filter:internal.common.svg-icons.get-content.result': true, 68 'filter:internal.common.svg-icons.get-content.result': true,
diff --git a/shared/models/search/videos-common-query.model.ts b/shared/models/search/videos-common-query.model.ts
index da479c928..2c52ca8cf 100644
--- a/shared/models/search/videos-common-query.model.ts
+++ b/shared/models/search/videos-common-query.model.ts
@@ -30,7 +30,9 @@ export interface VideosCommonQuery {
30 tagsAllOf?: string[] 30 tagsAllOf?: string[]
31 31
32 hasHLSFiles?: boolean 32 hasHLSFiles?: boolean
33 hasWebtorrentFiles?: boolean 33
34 hasWebtorrentFiles?: boolean // TODO: remove in v7
35 hasWebVideoFiles?: boolean
34 36
35 skipCount?: boolean 37 skipCount?: boolean
36 38
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 4202589f3..9aa66f2b8 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -78,6 +78,10 @@ export interface CustomConfig {
78 torrents: { 78 torrents: {
79 size: number 79 size: number
80 } 80 }
81
82 storyboards: {
83 size: number
84 }
81 } 85 }
82 86
83 signup: { 87 signup: {
@@ -129,7 +133,7 @@ export interface CustomConfig {
129 133
130 alwaysTranscodeOriginalResolution: boolean 134 alwaysTranscodeOriginalResolution: boolean
131 135
132 webtorrent: { 136 webVideos: {
133 enabled: boolean 137 enabled: boolean
134 } 138 }
135 139
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index 22ecee324..c14806dab 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -30,6 +30,7 @@ export type JobType =
30 | 'video-studio-edition' 30 | 'video-studio-edition'
31 | 'video-transcoding' 31 | 'video-transcoding'
32 | 'videos-views-stats' 32 | 'videos-views-stats'
33 | 'generate-video-storyboard'
33 34
34export interface Job { 35export interface Job {
35 id: number | string 36 id: number | string
@@ -147,17 +148,17 @@ export interface HLSTranscodingPayload extends BaseTranscodingPayload {
147 fps: number 148 fps: number
148 copyCodecs: boolean 149 copyCodecs: boolean
149 150
150 deleteWebTorrentFiles: boolean 151 deleteWebVideoFiles: boolean
151} 152}
152 153
153export interface NewWebTorrentResolutionTranscodingPayload extends BaseTranscodingPayload { 154export interface NewWebVideoResolutionTranscodingPayload extends BaseTranscodingPayload {
154 type: 'new-resolution-to-webtorrent' 155 type: 'new-resolution-to-web-video'
155 resolution: VideoResolution 156 resolution: VideoResolution
156 fps: number 157 fps: number
157} 158}
158 159
159export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { 160export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
160 type: 'merge-audio-to-webtorrent' 161 type: 'merge-audio-to-web-video'
161 162
162 resolution: VideoResolution 163 resolution: VideoResolution
163 fps: number 164 fps: number
@@ -166,7 +167,7 @@ export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
166} 167}
167 168
168export interface OptimizeTranscodingPayload extends BaseTranscodingPayload { 169export interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
169 type: 'optimize-to-webtorrent' 170 type: 'optimize-to-web-video'
170 171
171 quickTranscode: boolean 172 quickTranscode: boolean
172 173
@@ -175,7 +176,7 @@ export interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
175 176
176export type VideoTranscodingPayload = 177export type VideoTranscodingPayload =
177 HLSTranscodingPayload 178 HLSTranscodingPayload
178 | NewWebTorrentResolutionTranscodingPayload 179 | NewWebVideoResolutionTranscodingPayload
179 | OptimizeTranscodingPayload 180 | OptimizeTranscodingPayload
180 | MergeAudioTranscodingPayload 181 | MergeAudioTranscodingPayload
181 182
@@ -294,3 +295,10 @@ export interface TranscodingJobBuilderPayload {
294 priority?: number 295 priority?: number
295 }[][] 296 }[][]
296} 297}
298
299// ---------------------------------------------------------------------------
300
301export interface GenerateStoryboardPayload {
302 videoUUID: string
303 federate: boolean
304}
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index 024ed35bf..288cf84cd 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -140,7 +140,7 @@ export interface ServerConfig {
140 enabled: boolean 140 enabled: boolean
141 } 141 }
142 142
143 webtorrent: { 143 web_videos: {
144 enabled: boolean 144 enabled: boolean
145 } 145 }
146 146
diff --git a/shared/models/server/server-error-code.enum.ts b/shared/models/server/server-error-code.enum.ts
index 2b093380c..77d1e1d3f 100644
--- a/shared/models/server/server-error-code.enum.ts
+++ b/shared/models/server/server-error-code.enum.ts
@@ -49,7 +49,10 @@ export const enum ServerErrorCode {
49 49
50 RUNNER_JOB_NOT_IN_PROCESSING_STATE = 'runner_job_not_in_processing_state', 50 RUNNER_JOB_NOT_IN_PROCESSING_STATE = 'runner_job_not_in_processing_state',
51 RUNNER_JOB_NOT_IN_PENDING_STATE = 'runner_job_not_in_pending_state', 51 RUNNER_JOB_NOT_IN_PENDING_STATE = 'runner_job_not_in_pending_state',
52 UNKNOWN_RUNNER_TOKEN = 'unknown_runner_token' 52 UNKNOWN_RUNNER_TOKEN = 'unknown_runner_token',
53
54 VIDEO_REQUIRES_PASSWORD = 'video_requires_password',
55 INCORRECT_VIDEO_PASSWORD = 'incorrect_video_password'
53} 56}
54 57
55/** 58/**
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts
index f3cceb5f2..c1d5ffba4 100644
--- a/shared/models/users/user-update-me.model.ts
+++ b/shared/models/users/user-update-me.model.ts
@@ -5,8 +5,6 @@ export interface UserUpdateMe {
5 description?: string 5 description?: string
6 nsfwPolicy?: NSFWPolicyType 6 nsfwPolicy?: NSFWPolicyType
7 7
8 // FIXME: deprecated in favour of p2pEnabled in 4.1
9 webTorrentEnabled?: boolean
10 p2pEnabled?: boolean 8 p2pEnabled?: boolean
11 9
12 autoPlayVideo?: boolean 10 autoPlayVideo?: boolean
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 0761c1e32..9de4118b4 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -22,8 +22,6 @@ export interface User {
22 autoPlayNextVideo: boolean 22 autoPlayNextVideo: boolean
23 autoPlayNextVideoPlaylist: boolean 23 autoPlayNextVideoPlaylist: boolean
24 24
25 // @deprecated in favour of p2pEnabled
26 webTorrentEnabled: boolean
27 p2pEnabled: boolean 25 p2pEnabled: boolean
28 26
29 videosHistoryEnabled: boolean 27 videosHistoryEnabled: boolean
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts
index 4c1790228..b3ce6ad3f 100644
--- a/shared/models/videos/index.ts
+++ b/shared/models/videos/index.ts
@@ -15,6 +15,7 @@ export * from './channel-sync'
15 15
16export * from './nsfw-policy.type' 16export * from './nsfw-policy.type'
17 17
18export * from './storyboard.model'
18export * from './thumbnail.type' 19export * from './thumbnail.type'
19 20
20export * from './video-constant.model' 21export * from './video-constant.model'
@@ -39,3 +40,4 @@ export * from './video-update.model'
39export * from './video-view.model' 40export * from './video-view.model'
40export * from './video.model' 41export * from './video.model'
41export * from './video-create-result.model' 42export * from './video-create-result.model'
43export * from './video-password.model'
diff --git a/shared/models/videos/storyboard.model.ts b/shared/models/videos/storyboard.model.ts
new file mode 100644
index 000000000..c92c81f09
--- /dev/null
+++ b/shared/models/videos/storyboard.model.ts
@@ -0,0 +1,11 @@
1export interface Storyboard {
2 storyboardPath: string
3
4 totalHeight: number
5 totalWidth: number
6
7 spriteHeight: number
8 spriteWidth: number
9
10 spriteDuration: number
11}
diff --git a/shared/models/videos/transcoding/video-transcoding-create.model.ts b/shared/models/videos/transcoding/video-transcoding-create.model.ts
index aeb393e57..c6e756a0a 100644
--- a/shared/models/videos/transcoding/video-transcoding-create.model.ts
+++ b/shared/models/videos/transcoding/video-transcoding-create.model.ts
@@ -1,3 +1,3 @@
1export interface VideoTranscodingCreate { 1export interface VideoTranscodingCreate {
2 transcodingType: 'hls' | 'webtorrent' 2 transcodingType: 'hls' | 'webtorrent' | 'web-video' // TODO: remove webtorrent in v7
3} 3}
diff --git a/shared/models/videos/video-create.model.ts b/shared/models/videos/video-create.model.ts
index 732d508d1..7a34b5afe 100644
--- a/shared/models/videos/video-create.model.ts
+++ b/shared/models/videos/video-create.model.ts
@@ -18,6 +18,7 @@ export interface VideoCreate {
18 privacy: VideoPrivacy 18 privacy: VideoPrivacy
19 scheduleUpdate?: VideoScheduleUpdate 19 scheduleUpdate?: VideoScheduleUpdate
20 originallyPublishedAt?: Date | string 20 originallyPublishedAt?: Date | string
21 videoPasswords?: string[]
21 22
22 thumbnailfile?: Blob | string 23 thumbnailfile?: Blob | string
23 previewfile?: Blob | string 24 previewfile?: Blob | string
diff --git a/shared/models/videos/video-password.model.ts b/shared/models/videos/video-password.model.ts
new file mode 100644
index 000000000..c0280b9b9
--- /dev/null
+++ b/shared/models/videos/video-password.model.ts
@@ -0,0 +1,7 @@
1export interface VideoPassword {
2 id: number
3 password: string
4 videoId: number
5 createdAt: Date | string
6 updatedAt: Date | string
7}
diff --git a/shared/models/videos/video-privacy.enum.ts b/shared/models/videos/video-privacy.enum.ts
index 39fd0529f..12e1d196f 100644
--- a/shared/models/videos/video-privacy.enum.ts
+++ b/shared/models/videos/video-privacy.enum.ts
@@ -2,5 +2,6 @@ export const enum VideoPrivacy {
2 PUBLIC = 1, 2 PUBLIC = 1,
3 UNLISTED = 2, 3 UNLISTED = 2,
4 PRIVATE = 3, 4 PRIVATE = 3,
5 INTERNAL = 4 5 INTERNAL = 4,
6 PASSWORD_PROTECTED = 5
6} 7}
diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts
index 86653b959..43537b5af 100644
--- a/shared/models/videos/video-update.model.ts
+++ b/shared/models/videos/video-update.model.ts
@@ -19,6 +19,7 @@ export interface VideoUpdate {
19 previewfile?: Blob 19 previewfile?: Blob
20 scheduleUpdate?: VideoScheduleUpdate 20 scheduleUpdate?: VideoScheduleUpdate
21 originallyPublishedAt?: Date | string 21 originallyPublishedAt?: Date | string
22 videoPasswords?: string[]
22 23
23 pluginData?: any 24 pluginData?: any
24} 25}
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 06ffb327c..9004efb35 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -7,7 +7,7 @@ import { VideoScheduleUpdate } from './video-schedule-update.model'
7import { VideoState } from './video-state.enum' 7import { VideoState } from './video-state.enum'
8import { VideoStreamingPlaylist } from './video-streaming-playlist.model' 8import { VideoStreamingPlaylist } from './video-streaming-playlist.model'
9 9
10export interface Video { 10export interface Video extends Partial<VideoAdditionalAttributes> {
11 id: number 11 id: number
12 uuid: string 12 uuid: string
13 shortUUID: string 13 shortUUID: string
@@ -57,20 +57,22 @@ export interface Video {
57 } 57 }
58 58
59 pluginData?: any 59 pluginData?: any
60}
60 61
61 // Additional attributes dependending on the query 62// Not included by default, needs query params
62 waitTranscoding?: boolean 63export interface VideoAdditionalAttributes {
63 state?: VideoConstant<VideoState> 64 waitTranscoding: boolean
64 scheduledUpdate?: VideoScheduleUpdate 65 state: VideoConstant<VideoState>
66 scheduledUpdate: VideoScheduleUpdate
65 67
66 blacklisted?: boolean 68 blacklisted: boolean
67 blacklistedReason?: string 69 blacklistedReason: string
68 70
69 blockedOwner?: boolean 71 blockedOwner: boolean
70 blockedServer?: boolean 72 blockedServer: boolean
71 73
72 files?: VideoFile[] 74 files: VideoFile[]
73 streamingPlaylists?: VideoStreamingPlaylist[] 75 streamingPlaylists: VideoStreamingPlaylist[]
74} 76}
75 77
76export interface VideoDetails extends Video { 78export interface VideoDetails extends Video {
diff --git a/shared/server-commands/requests/requests.ts b/shared/server-commands/requests/requests.ts
index e3f1817f1..8227017eb 100644
--- a/shared/server-commands/requests/requests.ts
+++ b/shared/server-commands/requests/requests.ts
@@ -29,6 +29,7 @@ function makeRawRequest (options: {
29 range?: string 29 range?: string
30 query?: { [ id: string ]: string } 30 query?: { [ id: string ]: string }
31 method?: 'GET' | 'POST' 31 method?: 'GET' | 'POST'
32 headers?: { [ name: string ]: string }
32}) { 33}) {
33 const { host, protocol, pathname } = new URL(options.url) 34 const { host, protocol, pathname } = new URL(options.url)
34 35
@@ -37,7 +38,7 @@ function makeRawRequest (options: {
37 path: pathname, 38 path: pathname,
38 contentType: undefined, 39 contentType: undefined,
39 40
40 ...pick(options, [ 'expectedStatus', 'range', 'token', 'query' ]) 41 ...pick(options, [ 'expectedStatus', 'range', 'token', 'query', 'headers' ])
41 } 42 }
42 43
43 if (options.method === 'POST') { 44 if (options.method === 'POST') {
@@ -132,6 +133,7 @@ function makePutBodyRequest (options: {
132 token?: string 133 token?: string
133 fields: { [ fieldName: string ]: any } 134 fields: { [ fieldName: string ]: any }
134 expectedStatus?: HttpStatusCode 135 expectedStatus?: HttpStatusCode
136 headers?: { [name: string]: string }
135}) { 137}) {
136 const req = request(options.url).put(options.path) 138 const req = request(options.url).put(options.path)
137 .send(options.fields) 139 .send(options.fields)
diff --git a/shared/server-commands/server/config-command.ts b/shared/server-commands/server/config-command.ts
index b94bd2625..7f1e9d977 100644
--- a/shared/server-commands/server/config-command.ts
+++ b/shared/server-commands/server/config-command.ts
@@ -131,7 +131,7 @@ export class ConfigCommand extends AbstractCommand {
131 } 131 }
132 132
133 // TODO: convert args to object 133 // TODO: convert args to object
134 enableTranscoding (webtorrent = true, hls = true, with0p = false) { 134 enableTranscoding (webVideo = true, hls = true, with0p = false) {
135 return this.updateExistingSubConfig({ 135 return this.updateExistingSubConfig({
136 newConfig: { 136 newConfig: {
137 transcoding: { 137 transcoding: {
@@ -142,8 +142,8 @@ export class ConfigCommand extends AbstractCommand {
142 142
143 resolutions: ConfigCommand.getCustomConfigResolutions(true, with0p), 143 resolutions: ConfigCommand.getCustomConfigResolutions(true, with0p),
144 144
145 webtorrent: { 145 webVideos: {
146 enabled: webtorrent 146 enabled: webVideo
147 }, 147 },
148 hls: { 148 hls: {
149 enabled: hls 149 enabled: hls
@@ -154,19 +154,23 @@ export class ConfigCommand extends AbstractCommand {
154 } 154 }
155 155
156 // TODO: convert args to object 156 // TODO: convert args to object
157 enableMinimumTranscoding (webtorrent = true, hls = true) { 157 enableMinimumTranscoding (webVideo = true, hls = true) {
158 return this.updateExistingSubConfig({ 158 return this.updateExistingSubConfig({
159 newConfig: { 159 newConfig: {
160 transcoding: { 160 transcoding: {
161 enabled: true, 161 enabled: true,
162
163 allowAudioFiles: true,
164 allowAdditionalExtensions: true,
165
162 resolutions: { 166 resolutions: {
163 ...ConfigCommand.getCustomConfigResolutions(false), 167 ...ConfigCommand.getCustomConfigResolutions(false),
164 168
165 '240p': true 169 '240p': true
166 }, 170 },
167 171
168 webtorrent: { 172 webVideos: {
169 enabled: webtorrent 173 enabled: webVideo
170 }, 174 },
171 hls: { 175 hls: {
172 enabled: hls 176 enabled: hls
@@ -368,6 +372,9 @@ export class ConfigCommand extends AbstractCommand {
368 }, 372 },
369 torrents: { 373 torrents: {
370 size: 4 374 size: 4
375 },
376 storyboards: {
377 size: 5
371 } 378 }
372 }, 379 },
373 signup: { 380 signup: {
@@ -417,7 +424,7 @@ export class ConfigCommand extends AbstractCommand {
417 '2160p': false 424 '2160p': false
418 }, 425 },
419 alwaysTranscodeOriginalResolution: true, 426 alwaysTranscodeOriginalResolution: true,
420 webtorrent: { 427 webVideos: {
421 enabled: true 428 enabled: true
422 }, 429 },
423 hls: { 430 hls: {
diff --git a/shared/server-commands/server/jobs.ts b/shared/server-commands/server/jobs.ts
index ff3098063..8f131fba4 100644
--- a/shared/server-commands/server/jobs.ts
+++ b/shared/server-commands/server/jobs.ts
@@ -33,6 +33,8 @@ async function waitJobs (
33 33
34 // Check if each server has pending request 34 // Check if each server has pending request
35 for (const server of servers) { 35 for (const server of servers) {
36 if (process.env.DEBUG) console.log('Checking ' + server.url)
37
36 for (const state of states) { 38 for (const state of states) {
37 39
38 const jobPromise = server.jobs.list({ 40 const jobPromise = server.jobs.list({
@@ -45,6 +47,10 @@ async function waitJobs (
45 .then(jobs => { 47 .then(jobs => {
46 if (jobs.length !== 0) { 48 if (jobs.length !== 0) {
47 pendingRequests = true 49 pendingRequests = true
50
51 if (process.env.DEBUG) {
52 console.log(jobs)
53 }
48 } 54 }
49 }) 55 })
50 56
@@ -55,6 +61,10 @@ async function waitJobs (
55 .then(obj => { 61 .then(obj => {
56 if (obj.activityPubMessagesWaiting !== 0) { 62 if (obj.activityPubMessagesWaiting !== 0) {
57 pendingRequests = true 63 pendingRequests = true
64
65 if (process.env.DEBUG) {
66 console.log('AP messages waiting: ' + obj.activityPubMessagesWaiting)
67 }
58 } 68 }
59 }) 69 })
60 tasks.push(debugPromise) 70 tasks.push(debugPromise)
@@ -65,12 +75,15 @@ async function waitJobs (
65 for (const job of data) { 75 for (const job of data) {
66 if (job.state.id !== RunnerJobState.COMPLETED) { 76 if (job.state.id !== RunnerJobState.COMPLETED) {
67 pendingRequests = true 77 pendingRequests = true
78
79 if (process.env.DEBUG) {
80 console.log(job)
81 }
68 } 82 }
69 } 83 }
70 }) 84 })
71 tasks.push(runnerJobsPromise) 85 tasks.push(runnerJobsPromise)
72 } 86 }
73
74 } 87 }
75 88
76 return tasks 89 return tasks
diff --git a/shared/server-commands/server/object-storage-command.ts b/shared/server-commands/server/object-storage-command.ts
index 7d8ec93cd..6bb232c36 100644
--- a/shared/server-commands/server/object-storage-command.ts
+++ b/shared/server-commands/server/object-storage-command.ts
@@ -42,7 +42,7 @@ export class ObjectStorageCommand {
42 bucket_name: this.getMockStreamingPlaylistsBucketName() 42 bucket_name: this.getMockStreamingPlaylistsBucketName()
43 }, 43 },
44 44
45 videos: { 45 web_videos: {
46 bucket_name: this.getMockWebVideosBucketName() 46 bucket_name: this.getMockWebVideosBucketName()
47 } 47 }
48 } 48 }
@@ -136,9 +136,9 @@ export class ObjectStorageCommand {
136 prefix: `test:server-${serverNumber}-streaming-playlists:` 136 prefix: `test:server-${serverNumber}-streaming-playlists:`
137 }, 137 },
138 138
139 videos: { 139 web_videos: {
140 bucket_name: this.DEFAULT_SCALEWAY_BUCKET, 140 bucket_name: this.DEFAULT_SCALEWAY_BUCKET,
141 prefix: `test:server-${serverNumber}-videos:` 141 prefix: `test:server-${serverNumber}-web-videos:`
142 } 142 }
143 } 143 }
144 } 144 }
diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts
index 70f7a3ee2..38568a890 100644
--- a/shared/server-commands/server/server.ts
+++ b/shared/server-commands/server/server.ts
@@ -32,8 +32,10 @@ import {
32 HistoryCommand, 32 HistoryCommand,
33 ImportsCommand, 33 ImportsCommand,
34 LiveCommand, 34 LiveCommand,
35 VideoPasswordsCommand,
35 PlaylistsCommand, 36 PlaylistsCommand,
36 ServicesCommand, 37 ServicesCommand,
38 StoryboardCommand,
37 StreamingPlaylistsCommand, 39 StreamingPlaylistsCommand,
38 VideosCommand, 40 VideosCommand,
39 VideoStudioCommand, 41 VideoStudioCommand,
@@ -146,6 +148,9 @@ export class PeerTubeServer {
146 twoFactor?: TwoFactorCommand 148 twoFactor?: TwoFactorCommand
147 videoToken?: VideoTokenCommand 149 videoToken?: VideoTokenCommand
148 registrations?: RegistrationsCommand 150 registrations?: RegistrationsCommand
151 videoPasswords?: VideoPasswordsCommand
152
153 storyboard?: StoryboardCommand
149 154
150 runners?: RunnersCommand 155 runners?: RunnersCommand
151 runnerRegistrationTokens?: RunnerRegistrationTokensCommand 156 runnerRegistrationTokens?: RunnerRegistrationTokensCommand
@@ -232,7 +237,7 @@ export class PeerTubeServer {
232 } 237 }
233 238
234 // Share the environment 239 // Share the environment
235 const env = Object.create(process.env) 240 const env = { ...process.env }
236 env['NODE_ENV'] = 'test' 241 env['NODE_ENV'] = 'test'
237 env['NODE_APP_INSTANCE'] = this.internalServerNumber.toString() 242 env['NODE_APP_INSTANCE'] = this.internalServerNumber.toString()
238 env['NODE_CONFIG'] = JSON.stringify(configOverride) 243 env['NODE_CONFIG'] = JSON.stringify(configOverride)
@@ -365,12 +370,13 @@ export class PeerTubeServer {
365 tmp_persistent: this.getDirectoryPath('tmp-persistent') + '/', 370 tmp_persistent: this.getDirectoryPath('tmp-persistent') + '/',
366 bin: this.getDirectoryPath('bin') + '/', 371 bin: this.getDirectoryPath('bin') + '/',
367 avatars: this.getDirectoryPath('avatars') + '/', 372 avatars: this.getDirectoryPath('avatars') + '/',
368 videos: this.getDirectoryPath('videos') + '/', 373 web_videos: this.getDirectoryPath('web-videos') + '/',
369 streaming_playlists: this.getDirectoryPath('streaming-playlists') + '/', 374 streaming_playlists: this.getDirectoryPath('streaming-playlists') + '/',
370 redundancy: this.getDirectoryPath('redundancy') + '/', 375 redundancy: this.getDirectoryPath('redundancy') + '/',
371 logs: this.getDirectoryPath('logs') + '/', 376 logs: this.getDirectoryPath('logs') + '/',
372 previews: this.getDirectoryPath('previews') + '/', 377 previews: this.getDirectoryPath('previews') + '/',
373 thumbnails: this.getDirectoryPath('thumbnails') + '/', 378 thumbnails: this.getDirectoryPath('thumbnails') + '/',
379 storyboards: this.getDirectoryPath('storyboards') + '/',
374 torrents: this.getDirectoryPath('torrents') + '/', 380 torrents: this.getDirectoryPath('torrents') + '/',
375 captions: this.getDirectoryPath('captions') + '/', 381 captions: this.getDirectoryPath('captions') + '/',
376 cache: this.getDirectoryPath('cache') + '/', 382 cache: this.getDirectoryPath('cache') + '/',
@@ -434,8 +440,11 @@ export class PeerTubeServer {
434 this.videoToken = new VideoTokenCommand(this) 440 this.videoToken = new VideoTokenCommand(this)
435 this.registrations = new RegistrationsCommand(this) 441 this.registrations = new RegistrationsCommand(this)
436 442
443 this.storyboard = new StoryboardCommand(this)
444
437 this.runners = new RunnersCommand(this) 445 this.runners = new RunnersCommand(this)
438 this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this) 446 this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this)
439 this.runnerJobs = new RunnerJobsCommand(this) 447 this.runnerJobs = new RunnerJobsCommand(this)
448 this.videoPasswords = new VideoPasswordsCommand(this)
440 } 449 }
441} 450}
diff --git a/shared/server-commands/server/servers-command.ts b/shared/server-commands/server/servers-command.ts
index 19645cb93..c91c2b008 100644
--- a/shared/server-commands/server/servers-command.ts
+++ b/shared/server-commands/server/servers-command.ts
@@ -77,8 +77,8 @@ export class ServersCommand extends AbstractCommand {
77 return join(root(), 'test' + this.server.internalServerNumber, directory) 77 return join(root(), 'test' + this.server.internalServerNumber, directory)
78 } 78 }
79 79
80 buildWebTorrentFilePath (fileUrl: string) { 80 buildWebVideoFilePath (fileUrl: string) {
81 return this.buildDirectory(join('videos', basename(fileUrl))) 81 return this.buildDirectory(join('web-videos', basename(fileUrl)))
82 } 82 }
83 83
84 buildFragmentedFilePath (videoUUID: string, fileUrl: string) { 84 buildFragmentedFilePath (videoUUID: string, fileUrl: string) {
diff --git a/shared/server-commands/shared/abstract-command.ts b/shared/server-commands/shared/abstract-command.ts
index ca4ffada9..463acc26b 100644
--- a/shared/server-commands/shared/abstract-command.ts
+++ b/shared/server-commands/shared/abstract-command.ts
@@ -101,25 +101,29 @@ abstract class AbstractCommand {
101 101
102 protected putBodyRequest (options: InternalCommonCommandOptions & { 102 protected putBodyRequest (options: InternalCommonCommandOptions & {
103 fields?: { [ fieldName: string ]: any } 103 fields?: { [ fieldName: string ]: any }
104 headers?: { [name: string]: string }
104 }) { 105 }) {
105 const { fields } = options 106 const { fields, headers } = options
106 107
107 return makePutBodyRequest({ 108 return makePutBodyRequest({
108 ...this.buildCommonRequestOptions(options), 109 ...this.buildCommonRequestOptions(options),
109 110
110 fields 111 fields,
112 headers
111 }) 113 })
112 } 114 }
113 115
114 protected postBodyRequest (options: InternalCommonCommandOptions & { 116 protected postBodyRequest (options: InternalCommonCommandOptions & {
115 fields?: { [ fieldName: string ]: any } 117 fields?: { [ fieldName: string ]: any }
118 headers?: { [name: string]: string }
116 }) { 119 }) {
117 const { fields } = options 120 const { fields, headers } = options
118 121
119 return makePostBodyRequest({ 122 return makePostBodyRequest({
120 ...this.buildCommonRequestOptions(options), 123 ...this.buildCommonRequestOptions(options),
121 124
122 fields 125 fields,
126 headers
123 }) 127 })
124 } 128 }
125 129
@@ -206,6 +210,12 @@ abstract class AbstractCommand {
206 210
207 return expectedStatus !== undefined ? expectedStatus : defaultExpectedStatus 211 return expectedStatus !== undefined ? expectedStatus : defaultExpectedStatus
208 } 212 }
213
214 protected buildVideoPasswordHeader (videoPassword: string) {
215 return videoPassword !== undefined && videoPassword !== null
216 ? { 'x-peertube-video-password': videoPassword }
217 : undefined
218 }
209} 219}
210 220
211export { 221export {
diff --git a/shared/server-commands/videos/captions-command.ts b/shared/server-commands/videos/captions-command.ts
index 62bf9c5e6..a26fcb57d 100644
--- a/shared/server-commands/videos/captions-command.ts
+++ b/shared/server-commands/videos/captions-command.ts
@@ -34,14 +34,16 @@ export class CaptionsCommand extends AbstractCommand {
34 34
35 list (options: OverrideCommandOptions & { 35 list (options: OverrideCommandOptions & {
36 videoId: string | number 36 videoId: string | number
37 videoPassword?: string
37 }) { 38 }) {
38 const { videoId } = options 39 const { videoId, videoPassword } = options
39 const path = '/api/v1/videos/' + videoId + '/captions' 40 const path = '/api/v1/videos/' + videoId + '/captions'
40 41
41 return this.getRequestBody<ResultList<VideoCaption>>({ 42 return this.getRequestBody<ResultList<VideoCaption>>({
42 ...options, 43 ...options,
43 44
44 path, 45 path,
46 headers: this.buildVideoPasswordHeader(videoPassword),
45 implicitToken: false, 47 implicitToken: false,
46 defaultExpectedStatus: HttpStatusCode.OK_200 48 defaultExpectedStatus: HttpStatusCode.OK_200
47 }) 49 })
diff --git a/shared/server-commands/videos/comments-command.ts b/shared/server-commands/videos/comments-command.ts
index 154ec0c24..0dab1b66a 100644
--- a/shared/server-commands/videos/comments-command.ts
+++ b/shared/server-commands/videos/comments-command.ts
@@ -36,11 +36,12 @@ export class CommentsCommand extends AbstractCommand {
36 36
37 listThreads (options: OverrideCommandOptions & { 37 listThreads (options: OverrideCommandOptions & {
38 videoId: number | string 38 videoId: number | string
39 videoPassword?: string
39 start?: number 40 start?: number
40 count?: number 41 count?: number
41 sort?: string 42 sort?: string
42 }) { 43 }) {
43 const { start, count, sort, videoId } = options 44 const { start, count, sort, videoId, videoPassword } = options
44 const path = '/api/v1/videos/' + videoId + '/comment-threads' 45 const path = '/api/v1/videos/' + videoId + '/comment-threads'
45 46
46 return this.getRequestBody<VideoCommentThreads>({ 47 return this.getRequestBody<VideoCommentThreads>({
@@ -48,6 +49,7 @@ export class CommentsCommand extends AbstractCommand {
48 49
49 path, 50 path,
50 query: { start, count, sort }, 51 query: { start, count, sort },
52 headers: this.buildVideoPasswordHeader(videoPassword),
51 implicitToken: false, 53 implicitToken: false,
52 defaultExpectedStatus: HttpStatusCode.OK_200 54 defaultExpectedStatus: HttpStatusCode.OK_200
53 }) 55 })
@@ -72,8 +74,9 @@ export class CommentsCommand extends AbstractCommand {
72 async createThread (options: OverrideCommandOptions & { 74 async createThread (options: OverrideCommandOptions & {
73 videoId: number | string 75 videoId: number | string
74 text: string 76 text: string
77 videoPassword?: string
75 }) { 78 }) {
76 const { videoId, text } = options 79 const { videoId, text, videoPassword } = options
77 const path = '/api/v1/videos/' + videoId + '/comment-threads' 80 const path = '/api/v1/videos/' + videoId + '/comment-threads'
78 81
79 const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ 82 const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({
@@ -81,6 +84,7 @@ export class CommentsCommand extends AbstractCommand {
81 84
82 path, 85 path,
83 fields: { text }, 86 fields: { text },
87 headers: this.buildVideoPasswordHeader(videoPassword),
84 implicitToken: true, 88 implicitToken: true,
85 defaultExpectedStatus: HttpStatusCode.OK_200 89 defaultExpectedStatus: HttpStatusCode.OK_200
86 })) 90 }))
@@ -95,8 +99,9 @@ export class CommentsCommand extends AbstractCommand {
95 videoId: number | string 99 videoId: number | string
96 toCommentId: number 100 toCommentId: number
97 text: string 101 text: string
102 videoPassword?: string
98 }) { 103 }) {
99 const { videoId, toCommentId, text } = options 104 const { videoId, toCommentId, text, videoPassword } = options
100 const path = '/api/v1/videos/' + videoId + '/comments/' + toCommentId 105 const path = '/api/v1/videos/' + videoId + '/comments/' + toCommentId
101 106
102 const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ 107 const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({
@@ -104,6 +109,7 @@ export class CommentsCommand extends AbstractCommand {
104 109
105 path, 110 path,
106 fields: { text }, 111 fields: { text },
112 headers: this.buildVideoPasswordHeader(videoPassword),
107 implicitToken: true, 113 implicitToken: true,
108 defaultExpectedStatus: HttpStatusCode.OK_200 114 defaultExpectedStatus: HttpStatusCode.OK_200
109 })) 115 }))
diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts
index c17f6ef20..106d80af0 100644
--- a/shared/server-commands/videos/index.ts
+++ b/shared/server-commands/videos/index.ts
@@ -11,9 +11,11 @@ export * from './live-command'
11export * from './live' 11export * from './live'
12export * from './playlists-command' 12export * from './playlists-command'
13export * from './services-command' 13export * from './services-command'
14export * from './storyboard-command'
14export * from './streaming-playlists-command' 15export * from './streaming-playlists-command'
15export * from './comments-command' 16export * from './comments-command'
16export * from './video-studio-command' 17export * from './video-studio-command'
17export * from './video-token-command' 18export * from './video-token-command'
18export * from './views-command' 19export * from './views-command'
19export * from './videos-command' 20export * from './videos-command'
21export * from './video-passwords-command'
diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts
index 44d625970..6006d9fe9 100644
--- a/shared/server-commands/videos/live-command.ts
+++ b/shared/server-commands/videos/live-command.ts
@@ -120,8 +120,13 @@ export class LiveCommand extends AbstractCommand {
120 saveReplay: boolean 120 saveReplay: boolean
121 permanentLive: boolean 121 permanentLive: boolean
122 privacy?: VideoPrivacy 122 privacy?: VideoPrivacy
123 videoPasswords?: string[]
123 }) { 124 }) {
124 const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC } = options 125 const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options
126
127 const replaySettings = privacy === VideoPrivacy.PASSWORD_PROTECTED
128 ? { privacy: VideoPrivacy.PRIVATE }
129 : { privacy }
125 130
126 const { uuid } = await this.create({ 131 const { uuid } = await this.create({
127 ...options, 132 ...options,
@@ -130,9 +135,10 @@ export class LiveCommand extends AbstractCommand {
130 name: 'live', 135 name: 'live',
131 permanentLive, 136 permanentLive,
132 saveReplay, 137 saveReplay,
133 replaySettings: { privacy }, 138 replaySettings,
134 channelId: this.server.store.channel.id, 139 channelId: this.server.store.channel.id,
135 privacy 140 privacy,
141 videoPasswords
136 } 142 }
137 }) 143 })
138 144
diff --git a/shared/server-commands/videos/storyboard-command.ts b/shared/server-commands/videos/storyboard-command.ts
new file mode 100644
index 000000000..06d90fc12
--- /dev/null
+++ b/shared/server-commands/videos/storyboard-command.ts
@@ -0,0 +1,19 @@
1import { HttpStatusCode, Storyboard } from '@shared/models'
2import { AbstractCommand, OverrideCommandOptions } from '../shared'
3
4export class StoryboardCommand extends AbstractCommand {
5
6 list (options: OverrideCommandOptions & {
7 id: number | string
8 }) {
9 const path = '/api/v1/videos/' + options.id + '/storyboards'
10
11 return this.getRequestBody<{ storyboards: Storyboard[] }>({
12 ...options,
13
14 path,
15 implicitToken: true,
16 defaultExpectedStatus: HttpStatusCode.OK_200
17 })
18 }
19}
diff --git a/shared/server-commands/videos/video-passwords-command.ts b/shared/server-commands/videos/video-passwords-command.ts
new file mode 100644
index 000000000..bf10335b4
--- /dev/null
+++ b/shared/server-commands/videos/video-passwords-command.ts
@@ -0,0 +1,55 @@
1import { HttpStatusCode, ResultList, VideoPassword } from '@shared/models'
2import { AbstractCommand, OverrideCommandOptions } from '../shared'
3export class VideoPasswordsCommand extends AbstractCommand {
4
5 list (options: OverrideCommandOptions & {
6 videoId: number | string
7 start?: number
8 count?: number
9 sort?: string
10 }) {
11 const { start, count, sort, videoId } = options
12 const path = '/api/v1/videos/' + videoId + '/passwords'
13
14 return this.getRequestBody<ResultList<VideoPassword>>({
15 ...options,
16
17 path,
18 query: { start, count, sort },
19 implicitToken: true,
20 defaultExpectedStatus: HttpStatusCode.OK_200
21 })
22 }
23
24 updateAll (options: OverrideCommandOptions & {
25 videoId: number | string
26 passwords: string[]
27 }) {
28 const { videoId, passwords } = options
29 const path = `/api/v1/videos/${videoId}/passwords`
30
31 return this.putBodyRequest({
32 ...options,
33 path,
34 fields: { passwords },
35 implicitToken: true,
36 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
37 })
38 }
39
40 remove (options: OverrideCommandOptions & {
41 id: number
42 videoId: number | string
43 }) {
44 const { id, videoId } = options
45 const path = `/api/v1/videos/${videoId}/passwords/${id}`
46
47 return this.deleteRequest({
48 ...options,
49
50 path,
51 implicitToken: true,
52 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
53 })
54 }
55}
diff --git a/shared/server-commands/videos/video-studio-command.ts b/shared/server-commands/videos/video-studio-command.ts
index 9fe467cc2..675cd84b7 100644
--- a/shared/server-commands/videos/video-studio-command.ts
+++ b/shared/server-commands/videos/video-studio-command.ts
@@ -25,7 +25,7 @@ export class VideoStudioCommand extends AbstractCommand {
25 { 25 {
26 name: 'add-watermark', 26 name: 'add-watermark',
27 options: { 27 options: {
28 file: 'thumbnail.png' 28 file: 'custom-thumbnail.png'
29 } 29 }
30 }, 30 },
31 31
diff --git a/shared/server-commands/videos/video-token-command.ts b/shared/server-commands/videos/video-token-command.ts
index 0531bee65..c4ed29a8c 100644
--- a/shared/server-commands/videos/video-token-command.ts
+++ b/shared/server-commands/videos/video-token-command.ts
@@ -8,12 +8,14 @@ export class VideoTokenCommand extends AbstractCommand {
8 8
9 create (options: OverrideCommandOptions & { 9 create (options: OverrideCommandOptions & {
10 videoId: number | string 10 videoId: number | string
11 videoPassword?: string
11 }) { 12 }) {
12 const { videoId } = options 13 const { videoId, videoPassword } = options
13 const path = '/api/v1/videos/' + videoId + '/token' 14 const path = '/api/v1/videos/' + videoId + '/token'
14 15
15 return unwrapBody<VideoToken>(this.postBodyRequest({ 16 return unwrapBody<VideoToken>(this.postBodyRequest({
16 ...options, 17 ...options,
18 headers: this.buildVideoPasswordHeader(videoPassword),
17 19
18 path, 20 path,
19 implicitToken: true, 21 implicitToken: true,
@@ -23,6 +25,7 @@ export class VideoTokenCommand extends AbstractCommand {
23 25
24 async getVideoFileToken (options: OverrideCommandOptions & { 26 async getVideoFileToken (options: OverrideCommandOptions & {
25 videoId: number | string 27 videoId: number | string
28 videoPassword?: string
26 }) { 29 }) {
27 const { files } = await this.create(options) 30 const { files } = await this.create(options)
28 31
diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts
index b5df9c325..9602fa7da 100644
--- a/shared/server-commands/videos/videos-command.ts
+++ b/shared/server-commands/videos/videos-command.ts
@@ -111,8 +111,9 @@ export class VideosCommand extends AbstractCommand {
111 rate (options: OverrideCommandOptions & { 111 rate (options: OverrideCommandOptions & {
112 id: number | string 112 id: number | string
113 rating: UserVideoRateType 113 rating: UserVideoRateType
114 videoPassword?: string
114 }) { 115 }) {
115 const { id, rating } = options 116 const { id, rating, videoPassword } = options
116 const path = '/api/v1/videos/' + id + '/rate' 117 const path = '/api/v1/videos/' + id + '/rate'
117 118
118 return this.putBodyRequest({ 119 return this.putBodyRequest({
@@ -120,6 +121,7 @@ export class VideosCommand extends AbstractCommand {
120 121
121 path, 122 path,
122 fields: { rating }, 123 fields: { rating },
124 headers: this.buildVideoPasswordHeader(videoPassword),
123 implicitToken: true, 125 implicitToken: true,
124 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 126 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
125 }) 127 })
@@ -151,6 +153,23 @@ export class VideosCommand extends AbstractCommand {
151 }) 153 })
152 } 154 }
153 155
156 getWithPassword (options: OverrideCommandOptions & {
157 id: number | string
158 password?: string
159 }) {
160 const path = '/api/v1/videos/' + options.id
161
162 return this.getRequestBody<VideoDetails>({
163 ...options,
164 headers:{
165 'x-peertube-video-password': options.password
166 },
167 path,
168 implicitToken: false,
169 defaultExpectedStatus: HttpStatusCode.OK_200
170 })
171 }
172
154 getSource (options: OverrideCommandOptions & { 173 getSource (options: OverrideCommandOptions & {
155 id: number | string 174 id: number | string
156 }) { 175 }) {
@@ -608,11 +627,13 @@ export class VideosCommand extends AbstractCommand {
608 nsfw?: boolean 627 nsfw?: boolean
609 privacy?: VideoPrivacy 628 privacy?: VideoPrivacy
610 fixture?: string 629 fixture?: string
630 videoPasswords?: string[]
611 }) { 631 }) {
612 const attributes: VideoEdit = { name: options.name } 632 const attributes: VideoEdit = { name: options.name }
613 if (options.nsfw) attributes.nsfw = options.nsfw 633 if (options.nsfw) attributes.nsfw = options.nsfw
614 if (options.privacy) attributes.privacy = options.privacy 634 if (options.privacy) attributes.privacy = options.privacy
615 if (options.fixture) attributes.fixture = options.fixture 635 if (options.fixture) attributes.fixture = options.fixture
636 if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords
616 637
617 return this.upload({ ...options, attributes }) 638 return this.upload({ ...options, attributes })
618 } 639 }
@@ -665,10 +686,10 @@ export class VideosCommand extends AbstractCommand {
665 }) 686 })
666 } 687 }
667 688
668 removeAllWebTorrentFiles (options: OverrideCommandOptions & { 689 removeAllWebVideoFiles (options: OverrideCommandOptions & {
669 videoId: number | string 690 videoId: number | string
670 }) { 691 }) {
671 const path = '/api/v1/videos/' + options.videoId + '/webtorrent' 692 const path = '/api/v1/videos/' + options.videoId + '/web-videos'
672 693
673 return this.deleteRequest({ 694 return this.deleteRequest({
674 ...options, 695 ...options,
@@ -679,11 +700,11 @@ export class VideosCommand extends AbstractCommand {
679 }) 700 })
680 } 701 }
681 702
682 removeWebTorrentFile (options: OverrideCommandOptions & { 703 removeWebVideoFile (options: OverrideCommandOptions & {
683 videoId: number | string 704 videoId: number | string
684 fileId: number 705 fileId: number
685 }) { 706 }) {
686 const path = '/api/v1/videos/' + options.videoId + '/webtorrent/' + options.fileId 707 const path = '/api/v1/videos/' + options.videoId + '/web-videos/' + options.fileId
687 708
688 return this.deleteRequest({ 709 return this.deleteRequest({
689 ...options, 710 ...options,
@@ -696,7 +717,7 @@ export class VideosCommand extends AbstractCommand {
696 717
697 runTranscoding (options: OverrideCommandOptions & { 718 runTranscoding (options: OverrideCommandOptions & {
698 videoId: number | string 719 videoId: number | string
699 transcodingType: 'hls' | 'webtorrent' 720 transcodingType: 'hls' | 'webtorrent' | 'web-video'
700 }) { 721 }) {
701 const path = '/api/v1/videos/' + options.videoId + '/transcoding' 722 const path = '/api/v1/videos/' + options.videoId + '/transcoding'
702 723