aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+video-editor/edit
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-02-11 10:51:33 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-02-28 10:42:19 +0100
commitc729caf6cc34630877a0e5a1bda1719384cd0c8a (patch)
tree1d2e13722e518c73d2c9e6f0969615e29d51cf8c /client/src/app/+video-editor/edit
parenta24bf4dc659cebb65d887862bf21d7a35e9ec791 (diff)
downloadPeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.gz
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.zst
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.zip
Add basic video editor support
Diffstat (limited to 'client/src/app/+video-editor/edit')
-rw-r--r--client/src/app/+video-editor/edit/index.ts2
-rw-r--r--client/src/app/+video-editor/edit/video-editor-edit.component.html88
-rw-r--r--client/src/app/+video-editor/edit/video-editor-edit.component.scss76
-rw-r--r--client/src/app/+video-editor/edit/video-editor-edit.component.ts202
-rw-r--r--client/src/app/+video-editor/edit/video-editor-edit.resolver.ts18
5 files changed, 386 insertions, 0 deletions
diff --git a/client/src/app/+video-editor/edit/index.ts b/client/src/app/+video-editor/edit/index.ts
new file mode 100644
index 000000000..390ca80fc
--- /dev/null
+++ b/client/src/app/+video-editor/edit/index.ts
@@ -0,0 +1,2 @@
1export * from './video-editor-edit.component'
2export * from './video-editor-edit.resolver'
diff --git a/client/src/app/+video-editor/edit/video-editor-edit.component.html b/client/src/app/+video-editor/edit/video-editor-edit.component.html
new file mode 100644
index 000000000..d33dfaf18
--- /dev/null
+++ b/client/src/app/+video-editor/edit/video-editor-edit.component.html
@@ -0,0 +1,88 @@
1<div class="margin-content">
2 <h1 class="title-page title-page-single" i18n>Edit {{ video.name }}</h1>
3
4 <div class="columns">
5 <form role="form" [formGroup]="form">
6
7 <div class="section cut" formGroupName="cut">
8 <h2 i18n>CUT VIDEO</h2>
9
10 <div i18n class="description">Set a new start/end.</div>
11
12 <div class="form-group">
13 <label i18n for="cutStart">New start</label>
14 <my-timestamp-input inputName="cutStart" [disableBorder]="false" [maxTimestamp]="video.duration" formControlName="start"></my-timestamp-input>
15 </div>
16
17 <div class="form-group">
18 <label i18n for="cutEnd">New end</label>
19 <my-timestamp-input inputName="cutEnd" [disableBorder]="false" [maxTimestamp]="video.duration" formControlName="end"></my-timestamp-input>
20 </div>
21 </div>
22
23 <div class="section" formGroupName="add-intro">
24 <h2 i18n>ADD INTRO</h2>
25
26 <div i18n class="description">Concatenate a file at the beginning of the video.</div>
27
28 <div class="form-group">
29 <my-reactive-file
30 formControlName="file" inputName="addIntroFile" i18n-inputLabel inputLabel="Select the intro video file"
31 [extensions]="videoExtensions" [displayFilename]="true"
32 [ngbTooltip]="getIntroOutroTooltip()"
33 ></my-reactive-file>
34 </div>
35 </div>
36
37 <div class="section" formGroupName="add-outro">
38 <h2 i18n>ADD OUTRO</h2>
39
40 <div i18n class="description">Concatenate a file at the end of the video.</div>
41
42 <div class="form-group">
43 <my-reactive-file
44 formControlName="file" inputName="addOutroFile" i18n-inputLabel inputLabel="Select the outro video file"
45 [extensions]="videoExtensions" [displayFilename]="true"
46 [ngbTooltip]="getIntroOutroTooltip()"
47 ></my-reactive-file>
48 </div>
49 </div>
50
51 <div class="section" formGroupName="add-watermark">
52 <h2 i18n>ADD WATERMARK</h2>
53
54 <div i18n class="description">Add a watermark image to the video.</div>
55
56 <div class="form-group">
57 <my-reactive-file
58 formControlName="file" inputName="addWatermarkFile" i18n-inputLabel inputLabel="Select watermark image file"
59 [extensions]="imageExtensions" [displayFilename]="true"
60 [ngbTooltip]="getWatermarkTooltip()"
61 ></my-reactive-file>
62 </div>
63 </div>
64
65 <my-button
66 className="orange-button" i18n-label label="Run video edition" icon="circle-tick"
67 (click)="runEdition()" (keydown.enter)="runEdition()"
68 [disabled]="!form.valid || isRunningEdition || noEdition()"
69 ></my-button>
70 </form>
71
72
73 <div class="information">
74 <div>
75 <label i18n>Video before edition</label>
76 <my-embed [video]="video"></my-embed>
77 </div>
78
79 <div *ngIf="!noEdition()">
80 <label i18n>Edition tasks:</label>
81
82 <ol>
83 <li *ngFor="let task of getTasksSummary()">{{ task }}</li>
84 </ol>
85 </div>
86 </div>
87 </div>
88</div>
diff --git a/client/src/app/+video-editor/edit/video-editor-edit.component.scss b/client/src/app/+video-editor/edit/video-editor-edit.component.scss
new file mode 100644
index 000000000..43f336f59
--- /dev/null
+++ b/client/src/app/+video-editor/edit/video-editor-edit.component.scss
@@ -0,0 +1,76 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3
4.columns {
5 display: flex;
6
7 .information {
8 width: 100%;
9 margin-left: 50px;
10
11 > div {
12 margin-bottom: 30px;
13 }
14
15 @media screen and (max-width: $small-view) {
16 display: none;
17 }
18 }
19}
20
21h1 {
22 font-size: 20px;
23}
24
25h2 {
26 font-weight: $font-bold;
27 font-size: 16px;
28 color: pvar(--mainColor);
29 background-color: pvar(--mainBackgroundColor);
30 padding: 0 5px;
31 width: fit-content;
32 margin: -8px 0 0;
33}
34
35.section {
36 $min-width: 600px;
37
38 @include padding-left(10px);
39
40 min-width: $min-width;
41
42 margin-bottom: 50px;
43 border: 1px solid $separator-border-color;
44 border-radius: 5px;
45 width: fit-content;
46
47 .form-group,
48 .description {
49 @include margin-left(5px);
50 }
51
52 .description {
53 color: pvar(--greyForegroundColor);
54 margin-top: 5px;
55 margin-bottom: 15px;
56 }
57
58 @media screen and (max-width: $min-width) {
59 min-width: none;
60 }
61}
62
63my-timestamp-input {
64 display: block;
65}
66
67my-embed {
68 display: block;
69 max-width: 500px;
70 width: 100%;
71}
72
73my-reactive-file {
74 display: block;
75 width: fit-content;
76}
diff --git a/client/src/app/+video-editor/edit/video-editor-edit.component.ts b/client/src/app/+video-editor/edit/video-editor-edit.component.ts
new file mode 100644
index 000000000..93d7ffcec
--- /dev/null
+++ b/client/src/app/+video-editor/edit/video-editor-edit.component.ts
@@ -0,0 +1,202 @@
1import { Component, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { ConfirmService, Notifier, ServerService } from '@app/core'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { Video, VideoDetails } from '@app/shared/shared-main'
6import { LoadingBarService } from '@ngx-loading-bar/core'
7import { secondsToTime } from '@shared/core-utils'
8import { VideoEditorTask, VideoEditorTaskCut } from '@shared/models'
9import { VideoEditorService } from '../shared'
10
11@Component({
12 selector: 'my-video-editor-edit',
13 templateUrl: './video-editor-edit.component.html',
14 styleUrls: [ './video-editor-edit.component.scss' ]
15})
16export class VideoEditorEditComponent extends FormReactive implements OnInit {
17 isRunningEdition = false
18
19 video: VideoDetails
20
21 constructor (
22 protected formValidatorService: FormValidatorService,
23 private serverService: ServerService,
24 private notifier: Notifier,
25 private router: Router,
26 private route: ActivatedRoute,
27 private videoEditorService: VideoEditorService,
28 private loadingBar: LoadingBarService,
29 private confirmService: ConfirmService
30 ) {
31 super()
32 }
33
34 ngOnInit () {
35 this.video = this.route.snapshot.data.video
36
37 const defaultValues = {
38 cut: {
39 start: 0,
40 end: this.video.duration
41 }
42 }
43
44 this.buildForm({
45 cut: {
46 start: null,
47 end: null
48 },
49 'add-intro': {
50 file: null
51 },
52 'add-outro': {
53 file: null
54 },
55 'add-watermark': {
56 file: null
57 }
58 }, defaultValues)
59 }
60
61 get videoExtensions () {
62 return this.serverService.getHTMLConfig().video.file.extensions
63 }
64
65 get imageExtensions () {
66 return this.serverService.getHTMLConfig().video.image.extensions
67 }
68
69 async runEdition () {
70 if (this.isRunningEdition) return
71
72 const title = $localize`Are you sure you want to edit "${this.video.name}"?`
73 const listHTML = this.getTasksSummary().map(t => `<li>${t}</li>`).join('')
74
75 // eslint-disable-next-line max-len
76 const confirmHTML = $localize`The current video will be overwritten by this edited video and <strong>you won't be able to recover it</strong>.<br /><br />` +
77 $localize`As a reminder, the following tasks will be executed: <ol>${listHTML}</ol>`
78
79 if (await this.confirmService.confirm(confirmHTML, title) !== true) return
80
81 this.isRunningEdition = true
82
83 const tasks = this.buildTasks()
84
85 this.loadingBar.useRef().start()
86
87 return this.videoEditorService.editVideo(this.video.uuid, tasks)
88 .subscribe({
89 next: () => {
90 this.notifier.success($localize`Video updated.`)
91 this.router.navigateByUrl(Video.buildWatchUrl(this.video))
92 },
93
94 error: err => {
95 this.loadingBar.useRef().complete()
96 this.isRunningEdition = false
97 this.notifier.error(err.message)
98 console.error(err)
99 }
100 })
101 }
102
103 getIntroOutroTooltip () {
104 return $localize`(extensions: ${this.videoExtensions.join(', ')})`
105 }
106
107 getWatermarkTooltip () {
108 return $localize`(extensions: ${this.imageExtensions.join(', ')})`
109 }
110
111 noEdition () {
112 return this.buildTasks().length === 0
113 }
114
115 getTasksSummary () {
116 const tasks = this.buildTasks()
117
118 return tasks.map(t => {
119 if (t.name === 'add-intro') {
120 return $localize`"${this.getFilename(t.options.file)}" will be added at the beggining of the video`
121 }
122
123 if (t.name === 'add-outro') {
124 return $localize`"${this.getFilename(t.options.file)}" will be added at the end of the video`
125 }
126
127 if (t.name === 'add-watermark') {
128 return $localize`"${this.getFilename(t.options.file)}" image watermark will be added to the video`
129 }
130
131 if (t.name === 'cut') {
132 const { start, end } = t.options
133
134 if (start !== undefined && end !== undefined) {
135 return $localize`Video will begin at ${secondsToTime(start)} and stop at ${secondsToTime(end)}`
136 }
137
138 if (start !== undefined) {
139 return $localize`Video will begin at ${secondsToTime(start)}`
140 }
141
142 if (end !== undefined) {
143 return $localize`Video will stop at ${secondsToTime(end)}`
144 }
145 }
146
147 return ''
148 })
149 }
150
151 private getFilename (obj: any) {
152 return obj.name
153 }
154
155 private buildTasks () {
156 const tasks: VideoEditorTask[] = []
157 const value = this.form.value
158
159 const cut = value['cut']
160 if (cut['start'] !== 0 || cut['end'] !== this.video.duration) {
161
162 const options: VideoEditorTaskCut['options'] = {}
163 if (cut['start'] !== 0) options.start = cut['start']
164 if (cut['end'] !== this.video.duration) options.end = cut['end']
165
166 tasks.push({
167 name: 'cut',
168 options
169 })
170 }
171
172 if (value['add-intro']?.['file']) {
173 tasks.push({
174 name: 'add-intro',
175 options: {
176 file: value['add-intro']['file']
177 }
178 })
179 }
180
181 if (value['add-outro']?.['file']) {
182 tasks.push({
183 name: 'add-outro',
184 options: {
185 file: value['add-outro']['file']
186 }
187 })
188 }
189
190 if (value['add-watermark']?.['file']) {
191 tasks.push({
192 name: 'add-watermark',
193 options: {
194 file: value['add-watermark']['file']
195 }
196 })
197 }
198
199 return tasks
200 }
201
202}
diff --git a/client/src/app/+video-editor/edit/video-editor-edit.resolver.ts b/client/src/app/+video-editor/edit/video-editor-edit.resolver.ts
new file mode 100644
index 000000000..7b95ae834
--- /dev/null
+++ b/client/src/app/+video-editor/edit/video-editor-edit.resolver.ts
@@ -0,0 +1,18 @@
1
2import { Injectable } from '@angular/core'
3import { ActivatedRouteSnapshot, Resolve } from '@angular/router'
4import { VideoService } from '@app/shared/shared-main'
5
6@Injectable()
7export class VideoEditorEditResolver implements Resolve<any> {
8 constructor (
9 private videoService: VideoService
10 ) {
11 }
12
13 resolve (route: ActivatedRouteSnapshot) {
14 const videoId: string = route.params['videoId']
15
16 return this.videoService.getVideo({ videoId })
17 }
18}