aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/i18n/i18n.ts2
-rw-r--r--shared/extra-utils/ffprobe.ts96
-rw-r--r--shared/models/activitypub/activitypub-actor.ts7
-rw-r--r--shared/models/actors/account.model.ts7
-rw-r--r--shared/models/actors/actor-image.model.ts1
-rw-r--r--shared/models/actors/actor.model.ts6
-rw-r--r--shared/models/plugins/server/server-hook.model.ts5
-rw-r--r--shared/models/server/custom-config.model.ts4
-rw-r--r--shared/models/server/job.model.ts39
-rw-r--r--shared/models/server/server-config.model.ts4
-rw-r--r--shared/models/users/user-notification.model.ts11
-rw-r--r--shared/models/videos/channel/video-channel.model.ts13
-rw-r--r--shared/models/videos/editor/index.ts1
-rw-r--r--shared/models/videos/editor/video-editor-create-edit.model.ts42
-rw-r--r--shared/models/videos/index.ts1
-rw-r--r--shared/models/videos/transcoding/video-transcoding-fps.model.ts1
-rw-r--r--shared/models/videos/transcoding/video-transcoding.model.ts7
-rw-r--r--shared/models/videos/video-state.enum.ts3
-rw-r--r--shared/server-commands/server/config-command.ts34
-rw-r--r--shared/server-commands/server/server.ts3
-rw-r--r--shared/server-commands/server/servers-command.ts17
-rw-r--r--shared/server-commands/users/accounts.ts15
-rw-r--r--shared/server-commands/users/index.ts1
-rw-r--r--shared/server-commands/videos/channels.ts13
-rw-r--r--shared/server-commands/videos/index.ts1
-rw-r--r--shared/server-commands/videos/video-editor-command.ts67
26 files changed, 325 insertions, 76 deletions
diff --git a/shared/core-utils/i18n/i18n.ts b/shared/core-utils/i18n/i18n.ts
index ae6af8192..f0fcb5af7 100644
--- a/shared/core-utils/i18n/i18n.ts
+++ b/shared/core-utils/i18n/i18n.ts
@@ -17,6 +17,7 @@ export const I18N_LOCALES = {
17 'gd': 'Gàidhlig', 17 'gd': 'Gàidhlig',
18 'gl-ES': 'galego', 18 'gl-ES': 'galego',
19 'hu-HU': 'magyar', 19 'hu-HU': 'magyar',
20 'fa-IR': 'فارسی',
20 'it-IT': 'Italiano', 21 'it-IT': 'Italiano',
21 'ja-JP': '日本語', 22 'ja-JP': '日本語',
22 'kab': 'Taqbaylit', 23 'kab': 'Taqbaylit',
@@ -47,6 +48,7 @@ const I18N_LOCALE_ALIAS = {
47 'eu': 'eu-ES', 48 'eu': 'eu-ES',
48 'fi': 'fi-FI', 49 'fi': 'fi-FI',
49 'gl': 'gl-ES', 50 'gl': 'gl-ES',
51 'fa': 'fa-IR',
50 'fr': 'fr-FR', 52 'fr': 'fr-FR',
51 'hu': 'hu-HU', 53 'hu': 'hu-HU',
52 'it': 'it-IT', 54 'it': 'it-IT',
diff --git a/shared/extra-utils/ffprobe.ts b/shared/extra-utils/ffprobe.ts
index 53a3aa001..dfacd251c 100644
--- a/shared/extra-utils/ffprobe.ts
+++ b/shared/extra-utils/ffprobe.ts
@@ -17,12 +17,22 @@ function ffprobePromise (path: string) {
17 }) 17 })
18} 18}
19 19
20// ---------------------------------------------------------------------------
21// Audio
22// ---------------------------------------------------------------------------
23
20async function isAudioFile (path: string, existingProbe?: FfprobeData) { 24async function isAudioFile (path: string, existingProbe?: FfprobeData) {
21 const videoStream = await getVideoStreamFromFile(path, existingProbe) 25 const videoStream = await getVideoStream(path, existingProbe)
22 26
23 return !videoStream 27 return !videoStream
24} 28}
25 29
30async function hasAudioStream (path: string, existingProbe?: FfprobeData) {
31 const { audioStream } = await getAudioStream(path, existingProbe)
32
33 return !!audioStream
34}
35
26async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) { 36async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
27 // without position, ffprobe considers the last input only 37 // without position, ffprobe considers the last input only
28 // we make it consider the first input only 38 // we make it consider the first input only
@@ -78,29 +88,26 @@ function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) {
78 } 88 }
79} 89}
80 90
81async function getVideoStreamSize (path: string, existingProbe?: FfprobeData): Promise<{ width: number, height: number }> { 91// ---------------------------------------------------------------------------
82 const videoStream = await getVideoStreamFromFile(path, existingProbe) 92// Video
83 93// ---------------------------------------------------------------------------
84 return videoStream === null
85 ? { width: 0, height: 0 }
86 : { width: videoStream.width, height: videoStream.height }
87}
88 94
89async function getVideoFileResolution (path: string, existingProbe?: FfprobeData) { 95async function getVideoStreamDimensionsInfo (path: string, existingProbe?: FfprobeData) {
90 const size = await getVideoStreamSize(path, existingProbe) 96 const videoStream = await getVideoStream(path, existingProbe)
97 if (!videoStream) return undefined
91 98
92 return { 99 return {
93 width: size.width, 100 width: videoStream.width,
94 height: size.height, 101 height: videoStream.height,
95 ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width), 102 ratio: Math.max(videoStream.height, videoStream.width) / Math.min(videoStream.height, videoStream.width),
96 resolution: Math.min(size.height, size.width), 103 resolution: Math.min(videoStream.height, videoStream.width),
97 isPortraitMode: size.height > size.width 104 isPortraitMode: videoStream.height > videoStream.width
98 } 105 }
99} 106}
100 107
101async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) { 108async function getVideoStreamFPS (path: string, existingProbe?: FfprobeData) {
102 const videoStream = await getVideoStreamFromFile(path, existingProbe) 109 const videoStream = await getVideoStream(path, existingProbe)
103 if (videoStream === null) return 0 110 if (!videoStream) return 0
104 111
105 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { 112 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
106 const valuesText: string = videoStream[key] 113 const valuesText: string = videoStream[key]
@@ -116,19 +123,19 @@ async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) {
116 return 0 123 return 0
117} 124}
118 125
119async function getMetadataFromFile (path: string, existingProbe?: FfprobeData) { 126async function buildFileMetadata (path: string, existingProbe?: FfprobeData) {
120 const metadata = existingProbe || await ffprobePromise(path) 127 const metadata = existingProbe || await ffprobePromise(path)
121 128
122 return new VideoFileMetadata(metadata) 129 return new VideoFileMetadata(metadata)
123} 130}
124 131
125async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData): Promise<number> { 132async function getVideoStreamBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
126 const metadata = await getMetadataFromFile(path, existingProbe) 133 const metadata = await buildFileMetadata(path, existingProbe)
127 134
128 let bitrate = metadata.format.bit_rate as number 135 let bitrate = metadata.format.bit_rate as number
129 if (bitrate && !isNaN(bitrate)) return bitrate 136 if (bitrate && !isNaN(bitrate)) return bitrate
130 137
131 const videoStream = await getVideoStreamFromFile(path, existingProbe) 138 const videoStream = await getVideoStream(path, existingProbe)
132 if (!videoStream) return undefined 139 if (!videoStream) return undefined
133 140
134 bitrate = videoStream?.bit_rate 141 bitrate = videoStream?.bit_rate
@@ -137,51 +144,30 @@ async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData):
137 return undefined 144 return undefined
138} 145}
139 146
140async function getDurationFromVideoFile (path: string, existingProbe?: FfprobeData) { 147async function getVideoStreamDuration (path: string, existingProbe?: FfprobeData) {
141 const metadata = await getMetadataFromFile(path, existingProbe) 148 const metadata = await buildFileMetadata(path, existingProbe)
142 149
143 return Math.round(metadata.format.duration) 150 return Math.round(metadata.format.duration)
144} 151}
145 152
146async function getVideoStreamFromFile (path: string, existingProbe?: FfprobeData) { 153async function getVideoStream (path: string, existingProbe?: FfprobeData) {
147 const metadata = await getMetadataFromFile(path, existingProbe) 154 const metadata = await buildFileMetadata(path, existingProbe)
148
149 return metadata.streams.find(s => s.codec_type === 'video') || null
150}
151
152async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
153 const parsedAudio = await getAudioStream(path, probe)
154
155 if (!parsedAudio.audioStream) return true
156
157 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
158
159 const audioBitrate = parsedAudio.bitrate
160 if (!audioBitrate) return false
161
162 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
163 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
164
165 const channelLayout = parsedAudio.audioStream['channel_layout']
166 // Causes playback issues with Chrome
167 if (!channelLayout || channelLayout === 'unknown') return false
168 155
169 return true 156 return metadata.streams.find(s => s.codec_type === 'video')
170} 157}
171 158
172// --------------------------------------------------------------------------- 159// ---------------------------------------------------------------------------
173 160
174export { 161export {
175 getVideoStreamSize, 162 getVideoStreamDimensionsInfo,
176 getVideoFileResolution, 163 buildFileMetadata,
177 getMetadataFromFile,
178 getMaxAudioBitrate, 164 getMaxAudioBitrate,
179 getVideoStreamFromFile, 165 getVideoStream,
180 getDurationFromVideoFile, 166 getVideoStreamDuration,
181 getAudioStream, 167 getAudioStream,
182 getVideoFileFPS, 168 getVideoStreamFPS,
183 isAudioFile, 169 isAudioFile,
184 ffprobePromise, 170 ffprobePromise,
185 getVideoFileBitrate, 171 getVideoStreamBitrate,
186 canDoQuickAudioTranscode 172 hasAudioStream
187} 173}
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index 09d4f7402..efb6edec4 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -27,8 +27,11 @@ export interface ActivityPubActor {
27 publicKeyPem: string 27 publicKeyPem: string
28 } 28 }
29 29
30 icon?: ActivityIconObject 30 image?: ActivityIconObject | ActivityIconObject[]
31 image?: ActivityIconObject 31
32 icon?: ActivityIconObject | ActivityIconObject[]
33 // TODO: migrate to `icon`, introduced in 4.2
34 icons?: ActivityIconObject[]
32 35
33 published?: string 36 published?: string
34} 37}
diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts
index f2138077e..60f4236d5 100644
--- a/shared/models/actors/account.model.ts
+++ b/shared/models/actors/account.model.ts
@@ -4,6 +4,7 @@ import { Actor } from './actor.model'
4export interface Account extends Actor { 4export interface Account extends Actor {
5 displayName: string 5 displayName: string
6 description: string 6 description: string
7 avatars: ActorImage[]
7 8
8 updatedAt: Date | string 9 updatedAt: Date | string
9 10
@@ -16,5 +17,9 @@ export interface AccountSummary {
16 displayName: string 17 displayName: string
17 url: string 18 url: string
18 host: string 19 host: string
19 avatar?: ActorImage 20
21 avatars: ActorImage[]
22
23 // TODO: remove, deprecated in 4.2
24 avatar: ActorImage
20} 25}
diff --git a/shared/models/actors/actor-image.model.ts b/shared/models/actors/actor-image.model.ts
index ad5eab627..cfe44ac15 100644
--- a/shared/models/actors/actor-image.model.ts
+++ b/shared/models/actors/actor-image.model.ts
@@ -1,4 +1,5 @@
1export interface ActorImage { 1export interface ActorImage {
2 width: number
2 path: string 3 path: string
3 4
4 url?: string 5 url?: string
diff --git a/shared/models/actors/actor.model.ts b/shared/models/actors/actor.model.ts
index fd0662331..bf86a917f 100644
--- a/shared/models/actors/actor.model.ts
+++ b/shared/models/actors/actor.model.ts
@@ -8,5 +8,9 @@ export interface Actor {
8 followingCount: number 8 followingCount: number
9 followersCount: number 9 followersCount: number
10 createdAt: Date | string 10 createdAt: Date | string
11 avatar?: ActorImage 11
12 avatars: ActorImage[]
13
14 // TODO: remove, deprecated in 4.2
15 avatar: ActorImage
12} 16}
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index bd2b27da5..e64c3bbbc 100644
--- a/shared/models/plugins/server/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
@@ -6,6 +6,11 @@ export const serverFilterHookObject = {
6 'filter:api.videos.list.params': true, 6 'filter:api.videos.list.params': true,
7 'filter:api.videos.list.result': true, 7 'filter:api.videos.list.result': true,
8 8
9 // Filter params/result used to list a video playlists videos
10 // for the REST API
11 'filter:api.video-playlist.videos.list.params': true,
12 'filter:api.video-playlist.videos.list.result': true,
13
9 // Filter params/result used to list account videos for the REST API 14 // Filter params/result used to list account videos for the REST API
10 'filter:api.accounts.videos.list.params': true, 15 'filter:api.accounts.videos.list.params': true,
11 'filter:api.accounts.videos.list.result': true, 16 'filter:api.accounts.videos.list.result': true,
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 52d3d9588..c9e7654de 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -143,6 +143,10 @@ export interface CustomConfig {
143 } 143 }
144 } 144 }
145 145
146 videoEditor: {
147 enabled: boolean
148 }
149
146 import: { 150 import: {
147 videos: { 151 videos: {
148 concurrency: number 152 concurrency: number
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index 1519d1c3e..d0293f542 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -1,4 +1,5 @@
1import { ContextType } from '../activitypub/context' 1import { ContextType } from '../activitypub/context'
2import { VideoEditorTaskCut } from '../videos/editor'
2import { VideoResolution } from '../videos/file/video-resolution.enum' 3import { VideoResolution } from '../videos/file/video-resolution.enum'
3import { SendEmailOptions } from './emailer.model' 4import { SendEmailOptions } from './emailer.model'
4 5
@@ -20,6 +21,7 @@ export type JobType =
20 | 'video-live-ending' 21 | 'video-live-ending'
21 | 'actor-keys' 22 | 'actor-keys'
22 | 'move-to-object-storage' 23 | 'move-to-object-storage'
24 | 'video-edition'
23 25
24export interface Job { 26export interface Job {
25 id: number 27 id: number
@@ -155,3 +157,40 @@ export interface MoveObjectStoragePayload {
155 videoUUID: string 157 videoUUID: string
156 isNewVideo: boolean 158 isNewVideo: boolean
157} 159}
160
161export type VideoEditorTaskCutPayload = VideoEditorTaskCut
162
163export type VideoEditorTaskIntroPayload = {
164 name: 'add-intro'
165
166 options: {
167 file: string
168 }
169}
170
171export type VideoEditorTaskOutroPayload = {
172 name: 'add-outro'
173
174 options: {
175 file: string
176 }
177}
178
179export type VideoEditorTaskWatermarkPayload = {
180 name: 'add-watermark'
181
182 options: {
183 file: string
184 }
185}
186
187export type VideoEditionTaskPayload =
188 VideoEditorTaskCutPayload |
189 VideoEditorTaskIntroPayload |
190 VideoEditorTaskOutroPayload |
191 VideoEditorTaskWatermarkPayload
192
193export interface VideoEditionPayload {
194 videoUUID: string
195 tasks: VideoEditionTaskPayload[]
196}
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index 32be96b9d..0fe8b0de8 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -167,6 +167,10 @@ export interface ServerConfig {
167 } 167 }
168 } 168 }
169 169
170 videoEditor: {
171 enabled: boolean
172 }
173
170 import: { 174 import: {
171 videos: { 175 videos: {
172 http: { 176 http: {
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
index 5820589fe..a2621fb5b 100644
--- a/shared/models/users/user-notification.model.ts
+++ b/shared/models/users/user-notification.model.ts
@@ -40,14 +40,19 @@ export interface VideoInfo {
40 name: string 40 name: string
41} 41}
42 42
43export interface AvatarInfo {
44 width: number
45 path: string
46}
47
43export interface ActorInfo { 48export interface ActorInfo {
44 id: number 49 id: number
45 displayName: string 50 displayName: string
46 name: string 51 name: string
47 host: string 52 host: string
48 avatar?: { 53
49 path: string 54 avatars: AvatarInfo[]
50 } 55 avatar: AvatarInfo
51} 56}
52 57
53export interface UserNotification { 58export interface UserNotification {
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index 5393f924d..58b60c177 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -1,5 +1,5 @@
1import { Actor } from '../../actors/actor.model'
2import { Account, ActorImage } from '../../actors' 1import { Account, ActorImage } from '../../actors'
2import { Actor } from '../../actors/actor.model'
3 3
4export type ViewsPerDate = { 4export type ViewsPerDate = {
5 date: Date 5 date: Date
@@ -19,7 +19,10 @@ export interface VideoChannel extends Actor {
19 videosCount?: number 19 videosCount?: number
20 viewsPerDay?: ViewsPerDate[] // chronologically ordered 20 viewsPerDay?: ViewsPerDate[] // chronologically ordered
21 21
22 banner?: ActorImage 22 banners: ActorImage[]
23
24 // TODO: remove, deprecated in 4.2
25 banner: ActorImage
23} 26}
24 27
25export interface VideoChannelSummary { 28export interface VideoChannelSummary {
@@ -28,5 +31,9 @@ export interface VideoChannelSummary {
28 displayName: string 31 displayName: string
29 url: string 32 url: string
30 host: string 33 host: string
31 avatar?: ActorImage 34
35 avatars: ActorImage[]
36
37 // TODO: remove, deprecated in 4.2
38 avatar: ActorImage
32} 39}
diff --git a/shared/models/videos/editor/index.ts b/shared/models/videos/editor/index.ts
new file mode 100644
index 000000000..3436f2c3f
--- /dev/null
+++ b/shared/models/videos/editor/index.ts
@@ -0,0 +1 @@
export * from './video-editor-create-edit.model'
diff --git a/shared/models/videos/editor/video-editor-create-edit.model.ts b/shared/models/videos/editor/video-editor-create-edit.model.ts
new file mode 100644
index 000000000..36b7c8d55
--- /dev/null
+++ b/shared/models/videos/editor/video-editor-create-edit.model.ts
@@ -0,0 +1,42 @@
1export interface VideoEditorCreateEdition {
2 tasks: VideoEditorTask[]
3}
4
5export type VideoEditorTask =
6 VideoEditorTaskCut |
7 VideoEditorTaskIntro |
8 VideoEditorTaskOutro |
9 VideoEditorTaskWatermark
10
11export interface VideoEditorTaskCut {
12 name: 'cut'
13
14 options: {
15 start?: number
16 end?: number
17 }
18}
19
20export interface VideoEditorTaskIntro {
21 name: 'add-intro'
22
23 options: {
24 file: Blob | string
25 }
26}
27
28export interface VideoEditorTaskOutro {
29 name: 'add-outro'
30
31 options: {
32 file: Blob | string
33 }
34}
35
36export interface VideoEditorTaskWatermark {
37 name: 'add-watermark'
38
39 options: {
40 file: Blob | string
41 }
42}
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts
index 67614efc9..e8eb227ab 100644
--- a/shared/models/videos/index.ts
+++ b/shared/models/videos/index.ts
@@ -3,6 +3,7 @@ export * from './caption'
3export * from './change-ownership' 3export * from './change-ownership'
4export * from './channel' 4export * from './channel'
5export * from './comment' 5export * from './comment'
6export * from './editor'
6export * from './live' 7export * from './live'
7export * from './file' 8export * from './file'
8export * from './import' 9export * from './import'
diff --git a/shared/models/videos/transcoding/video-transcoding-fps.model.ts b/shared/models/videos/transcoding/video-transcoding-fps.model.ts
index 25fc1c2da..9a330ac94 100644
--- a/shared/models/videos/transcoding/video-transcoding-fps.model.ts
+++ b/shared/models/videos/transcoding/video-transcoding-fps.model.ts
@@ -2,6 +2,7 @@ export type VideoTranscodingFPS = {
2 MIN: number 2 MIN: number
3 STANDARD: number[] 3 STANDARD: number[]
4 HD_STANDARD: number[] 4 HD_STANDARD: number[]
5 AUDIO_MERGE: number
5 AVERAGE: number 6 AVERAGE: number
6 MAX: number 7 MAX: number
7 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number 8 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number
diff --git a/shared/models/videos/transcoding/video-transcoding.model.ts b/shared/models/videos/transcoding/video-transcoding.model.ts
index 3a7fb6472..91eacf8dc 100644
--- a/shared/models/videos/transcoding/video-transcoding.model.ts
+++ b/shared/models/videos/transcoding/video-transcoding.model.ts
@@ -7,8 +7,11 @@ export type EncoderOptionsBuilderParams = {
7 7
8 resolution: VideoResolution 8 resolution: VideoResolution
9 9
10 // Could be null for "merge audio" transcoding 10 // If PeerTube applies a filter, transcoding profile must not copy input stream
11 fps?: number 11 canCopyAudio: boolean
12 canCopyVideo: boolean
13
14 fps: number
12 15
13 // Could be undefined if we could not get input bitrate (some RTMP streams for example) 16 // Could be undefined if we could not get input bitrate (some RTMP streams for example)
14 inputBitrate: number 17 inputBitrate: number
diff --git a/shared/models/videos/video-state.enum.ts b/shared/models/videos/video-state.enum.ts
index 09268d2ff..e45e4adc2 100644
--- a/shared/models/videos/video-state.enum.ts
+++ b/shared/models/videos/video-state.enum.ts
@@ -6,5 +6,6 @@ export const enum VideoState {
6 LIVE_ENDED = 5, 6 LIVE_ENDED = 5,
7 TO_MOVE_TO_EXTERNAL_STORAGE = 6, 7 TO_MOVE_TO_EXTERNAL_STORAGE = 6,
8 TRANSCODING_FAILED = 7, 8 TRANSCODING_FAILED = 7,
9 TO_MOVE_TO_EXTERNAL_STORAGE_FAILED = 8 9 TO_MOVE_TO_EXTERNAL_STORAGE_FAILED = 8,
10 TO_EDIT = 9
10} 11}
diff --git a/shared/server-commands/server/config-command.ts b/shared/server-commands/server/config-command.ts
index 797231b1d..c0042060b 100644
--- a/shared/server-commands/server/config-command.ts
+++ b/shared/server-commands/server/config-command.ts
@@ -59,6 +59,9 @@ export class ConfigCommand extends AbstractCommand {
59 newConfig: { 59 newConfig: {
60 transcoding: { 60 transcoding: {
61 enabled: false 61 enabled: false
62 },
63 videoEditor: {
64 enabled: false
62 } 65 }
63 } 66 }
64 }) 67 })
@@ -69,6 +72,10 @@ export class ConfigCommand extends AbstractCommand {
69 newConfig: { 72 newConfig: {
70 transcoding: { 73 transcoding: {
71 enabled: true, 74 enabled: true,
75
76 allowAudioFiles: true,
77 allowAdditionalExtensions: true,
78
72 resolutions: ConfigCommand.getCustomConfigResolutions(true), 79 resolutions: ConfigCommand.getCustomConfigResolutions(true),
73 80
74 webtorrent: { 81 webtorrent: {
@@ -82,6 +89,28 @@ export class ConfigCommand extends AbstractCommand {
82 }) 89 })
83 } 90 }
84 91
92 enableMinimumTranscoding (webtorrent = true, hls = true) {
93 return this.updateExistingSubConfig({
94 newConfig: {
95 transcoding: {
96 enabled: true,
97 resolutions: {
98 ...ConfigCommand.getCustomConfigResolutions(false),
99
100 '240p': true
101 },
102
103 webtorrent: {
104 enabled: webtorrent
105 },
106 hls: {
107 enabled: hls
108 }
109 }
110 }
111 })
112 }
113
85 getConfig (options: OverrideCommandOptions = {}) { 114 getConfig (options: OverrideCommandOptions = {}) {
86 const path = '/api/v1/config' 115 const path = '/api/v1/config'
87 116
@@ -148,7 +177,7 @@ export class ConfigCommand extends AbstractCommand {
148 async updateExistingSubConfig (options: OverrideCommandOptions & { 177 async updateExistingSubConfig (options: OverrideCommandOptions & {
149 newConfig: DeepPartial<CustomConfig> 178 newConfig: DeepPartial<CustomConfig>
150 }) { 179 }) {
151 const existing = await this.getCustomConfig(options) 180 const existing = await this.getCustomConfig({ ...options, expectedStatus: HttpStatusCode.OK_200 })
152 181
153 return this.updateCustomConfig({ ...options, newCustomConfig: merge({}, existing, options.newConfig) }) 182 return this.updateCustomConfig({ ...options, newCustomConfig: merge({}, existing, options.newConfig) })
154 } 183 }
@@ -282,6 +311,9 @@ export class ConfigCommand extends AbstractCommand {
282 } 311 }
283 } 312 }
284 }, 313 },
314 videoEditor: {
315 enabled: false
316 },
285 import: { 317 import: {
286 videos: { 318 videos: {
287 concurrency: 3, 319 concurrency: 3,
diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts
index da89fd876..af4423e8d 100644
--- a/shared/server-commands/server/server.ts
+++ b/shared/server-commands/server/server.ts
@@ -25,6 +25,7 @@ import {
25 PlaylistsCommand, 25 PlaylistsCommand,
26 ServicesCommand, 26 ServicesCommand,
27 StreamingPlaylistsCommand, 27 StreamingPlaylistsCommand,
28 VideoEditorCommand,
28 VideosCommand 29 VideosCommand
29} from '../videos' 30} from '../videos'
30import { CommentsCommand } from '../videos/comments-command' 31import { CommentsCommand } from '../videos/comments-command'
@@ -124,6 +125,7 @@ export class PeerTubeServer {
124 login?: LoginCommand 125 login?: LoginCommand
125 users?: UsersCommand 126 users?: UsersCommand
126 objectStorage?: ObjectStorageCommand 127 objectStorage?: ObjectStorageCommand
128 videoEditor?: VideoEditorCommand
127 videos?: VideosCommand 129 videos?: VideosCommand
128 130
129 constructor (options: { serverNumber: number } | { url: string }) { 131 constructor (options: { serverNumber: number } | { url: string }) {
@@ -394,5 +396,6 @@ export class PeerTubeServer {
394 this.users = new UsersCommand(this) 396 this.users = new UsersCommand(this)
395 this.videos = new VideosCommand(this) 397 this.videos = new VideosCommand(this)
396 this.objectStorage = new ObjectStorageCommand(this) 398 this.objectStorage = new ObjectStorageCommand(this)
399 this.videoEditor = new VideoEditorCommand(this)
397 } 400 }
398} 401}
diff --git a/shared/server-commands/server/servers-command.ts b/shared/server-commands/server/servers-command.ts
index c5d8d18dc..19645cb93 100644
--- a/shared/server-commands/server/servers-command.ts
+++ b/shared/server-commands/server/servers-command.ts
@@ -30,10 +30,12 @@ export class ServersCommand extends AbstractCommand {
30 }) 30 })
31 } 31 }
32 32
33 async cleanupTests () { 33 cleanupTests () {
34 const p: Promise<any>[] = [] 34 const promises: Promise<any>[] = []
35
36 const saveGithubLogsIfNeeded = async () => {
37 if (!isGithubCI()) return
35 38
36 if (isGithubCI()) {
37 await ensureDir('artifacts') 39 await ensureDir('artifacts')
38 40
39 const origin = this.buildDirectory('logs/peertube.log') 41 const origin = this.buildDirectory('logs/peertube.log')
@@ -44,14 +46,17 @@ export class ServersCommand extends AbstractCommand {
44 } 46 }
45 47
46 if (this.server.parallel) { 48 if (this.server.parallel) {
47 p.push(ServersCommand.flushTests(this.server.internalServerNumber)) 49 const promise = saveGithubLogsIfNeeded()
50 .then(() => ServersCommand.flushTests(this.server.internalServerNumber))
51
52 promises.push(promise)
48 } 53 }
49 54
50 if (this.server.customConfigFile) { 55 if (this.server.customConfigFile) {
51 p.push(remove(this.server.customConfigFile)) 56 promises.push(remove(this.server.customConfigFile))
52 } 57 }
53 58
54 return p 59 return promises
55 } 60 }
56 61
57 async waitUntilLog (str: string, count = 1, strictCount = true) { 62 async waitUntilLog (str: string, count = 1, strictCount = true) {
diff --git a/shared/server-commands/users/accounts.ts b/shared/server-commands/users/accounts.ts
new file mode 100644
index 000000000..6387891f4
--- /dev/null
+++ b/shared/server-commands/users/accounts.ts
@@ -0,0 +1,15 @@
1import { PeerTubeServer } from '../server/server'
2
3async function setDefaultAccountAvatar (serversArg: PeerTubeServer | PeerTubeServer[], token?: string) {
4 const servers = Array.isArray(serversArg)
5 ? serversArg
6 : [ serversArg ]
7
8 for (const server of servers) {
9 await server.users.updateMyAvatar({ fixture: 'avatar.png', token })
10 }
11}
12
13export {
14 setDefaultAccountAvatar
15}
diff --git a/shared/server-commands/users/index.ts b/shared/server-commands/users/index.ts
index c2bc5c44f..f6f93b4d2 100644
--- a/shared/server-commands/users/index.ts
+++ b/shared/server-commands/users/index.ts
@@ -1,4 +1,5 @@
1export * from './accounts-command' 1export * from './accounts-command'
2export * from './accounts'
2export * from './blocklist-command' 3export * from './blocklist-command'
3export * from './login' 4export * from './login'
4export * from './login-command' 5export * from './login-command'
diff --git a/shared/server-commands/videos/channels.ts b/shared/server-commands/videos/channels.ts
index 756c47453..3c0d4b723 100644
--- a/shared/server-commands/videos/channels.ts
+++ b/shared/server-commands/videos/channels.ts
@@ -13,6 +13,17 @@ function setDefaultVideoChannel (servers: PeerTubeServer[]) {
13 return Promise.all(tasks) 13 return Promise.all(tasks)
14} 14}
15 15
16async function setDefaultChannelAvatar (serversArg: PeerTubeServer | PeerTubeServer[], channelName: string = 'root_channel') {
17 const servers = Array.isArray(serversArg)
18 ? serversArg
19 : [ serversArg ]
20
21 for (const server of servers) {
22 await server.channels.updateImage({ channelName, fixture: 'avatar.png', type: 'avatar' })
23 }
24}
25
16export { 26export {
17 setDefaultVideoChannel 27 setDefaultVideoChannel,
28 setDefaultChannelAvatar
18} 29}
diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts
index 68a188b21..154aed9a6 100644
--- a/shared/server-commands/videos/index.ts
+++ b/shared/server-commands/videos/index.ts
@@ -12,4 +12,5 @@ export * from './playlists-command'
12export * from './services-command' 12export * from './services-command'
13export * from './streaming-playlists-command' 13export * from './streaming-playlists-command'
14export * from './comments-command' 14export * from './comments-command'
15export * from './video-editor-command'
15export * from './videos-command' 16export * from './videos-command'
diff --git a/shared/server-commands/videos/video-editor-command.ts b/shared/server-commands/videos/video-editor-command.ts
new file mode 100644
index 000000000..485edce8e
--- /dev/null
+++ b/shared/server-commands/videos/video-editor-command.ts
@@ -0,0 +1,67 @@
1import { HttpStatusCode, VideoEditorTask } from '@shared/models'
2import { AbstractCommand, OverrideCommandOptions } from '../shared'
3
4export class VideoEditorCommand extends AbstractCommand {
5
6 static getComplexTask (): VideoEditorTask[] {
7 return [
8 // Total duration: 2
9 {
10 name: 'cut',
11 options: {
12 start: 1,
13 end: 3
14 }
15 },
16
17 // Total duration: 7
18 {
19 name: 'add-outro',
20 options: {
21 file: 'video_short.webm'
22 }
23 },
24
25 {
26 name: 'add-watermark',
27 options: {
28 file: 'thumbnail.png'
29 }
30 },
31
32 // Total duration: 9
33 {
34 name: 'add-intro',
35 options: {
36 file: 'video_very_short_240p.mp4'
37 }
38 }
39 ]
40 }
41
42 createEditionTasks (options: OverrideCommandOptions & {
43 videoId: number | string
44 tasks: VideoEditorTask[]
45 }) {
46 const path = '/api/v1/videos/' + options.videoId + '/editor/edit'
47 const attaches: { [id: string]: any } = {}
48
49 for (let i = 0; i < options.tasks.length; i++) {
50 const task = options.tasks[i]
51
52 if (task.name === 'add-intro' || task.name === 'add-outro' || task.name === 'add-watermark') {
53 attaches[`tasks[${i}][options][file]`] = task.options.file
54 }
55 }
56
57 return this.postUploadRequest({
58 ...options,
59
60 path,
61 attaches,
62 fields: { tasks: options.tasks },
63 implicitToken: true,
64 defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
65 })
66 }
67}