diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-12 19:02:00 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-16 11:50:08 +0200 |
commit | 40e87e9ecc54e3513fb586928330a7855eb192c6 (patch) | |
tree | af1111ecba85f9cd8286811ff332a67cf21be2f6 /client/src/app/videos | |
parent | d4557fd3ecc8d4ed4fb0e5c868929bc36c959ed2 (diff) | |
download | PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.gz PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.zst PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.zip |
Implement captions/subtitles
Diffstat (limited to 'client/src/app/videos')
14 files changed, 333 insertions, 85 deletions
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html new file mode 100644 index 000000000..9cd303b29 --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html | |||
@@ -0,0 +1,47 @@ | |||
1 | <div bsModal #modal="bs-modal" class="modal" tabindex="-1"> | ||
2 | <div class="modal-dialog"> | ||
3 | <div class="modal-content" [formGroup]="form"> | ||
4 | |||
5 | <div class="modal-header"> | ||
6 | <span class="close" aria-hidden="true" (click)="hide()"></span> | ||
7 | <h4 i18n class="modal-title">Add caption</h4> | ||
8 | </div> | ||
9 | |||
10 | <div class="modal-body"> | ||
11 | <label i18n for="language">Language</label> | ||
12 | <div class="peertube-select-container"> | ||
13 | <select id="language" formControlName="language"> | ||
14 | <option></option> | ||
15 | <option *ngFor="let language of videoCaptionLanguages" [value]="language.id">{{ language.label }}</option> | ||
16 | </select> | ||
17 | </div> | ||
18 | |||
19 | <div *ngIf="formErrors.language" class="form-error"> | ||
20 | {{ formErrors.language }} | ||
21 | </div> | ||
22 | |||
23 | <div class="caption-file"> | ||
24 | <my-reactive-file | ||
25 | formControlName="captionfile" inputName="captionfile" i18n-inputLabel inputLabel="Select the caption file" | ||
26 | [extensions]="videoCaptionExtensions" [maxFileSize]="videoCaptionMaxSize" [displayFilename]="true" | ||
27 | ></my-reactive-file> | ||
28 | </div> | ||
29 | |||
30 | <div *ngIf="isReplacingExistingCaption()" class="warning-replace-caption" i18n> | ||
31 | This will replace an existing caption! | ||
32 | </div> | ||
33 | |||
34 | <div class="form-group inputs"> | ||
35 | <span i18n class="action-button action-button-cancel" (click)="hide()"> | ||
36 | Cancel | ||
37 | </span> | ||
38 | |||
39 | <input | ||
40 | type="submit" i18n-value value="Add this caption" class="action-button-submit" | ||
41 | [disabled]="!form.valid" (click)="addCaption()" | ||
42 | > | ||
43 | </div> | ||
44 | </div> | ||
45 | </div> | ||
46 | </div> | ||
47 | </div> | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss new file mode 100644 index 000000000..c6da1877e --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.scss | |||
@@ -0,0 +1,15 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .peertube-select-container { | ||
5 | @include peertube-select-container(auto); | ||
6 | } | ||
7 | |||
8 | .caption-file { | ||
9 | margin-top: 20px; | ||
10 | } | ||
11 | |||
12 | .warning-replace-caption { | ||
13 | color: red; | ||
14 | margin-top: 10px; | ||
15 | } \ No newline at end of file | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts new file mode 100644 index 000000000..45b8c71f8 --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.ts | |||
@@ -0,0 +1,80 @@ | |||
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | ||
2 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
3 | import { FormReactive } from '@app/shared' | ||
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
5 | import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' | ||
6 | import { ServerService } from '@app/core' | ||
7 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-video-caption-add-modal', | ||
11 | styleUrls: [ './video-caption-add-modal.component.scss' ], | ||
12 | templateUrl: './video-caption-add-modal.component.html' | ||
13 | }) | ||
14 | |||
15 | export class VideoCaptionAddModalComponent extends FormReactive implements OnInit { | ||
16 | @Input() existingCaptions: string[] | ||
17 | |||
18 | @Output() captionAdded = new EventEmitter<VideoCaptionEdit>() | ||
19 | |||
20 | @ViewChild('modal') modal: ModalDirective | ||
21 | |||
22 | videoCaptionLanguages = [] | ||
23 | |||
24 | private closingModal = false | ||
25 | |||
26 | constructor ( | ||
27 | protected formValidatorService: FormValidatorService, | ||
28 | private serverService: ServerService, | ||
29 | private videoCaptionsValidatorsService: VideoCaptionsValidatorsService | ||
30 | ) { | ||
31 | super() | ||
32 | } | ||
33 | |||
34 | get videoCaptionExtensions () { | ||
35 | return this.serverService.getConfig().videoCaption.file.extensions | ||
36 | } | ||
37 | |||
38 | get videoCaptionMaxSize () { | ||
39 | return this.serverService.getConfig().videoCaption.file.size.max | ||
40 | } | ||
41 | |||
42 | ngOnInit () { | ||
43 | this.videoCaptionLanguages = this.serverService.getVideoLanguages() | ||
44 | |||
45 | this.buildForm({ | ||
46 | language: this.videoCaptionsValidatorsService.VIDEO_CAPTION_LANGUAGE, | ||
47 | captionfile: this.videoCaptionsValidatorsService.VIDEO_CAPTION_FILE | ||
48 | }) | ||
49 | } | ||
50 | |||
51 | show () { | ||
52 | this.modal.show() | ||
53 | } | ||
54 | |||
55 | hide () { | ||
56 | this.modal.hide() | ||
57 | } | ||
58 | |||
59 | isReplacingExistingCaption () { | ||
60 | if (this.closingModal === true) return false | ||
61 | |||
62 | const languageId = this.form.value[ 'language' ] | ||
63 | |||
64 | return languageId && this.existingCaptions.indexOf(languageId) !== -1 | ||
65 | } | ||
66 | |||
67 | async addCaption () { | ||
68 | this.closingModal = true | ||
69 | |||
70 | const languageId = this.form.value[ 'language' ] | ||
71 | const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) | ||
72 | |||
73 | this.captionAdded.emit({ | ||
74 | language: languageObject, | ||
75 | captionfile: this.form.value['captionfile'] | ||
76 | }) | ||
77 | |||
78 | this.hide() | ||
79 | } | ||
80 | } | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index 447c5ab9b..14d5f3614 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -132,13 +132,39 @@ | |||
132 | <label i18n for="waitTranscoding">Wait transcoding before publishing the video</label> | 132 | <label i18n for="waitTranscoding">Wait transcoding before publishing the video</label> |
133 | <my-help | 133 | <my-help |
134 | tooltipPlacement="top" helpType="custom" i18n-customHtml | 134 | tooltipPlacement="top" helpType="custom" i18n-customHtml |
135 | customHtml="If you decide to not wait transcoding before publishing the video, it can be unplayable until it transcoding ends." | 135 | customHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." |
136 | ></my-help> | 136 | ></my-help> |
137 | </div> | 137 | </div> |
138 | 138 | ||
139 | </div> | 139 | </div> |
140 | </tab> | 140 | </tab> |
141 | 141 | ||
142 | <tab i18n-heading heading="Captions"> | ||
143 | <div class="col-md-12 captions"> | ||
144 | |||
145 | <div class="captions-header"> | ||
146 | <a (click)="openAddCaptionModal()" class="create-caption"> | ||
147 | <span class="icon icon-add"></span> | ||
148 | <ng-container i18n>Add another caption</ng-container> | ||
149 | </a> | ||
150 | </div> | ||
151 | |||
152 | <div class="form-group" *ngFor="let videoCaption of videoCaptions"> | ||
153 | |||
154 | <div class="caption-entry"> | ||
155 | <div class="caption-entry-label">{{ videoCaption.language.label }}</div> | ||
156 | |||
157 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> | ||
158 | </div> | ||
159 | </div> | ||
160 | |||
161 | <div class="no-caption" *ngIf="videoCaptions?.length === 0"> | ||
162 | No captions for now. | ||
163 | </div> | ||
164 | |||
165 | </div> | ||
166 | </tab> | ||
167 | |||
142 | <tab i18n-heading heading="Advanced settings"> | 168 | <tab i18n-heading heading="Advanced settings"> |
143 | <div class="col-md-12 advanced-settings"> | 169 | <div class="col-md-12 advanced-settings"> |
144 | <div class="form-group"> | 170 | <div class="form-group"> |
@@ -172,3 +198,7 @@ | |||
172 | </tabset> | 198 | </tabset> |
173 | 199 | ||
174 | </div> | 200 | </div> |
201 | |||
202 | <my-video-caption-add-modal | ||
203 | #videoCaptionAddModal [existingCaptions]="getExistingCaptions()" (captionAdded)="onCaptionAdded($event)" | ||
204 | ></my-video-caption-add-modal> \ No newline at end of file | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss index 061eca4a7..03b8359de 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -7,6 +7,7 @@ | |||
7 | 7 | ||
8 | .video-edit { | 8 | .video-edit { |
9 | height: 100%; | 9 | height: 100%; |
10 | min-height: 300px; | ||
10 | 11 | ||
11 | .form-group { | 12 | .form-group { |
12 | margin-bottom: 25px; | 13 | margin-bottom: 25px; |
@@ -49,6 +50,40 @@ | |||
49 | } | 50 | } |
50 | } | 51 | } |
51 | 52 | ||
53 | .captions { | ||
54 | |||
55 | .captions-header { | ||
56 | text-align: right; | ||
57 | |||
58 | .create-caption { | ||
59 | @include create-button('../../../../assets/images/global/add.svg'); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | .caption-entry { | ||
64 | display: flex; | ||
65 | height: 40px; | ||
66 | align-items: center; | ||
67 | |||
68 | .caption-entry-label { | ||
69 | font-size: 15px; | ||
70 | font-weight: bold; | ||
71 | |||
72 | margin-right: 20px; | ||
73 | } | ||
74 | |||
75 | .caption-entry-delete { | ||
76 | @include peertube-button; | ||
77 | @include grey-button; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | .no-caption { | ||
82 | text-align: center; | ||
83 | font-size: 15px; | ||
84 | } | ||
85 | } | ||
86 | |||
52 | .submit-container { | 87 | .submit-container { |
53 | text-align: right; | 88 | text-align: right; |
54 | 89 | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts index 66eb6611a..9394d7dab 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core' |
2 | import { FormGroup, ValidatorFn, Validators } from '@angular/forms' | 2 | import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' | 4 | import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared' |
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { NotificationsService } from 'angular2-notifications' |
@@ -8,6 +8,10 @@ import { VideoEdit } from '../../../shared/video/video-edit.model' | |||
8 | import { map } from 'rxjs/operators' | 8 | import { map } from 'rxjs/operators' |
9 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 9 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
10 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' | 10 | import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' |
11 | import { VideoCaptionService } from '@app/shared/video-caption' | ||
12 | import { VideoCaptionAddModalComponent } from '@app/videos/+video-edit/shared/video-caption-add-modal.component' | ||
13 | import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' | ||
14 | import { removeElementFromArray } from '@app/shared/misc/utils' | ||
11 | 15 | ||
12 | @Component({ | 16 | @Component({ |
13 | selector: 'my-video-edit', | 17 | selector: 'my-video-edit', |
@@ -15,13 +19,16 @@ import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calend | |||
15 | templateUrl: './video-edit.component.html' | 19 | templateUrl: './video-edit.component.html' |
16 | }) | 20 | }) |
17 | 21 | ||
18 | export class VideoEditComponent implements OnInit { | 22 | export class VideoEditComponent implements OnInit, OnDestroy { |
19 | @Input() form: FormGroup | 23 | @Input() form: FormGroup |
20 | @Input() formErrors: { [ id: string ]: string } = {} | 24 | @Input() formErrors: { [ id: string ]: string } = {} |
21 | @Input() validationMessages: FormReactiveValidationMessages = {} | 25 | @Input() validationMessages: FormReactiveValidationMessages = {} |
22 | @Input() videoPrivacies = [] | 26 | @Input() videoPrivacies = [] |
23 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] | 27 | @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] |
24 | @Input() schedulePublicationPossible = true | 28 | @Input() schedulePublicationPossible = true |
29 | @Input() videoCaptions: VideoCaptionEdit[] = [] | ||
30 | |||
31 | @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent | ||
25 | 32 | ||
26 | // So that it can be accessed in the template | 33 | // So that it can be accessed in the template |
27 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY | 34 | readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY |
@@ -41,9 +48,12 @@ export class VideoEditComponent implements OnInit { | |||
41 | calendarTimezone: string | 48 | calendarTimezone: string |
42 | calendarDateFormat: string | 49 | calendarDateFormat: string |
43 | 50 | ||
51 | private schedulerInterval | ||
52 | |||
44 | constructor ( | 53 | constructor ( |
45 | private formValidatorService: FormValidatorService, | 54 | private formValidatorService: FormValidatorService, |
46 | private videoValidatorsService: VideoValidatorsService, | 55 | private videoValidatorsService: VideoValidatorsService, |
56 | private videoCaptionService: VideoCaptionService, | ||
47 | private route: ActivatedRoute, | 57 | private route: ActivatedRoute, |
48 | private router: Router, | 58 | private router: Router, |
49 | private notificationsService: NotificationsService, | 59 | private notificationsService: NotificationsService, |
@@ -91,6 +101,13 @@ export class VideoEditComponent implements OnInit { | |||
91 | defaultValues | 101 | defaultValues |
92 | ) | 102 | ) |
93 | 103 | ||
104 | this.form.addControl('captions', new FormArray([ | ||
105 | new FormGroup({ | ||
106 | language: new FormControl(), | ||
107 | captionfile: new FormControl() | ||
108 | }) | ||
109 | ])) | ||
110 | |||
94 | this.trackChannelChange() | 111 | this.trackChannelChange() |
95 | this.trackPrivacyChange() | 112 | this.trackPrivacyChange() |
96 | } | 113 | } |
@@ -102,7 +119,35 @@ export class VideoEditComponent implements OnInit { | |||
102 | this.videoLicences = this.serverService.getVideoLicences() | 119 | this.videoLicences = this.serverService.getVideoLicences() |
103 | this.videoLanguages = this.serverService.getVideoLanguages() | 120 | this.videoLanguages = this.serverService.getVideoLanguages() |
104 | 121 | ||
105 | setTimeout(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute | 122 | this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute |
123 | } | ||
124 | |||
125 | ngOnDestroy () { | ||
126 | if (this.schedulerInterval) clearInterval(this.schedulerInterval) | ||
127 | } | ||
128 | |||
129 | getExistingCaptions () { | ||
130 | return this.videoCaptions.map(c => c.language.id) | ||
131 | } | ||
132 | |||
133 | onCaptionAdded (caption: VideoCaptionEdit) { | ||
134 | this.videoCaptions.push( | ||
135 | Object.assign(caption, { action: 'CREATE' as 'CREATE' }) | ||
136 | ) | ||
137 | } | ||
138 | |||
139 | deleteCaption (caption: VideoCaptionEdit) { | ||
140 | // This caption is not on the server, just remove it from our array | ||
141 | if (caption.action === 'CREATE') { | ||
142 | removeElementFromArray(this.videoCaptions, caption) | ||
143 | return | ||
144 | } | ||
145 | |||
146 | caption.action = 'REMOVE' as 'REMOVE' | ||
147 | } | ||
148 | |||
149 | openAddCaptionModal () { | ||
150 | this.videoCaptionAddModal.show() | ||
106 | } | 151 | } |
107 | 152 | ||
108 | private trackPrivacyChange () { | 153 | private trackPrivacyChange () { |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts index 6bf3e34b1..f6bd65fdc 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts | |||
@@ -5,6 +5,7 @@ import { SharedModule } from '../../../shared/' | |||
5 | import { VideoEditComponent } from './video-edit.component' | 5 | import { VideoEditComponent } from './video-edit.component' |
6 | import { VideoImageComponent } from './video-image.component' | 6 | import { VideoImageComponent } from './video-image.component' |
7 | import { CalendarModule } from 'primeng/components/calendar/calendar' | 7 | import { CalendarModule } from 'primeng/components/calendar/calendar' |
8 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | ||
8 | 9 | ||
9 | @NgModule({ | 10 | @NgModule({ |
10 | imports: [ | 11 | imports: [ |
@@ -16,7 +17,8 @@ import { CalendarModule } from 'primeng/components/calendar/calendar' | |||
16 | 17 | ||
17 | declarations: [ | 18 | declarations: [ |
18 | VideoEditComponent, | 19 | VideoEditComponent, |
19 | VideoImageComponent | 20 | VideoImageComponent, |
21 | VideoCaptionAddModalComponent | ||
20 | ], | 22 | ], |
21 | 23 | ||
22 | exports: [ | 24 | exports: [ |
diff --git a/client/src/app/videos/+video-edit/shared/video-image.component.html b/client/src/app/videos/+video-edit/shared/video-image.component.html index e319d7ee7..c09c862c4 100644 --- a/client/src/app/videos/+video-edit/shared/video-image.component.html +++ b/client/src/app/videos/+video-edit/shared/video-image.component.html | |||
@@ -1,15 +1,8 @@ | |||
1 | <div class="root"> | 1 | <div class="root"> |
2 | <div> | 2 | <my-reactive-file |
3 | <div class="button-file"> | 3 | [inputName]="inputName" [inputLabel]="inputLabel" [extensions]="videoImageExtensions" [maxFileSize]="maxVideoImageSize" |
4 | <span>{{ inputLabel }}</span> | 4 | (fileChanged)="onFileChanged($event)" |
5 | <input | 5 | ></my-reactive-file> |
6 | type="file" | ||
7 | [name]="inputName" [id]="inputName" [accept]="videoImageExtensions" | ||
8 | (change)="fileChange($event)" | ||
9 | /> | ||
10 | </div> | ||
11 | <div i18n class="image-constraints">(extensions: {{ videoImageExtensions }}, max size: {{ maxVideoImageSize | bytes }})</div> | ||
12 | </div> | ||
13 | 6 | ||
14 | <img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" /> | 7 | <img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" /> |
15 | <div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div> | 8 | <div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div> |
diff --git a/client/src/app/videos/+video-edit/shared/video-image.component.scss b/client/src/app/videos/+video-edit/shared/video-image.component.scss index d4901e7ab..b63963bca 100644 --- a/client/src/app/videos/+video-edit/shared/video-image.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-image.component.scss | |||
@@ -6,16 +6,6 @@ | |||
6 | display: flex; | 6 | display: flex; |
7 | align-items: center; | 7 | align-items: center; |
8 | 8 | ||
9 | .button-file { | ||
10 | @include peertube-button-file(auto); | ||
11 | |||
12 | min-width: 190px; | ||
13 | } | ||
14 | |||
15 | .image-constraints { | ||
16 | font-size: 13px; | ||
17 | } | ||
18 | |||
19 | .preview { | 9 | .preview { |
20 | border: 2px solid grey; | 10 | border: 2px solid grey; |
21 | border-radius: 4px; | 11 | border-radius: 4px; |
diff --git a/client/src/app/videos/+video-edit/shared/video-image.component.ts b/client/src/app/videos/+video-edit/shared/video-image.component.ts index 25955baaa..a604cde90 100644 --- a/client/src/app/videos/+video-edit/shared/video-image.component.ts +++ b/client/src/app/videos/+video-edit/shared/video-image.component.ts | |||
@@ -2,8 +2,6 @@ import { Component, forwardRef, Input } from '@angular/core' | |||
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' | 3 | import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' |
4 | import { ServerService } from '@app/core' | 4 | import { ServerService } from '@app/core' |
5 | import { NotificationsService } from 'angular2-notifications' | ||
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
7 | 5 | ||
8 | @Component({ | 6 | @Component({ |
9 | selector: 'my-video-image', | 7 | selector: 'my-video-image', |
@@ -25,36 +23,26 @@ export class VideoImageComponent implements ControlValueAccessor { | |||
25 | 23 | ||
26 | imageSrc: SafeResourceUrl | 24 | imageSrc: SafeResourceUrl |
27 | 25 | ||
28 | private file: Blob | 26 | private file: File |
29 | 27 | ||
30 | constructor ( | 28 | constructor ( |
31 | private sanitizer: DomSanitizer, | 29 | private sanitizer: DomSanitizer, |
32 | private serverService: ServerService, | 30 | private serverService: ServerService |
33 | private notificationsService: NotificationsService, | ||
34 | private i18n: I18n | ||
35 | ) {} | 31 | ) {} |
36 | 32 | ||
37 | get videoImageExtensions () { | 33 | get videoImageExtensions () { |
38 | return this.serverService.getConfig().video.image.extensions.join(',') | 34 | return this.serverService.getConfig().video.image.extensions |
39 | } | 35 | } |
40 | 36 | ||
41 | get maxVideoImageSize () { | 37 | get maxVideoImageSize () { |
42 | return this.serverService.getConfig().video.image.size.max | 38 | return this.serverService.getConfig().video.image.size.max |
43 | } | 39 | } |
44 | 40 | ||
45 | fileChange (event: any) { | 41 | onFileChanged (file: File) { |
46 | if (event.target.files && event.target.files.length) { | 42 | this.file = file |
47 | const [ file ] = event.target.files | ||
48 | |||
49 | if (file.size > this.maxVideoImageSize) { | ||
50 | this.notificationsService.error(this.i18n('Error'), this.i18n('This image is too large.')) | ||
51 | return | ||
52 | } | ||
53 | 43 | ||
54 | this.file = file | 44 | this.propagateChange(this.file) |
55 | this.propagateChange(this.file) | 45 | this.updatePreview() |
56 | this.updatePreview() | ||
57 | } | ||
58 | } | 46 | } |
59 | 47 | ||
60 | propagateChange = (_: any) => { /* empty */ } | 48 | propagateChange = (_: any) => { /* empty */ } |
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html index 7d9443209..9c2c01c65 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -46,7 +46,7 @@ | |||
46 | <!-- Hidden because we want to load the component --> | 46 | <!-- Hidden because we want to load the component --> |
47 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> | 47 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> |
48 | <my-video-edit | 48 | <my-video-edit |
49 | [form]="form" [formErrors]="formErrors" | 49 | [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" |
50 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 50 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
51 | ></my-video-edit> | 51 | ></my-video-edit> |
52 | 52 | ||
diff --git a/client/src/app/videos/+video-edit/video-add.component.ts b/client/src/app/videos/+video-edit/video-add.component.ts index 7c4b6260b..8c30cedfb 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -15,6 +15,8 @@ import { VideoEdit } from '../../shared/video/video-edit.model' | |||
15 | import { VideoService } from '../../shared/video/video.service' | 15 | import { VideoService } from '../../shared/video/video.service' |
16 | import { I18n } from '@ngx-translate/i18n-polyfill' | 16 | import { I18n } from '@ngx-translate/i18n-polyfill' |
17 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 17 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
18 | import { switchMap } from 'rxjs/operators' | ||
19 | import { VideoCaptionService } from '@app/shared/video-caption' | ||
18 | 20 | ||
19 | @Component({ | 21 | @Component({ |
20 | selector: 'my-videos-add', | 22 | selector: 'my-videos-add', |
@@ -46,6 +48,7 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy | |||
46 | videoPrivacies = [] | 48 | videoPrivacies = [] |
47 | firstStepPrivacyId = 0 | 49 | firstStepPrivacyId = 0 |
48 | firstStepChannelId = 0 | 50 | firstStepChannelId = 0 |
51 | videoCaptions = [] | ||
49 | 52 | ||
50 | constructor ( | 53 | constructor ( |
51 | protected formValidatorService: FormValidatorService, | 54 | protected formValidatorService: FormValidatorService, |
@@ -56,7 +59,8 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy | |||
56 | private serverService: ServerService, | 59 | private serverService: ServerService, |
57 | private videoService: VideoService, | 60 | private videoService: VideoService, |
58 | private loadingBar: LoadingBarService, | 61 | private loadingBar: LoadingBarService, |
59 | private i18n: I18n | 62 | private i18n: I18n, |
63 | private videoCaptionService: VideoCaptionService | ||
60 | ) { | 64 | ) { |
61 | super() | 65 | super() |
62 | } | 66 | } |
@@ -159,11 +163,8 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy | |||
159 | let name: string | 163 | let name: string |
160 | 164 | ||
161 | // If the name of the file is very small, keep the extension | 165 | // If the name of the file is very small, keep the extension |
162 | if (nameWithoutExtension.length < 3) { | 166 | if (nameWithoutExtension.length < 3) name = videofile.name |
163 | name = videofile.name | 167 | else name = nameWithoutExtension |
164 | } else { | ||
165 | name = nameWithoutExtension | ||
166 | } | ||
167 | 168 | ||
168 | const privacy = this.firstStepPrivacyId.toString() | 169 | const privacy = this.firstStepPrivacyId.toString() |
169 | const nsfw = false | 170 | const nsfw = false |
@@ -225,22 +226,25 @@ export class VideoAddComponent extends FormReactive implements OnInit, OnDestroy | |||
225 | this.isUpdatingVideo = true | 226 | this.isUpdatingVideo = true |
226 | this.loadingBar.start() | 227 | this.loadingBar.start() |
227 | this.videoService.updateVideo(video) | 228 | this.videoService.updateVideo(video) |
228 | .subscribe( | 229 | .pipe( |
229 | () => { | 230 | // Then update captions |
230 | this.isUpdatingVideo = false | 231 | switchMap(() => this.videoCaptionService.updateCaptions(video.id, this.videoCaptions)) |
231 | this.isUploadingVideo = false | 232 | ) |
232 | this.loadingBar.complete() | 233 | .subscribe( |
233 | 234 | () => { | |
234 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.')) | 235 | this.isUpdatingVideo = false |
235 | this.router.navigate([ '/videos/watch', video.uuid ]) | 236 | this.isUploadingVideo = false |
236 | }, | 237 | this.loadingBar.complete() |
237 | 238 | ||
238 | err => { | 239 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.')) |
239 | this.isUpdatingVideo = false | 240 | this.router.navigate([ '/videos/watch', video.uuid ]) |
240 | this.notificationsService.error(this.i18n('Error'), err.message) | 241 | }, |
241 | console.error(err) | 242 | |
242 | } | 243 | err => { |
243 | ) | 244 | this.isUpdatingVideo = false |
244 | 245 | this.notificationsService.error(this.i18n('Error'), err.message) | |
246 | console.error(err) | ||
247 | } | ||
248 | ) | ||
245 | } | 249 | } |
246 | } | 250 | } |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index 5cb16c8ab..9242c30a0 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -8,6 +8,7 @@ | |||
8 | <my-video-edit | 8 | <my-video-edit |
9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" | 9 | [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" |
10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" | 10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels" |
11 | [videoCaptions]="videoCaptions" | ||
11 | ></my-video-edit> | 12 | ></my-video-edit> |
12 | 13 | ||
13 | <div class="submit-container"> | 14 | <div class="submit-container"> |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index c4e6f44de..b67874401 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -12,6 +12,7 @@ import { VideoService } from '../../shared/video/video.service' | |||
12 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 12 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
13 | import { I18n } from '@ngx-translate/i18n-polyfill' | 13 | import { I18n } from '@ngx-translate/i18n-polyfill' |
14 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 14 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
15 | import { VideoCaptionService } from '@app/shared/video-caption' | ||
15 | 16 | ||
16 | @Component({ | 17 | @Component({ |
17 | selector: 'my-videos-update', | 18 | selector: 'my-videos-update', |
@@ -25,6 +26,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
25 | videoPrivacies = [] | 26 | videoPrivacies = [] |
26 | userVideoChannels = [] | 27 | userVideoChannels = [] |
27 | schedulePublicationPossible = false | 28 | schedulePublicationPossible = false |
29 | videoCaptions = [] | ||
28 | 30 | ||
29 | constructor ( | 31 | constructor ( |
30 | protected formValidatorService: FormValidatorService, | 32 | protected formValidatorService: FormValidatorService, |
@@ -36,6 +38,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
36 | private authService: AuthService, | 38 | private authService: AuthService, |
37 | private loadingBar: LoadingBarService, | 39 | private loadingBar: LoadingBarService, |
38 | private videoChannelService: VideoChannelService, | 40 | private videoChannelService: VideoChannelService, |
41 | private videoCaptionService: VideoCaptionService, | ||
39 | private i18n: I18n | 42 | private i18n: I18n |
40 | ) { | 43 | ) { |
41 | super() | 44 | super() |
@@ -63,12 +66,21 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
63 | map(videoChannels => videoChannels.map(c => ({ id: c.id, label: c.displayName, support: c.support }))), | 66 | map(videoChannels => videoChannels.map(c => ({ id: c.id, label: c.displayName, support: c.support }))), |
64 | map(videoChannels => ({ video, videoChannels })) | 67 | map(videoChannels => ({ video, videoChannels })) |
65 | ) | 68 | ) |
69 | }), | ||
70 | switchMap(({ video, videoChannels }) => { | ||
71 | return this.videoCaptionService | ||
72 | .listCaptions(video.id) | ||
73 | .pipe( | ||
74 | map(result => result.data), | ||
75 | map(videoCaptions => ({ video, videoChannels, videoCaptions })) | ||
76 | ) | ||
66 | }) | 77 | }) |
67 | ) | 78 | ) |
68 | .subscribe( | 79 | .subscribe( |
69 | ({ video, videoChannels }) => { | 80 | ({ video, videoChannels, videoCaptions }) => { |
70 | this.video = new VideoEdit(video) | 81 | this.video = new VideoEdit(video) |
71 | this.userVideoChannels = videoChannels | 82 | this.userVideoChannels = videoChannels |
83 | this.videoCaptions = videoCaptions | ||
72 | 84 | ||
73 | // We cannot set private a video that was not private | 85 | // We cannot set private a video that was not private |
74 | if (this.video.privacy !== VideoPrivacy.PRIVATE) { | 86 | if (this.video.privacy !== VideoPrivacy.PRIVATE) { |
@@ -102,21 +114,27 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
102 | 114 | ||
103 | this.loadingBar.start() | 115 | this.loadingBar.start() |
104 | this.isUpdatingVideo = true | 116 | this.isUpdatingVideo = true |
117 | |||
118 | // Update the video | ||
105 | this.videoService.updateVideo(this.video) | 119 | this.videoService.updateVideo(this.video) |
106 | .subscribe( | 120 | .pipe( |
107 | () => { | 121 | // Then update captions |
108 | this.isUpdatingVideo = false | 122 | switchMap(() => this.videoCaptionService.updateCaptions(this.video.id, this.videoCaptions)) |
109 | this.loadingBar.complete() | 123 | ) |
110 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) | 124 | .subscribe( |
111 | this.router.navigate([ '/videos/watch', this.video.uuid ]) | 125 | () => { |
112 | }, | 126 | this.isUpdatingVideo = false |
113 | 127 | this.loadingBar.complete() | |
114 | err => { | 128 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) |
115 | this.isUpdatingVideo = false | 129 | this.router.navigate([ '/videos/watch', this.video.uuid ]) |
116 | this.notificationsService.error(this.i18n('Error'), err.message) | 130 | }, |
117 | console.error(err) | 131 | |
118 | } | 132 | err => { |
119 | ) | 133 | this.isUpdatingVideo = false |
134 | this.notificationsService.error(this.i18n('Error'), err.message) | ||
135 | console.error(err) | ||
136 | } | ||
137 | ) | ||
120 | 138 | ||
121 | } | 139 | } |
122 | 140 | ||