diff options
Diffstat (limited to 'shared')
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 | |||
20 | async function isAudioFile (path: string, existingProbe?: FfprobeData) { | 24 | async 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 | ||
30 | async function hasAudioStream (path: string, existingProbe?: FfprobeData) { | ||
31 | const { audioStream } = await getAudioStream(path, existingProbe) | ||
32 | |||
33 | return !!audioStream | ||
34 | } | ||
35 | |||
26 | async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) { | 36 | async 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 | ||
81 | async 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 | ||
89 | async function getVideoFileResolution (path: string, existingProbe?: FfprobeData) { | 95 | async 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 | ||
101 | async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) { | 108 | async 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 | ||
119 | async function getMetadataFromFile (path: string, existingProbe?: FfprobeData) { | 126 | async 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 | ||
125 | async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData): Promise<number> { | 132 | async 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 | ||
140 | async function getDurationFromVideoFile (path: string, existingProbe?: FfprobeData) { | 147 | async 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 | ||
146 | async function getVideoStreamFromFile (path: string, existingProbe?: FfprobeData) { | 153 | async 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 | |||
152 | async 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 | ||
174 | export { | 161 | export { |
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' | |||
4 | export interface Account extends Actor { | 4 | export 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 @@ | |||
1 | export interface ActorImage { | 1 | export 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 @@ | |||
1 | import { ContextType } from '../activitypub/context' | 1 | import { ContextType } from '../activitypub/context' |
2 | import { VideoEditorTaskCut } from '../videos/editor' | ||
2 | import { VideoResolution } from '../videos/file/video-resolution.enum' | 3 | import { VideoResolution } from '../videos/file/video-resolution.enum' |
3 | import { SendEmailOptions } from './emailer.model' | 4 | import { 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 | ||
24 | export interface Job { | 26 | export 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 | |||
161 | export type VideoEditorTaskCutPayload = VideoEditorTaskCut | ||
162 | |||
163 | export type VideoEditorTaskIntroPayload = { | ||
164 | name: 'add-intro' | ||
165 | |||
166 | options: { | ||
167 | file: string | ||
168 | } | ||
169 | } | ||
170 | |||
171 | export type VideoEditorTaskOutroPayload = { | ||
172 | name: 'add-outro' | ||
173 | |||
174 | options: { | ||
175 | file: string | ||
176 | } | ||
177 | } | ||
178 | |||
179 | export type VideoEditorTaskWatermarkPayload = { | ||
180 | name: 'add-watermark' | ||
181 | |||
182 | options: { | ||
183 | file: string | ||
184 | } | ||
185 | } | ||
186 | |||
187 | export type VideoEditionTaskPayload = | ||
188 | VideoEditorTaskCutPayload | | ||
189 | VideoEditorTaskIntroPayload | | ||
190 | VideoEditorTaskOutroPayload | | ||
191 | VideoEditorTaskWatermarkPayload | ||
192 | |||
193 | export 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 | ||
43 | export interface AvatarInfo { | ||
44 | width: number | ||
45 | path: string | ||
46 | } | ||
47 | |||
43 | export interface ActorInfo { | 48 | export 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 | ||
53 | export interface UserNotification { | 58 | export 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 @@ | |||
1 | import { Actor } from '../../actors/actor.model' | ||
2 | import { Account, ActorImage } from '../../actors' | 1 | import { Account, ActorImage } from '../../actors' |
2 | import { Actor } from '../../actors/actor.model' | ||
3 | 3 | ||
4 | export type ViewsPerDate = { | 4 | export 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 | ||
25 | export interface VideoChannelSummary { | 28 | export 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 @@ | |||
1 | export interface VideoEditorCreateEdition { | ||
2 | tasks: VideoEditorTask[] | ||
3 | } | ||
4 | |||
5 | export type VideoEditorTask = | ||
6 | VideoEditorTaskCut | | ||
7 | VideoEditorTaskIntro | | ||
8 | VideoEditorTaskOutro | | ||
9 | VideoEditorTaskWatermark | ||
10 | |||
11 | export interface VideoEditorTaskCut { | ||
12 | name: 'cut' | ||
13 | |||
14 | options: { | ||
15 | start?: number | ||
16 | end?: number | ||
17 | } | ||
18 | } | ||
19 | |||
20 | export interface VideoEditorTaskIntro { | ||
21 | name: 'add-intro' | ||
22 | |||
23 | options: { | ||
24 | file: Blob | string | ||
25 | } | ||
26 | } | ||
27 | |||
28 | export interface VideoEditorTaskOutro { | ||
29 | name: 'add-outro' | ||
30 | |||
31 | options: { | ||
32 | file: Blob | string | ||
33 | } | ||
34 | } | ||
35 | |||
36 | export 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' | |||
3 | export * from './change-ownership' | 3 | export * from './change-ownership' |
4 | export * from './channel' | 4 | export * from './channel' |
5 | export * from './comment' | 5 | export * from './comment' |
6 | export * from './editor' | ||
6 | export * from './live' | 7 | export * from './live' |
7 | export * from './file' | 8 | export * from './file' |
8 | export * from './import' | 9 | export * 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' |
30 | import { CommentsCommand } from '../videos/comments-command' | 31 | import { 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 @@ | |||
1 | import { PeerTubeServer } from '../server/server' | ||
2 | |||
3 | async 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 | |||
13 | export { | ||
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 @@ | |||
1 | export * from './accounts-command' | 1 | export * from './accounts-command' |
2 | export * from './accounts' | ||
2 | export * from './blocklist-command' | 3 | export * from './blocklist-command' |
3 | export * from './login' | 4 | export * from './login' |
4 | export * from './login-command' | 5 | export * 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 | ||
16 | async 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 | |||
16 | export { | 26 | export { |
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' | |||
12 | export * from './services-command' | 12 | export * from './services-command' |
13 | export * from './streaming-playlists-command' | 13 | export * from './streaming-playlists-command' |
14 | export * from './comments-command' | 14 | export * from './comments-command' |
15 | export * from './video-editor-command' | ||
15 | export * from './videos-command' | 16 | export * 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 @@ | |||
1 | import { HttpStatusCode, VideoEditorTask } from '@shared/models' | ||
2 | import { AbstractCommand, OverrideCommandOptions } from '../shared' | ||
3 | |||
4 | export 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 | } | ||