1
0
Fork 0

Add simple subtitle edition from video captions tab

Introduce a new __Edit__ button on a subtitle.
It opens a modal with simple textarea allowing the user to do quick corrections on a subtitle.
This commit is contained in:
lutangar 2021-12-22 18:36:56 +01:00 committed by Chocobozzz
parent e66d0892b1
commit 57d74ec83d
11 changed files with 181 additions and 10 deletions

View file

@ -77,7 +77,8 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni
this.captionAdded.emit({
language: languageObject,
captionfile: this.form.value['captionfile']
captionfile: this.form.value['captionfile'],
action: 'CREATE'
})
this.hide()

View file

@ -0,0 +1,36 @@
<ng-template #modal>
<ng-container [formGroup]="form">
<div class="modal-header">
<h4 i18n class="modal-title">Edit caption</h4>
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
</div>
<div class="modal-body">
<label i18n for="captionFileContent">Caption</label>
<textarea
id="captionFileContent"
formControlName="captionFileContent"
class="form-control caption-textarea"
[ngClass]="{ 'input-error': formErrors['captionFileContent'] }"
>
</textarea>
<div *ngIf="formErrors.captionFileContent" class="form-error">
{{ formErrors.description }}
</div>
</div>
<div class="modal-footer inputs">
<input
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
(click)="cancel()" (key.enter)="cancel()"
>
<input
type="submit" i18n-value value="Edit this caption" class="peertube-button orange-button"
[disabled]="!form.valid" (click)="updateCaption()"
>
</div>
</ng-container>
</ng-template>

View file

@ -0,0 +1,4 @@
.caption-textarea {
min-height: 600px;
}

View file

@ -0,0 +1,92 @@
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
import { VIDEO_CAPTION_FILE_CONTENT_VALIDATOR } from '@app/shared/form-validators/video-captions-validators'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { VideoCaptionEdit, VideoCaptionService, VideoCaptionWithPathEdit } from '@app/shared/shared-main'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { HTMLServerConfig, VideoConstant } from '@shared/models'
import { ServerService } from '../../../../core'
@Component({
selector: 'my-video-caption-edit-modal',
styleUrls: [ './video-caption-edit-modal.component.scss' ],
templateUrl: './video-caption-edit-modal.component.html'
})
export class VideoCaptionEditModalComponent extends FormReactive implements OnInit {
@Input() videoCaption: VideoCaptionWithPathEdit
@Input() serverConfig: HTMLServerConfig
@Output() captionEdited = new EventEmitter<VideoCaptionEdit>()
@ViewChild('modal', { static: true }) modal: ElementRef
videoCaptionLanguages: VideoConstant<string>[] = []
private openedModal: NgbModalRef
private closingModal = false
constructor (
protected formValidatorService: FormValidatorService,
private modalService: NgbModal,
private videoCaptionService: VideoCaptionService,
private serverService: ServerService
) {
super()
}
ngOnInit () {
this.serverService.getVideoLanguages().subscribe(languages => this.videoCaptionLanguages = languages)
this.buildForm({ captionFileContent: VIDEO_CAPTION_FILE_CONTENT_VALIDATOR })
this.loadCaptionContent()
}
loadCaptionContent () {
const { captionPath } = this.videoCaption
if (captionPath) {
this.videoCaptionService.getCaptionContent({
captionPath
}).subscribe((res) => {
this.form.patchValue({
captionFileContent: res
})
})
}
}
show () {
this.closingModal = false
this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
}
hide () {
this.closingModal = true
this.openedModal.close()
}
cancel () {
this.hide()
}
isReplacingExistingCaption () {
return true
}
updateCaption () {
const format = 'vtt'
const languageId = this.videoCaption.language.id
const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId)
this.captionEdited.emit({
language: languageObject,
captionfile: new File([ this.form.value['captionFileContent'] ], `${languageId}.${format}`, {
type: 'text/vtt',
lastModified: Date.now()
}),
action: 'UPDATE'
})
this.hide()
}
}

View file

@ -186,6 +186,7 @@
<div i18n class="caption-entry-state">Already uploaded &#10004;</div>
<span i18n class="caption-entry-edit" (click)="videoCaptionEditModal.show()">Edit</span>
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
</ng-container>
@ -197,6 +198,14 @@
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span>
</ng-container>
<ng-container *ngIf="videoCaption.action === 'UPDATE'">
<span class="caption-entry-label">{{ videoCaption.language.label }}</span>
<div i18n class="caption-entry-state caption-entry-state-create">Will be edited on update</div>
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel edition</span>
</ng-container>
<ng-container *ngIf="videoCaption.action === 'REMOVE'">
<span class="caption-entry-label">{{ videoCaption.language.label }}</span>
@ -204,6 +213,13 @@
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
</ng-container>
<my-video-caption-edit-modal
#videoCaptionEditModal
[videoCaption]="videoCaption"
[serverConfig]="serverConfig"
(captionEdited)="onCaptionEdited($event)"
></my-video-caption-edit-modal>
</div>
</div>
@ -373,5 +389,5 @@
</div>
<my-video-caption-add-modal
#videoCaptionAddModal [existingCaptions]="getExistingCaptions()" [serverConfig]="serverConfig" (captionAdded)="onCaptionAdded($event)"
#videoCaptionAddModal [existingCaptions]="getExistingCaptions()" [serverConfig]="serverConfig" (captionAdded)="onCaptionEdited($event)"
></my-video-caption-add-modal>

View file

@ -96,6 +96,10 @@ my-peertube-checkbox {
}
}
.caption-entry-edit {
@include peertube-button;
}
.caption-entry-delete {
@include peertube-button;
@include grey-button;

View file

@ -21,7 +21,7 @@ import {
} from '@app/shared/form-validators/video-validators'
import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms'
import { InstanceService } from '@app/shared/shared-instance'
import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
import { VideoCaptionEdit, VideoCaptionWithPathEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
import { PluginInfo } from '@root-helpers/plugins-manager'
import {
HTMLServerConfig,
@ -34,6 +34,7 @@ import {
} from '@shared/models'
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
import { VideoEditType } from './video-edit.type'
type VideoLanguages = VideoConstant<string> & { group?: string }
@ -58,13 +59,14 @@ export class VideoEditComponent implements OnInit, OnDestroy {
@Input() userVideoChannels: SelectChannelItem[] = []
@Input() forbidScheduledPublication = true
@Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = []
@Input() videoCaptions: (VideoCaptionWithPathEdit)[] = []
@Input() waitTranscodingEnabled = true
@Input() type: VideoEditType
@Input() liveVideo: LiveVideo
@ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent
@ViewChild('videoCaptionEditModal', { static: true }) editCaptionModal: VideoCaptionEditModalComponent
@Output() formBuilt = new EventEmitter<void>()
@Output() pluginFieldsAdded = new EventEmitter<void>()
@ -228,12 +230,12 @@ export class VideoEditComponent implements OnInit, OnDestroy {
.map(c => c.language.id)
}
onCaptionAdded (caption: VideoCaptionEdit) {
onCaptionEdited (caption: VideoCaptionEdit) {
const existingCaption = this.videoCaptions.find(c => c.language.id === caption.language.id)
// Replace existing caption?
if (existingCaption) {
Object.assign(existingCaption, caption, { action: 'CREATE' as 'CREATE' })
Object.assign(existingCaption, caption)
} else {
this.videoCaptions.push(
Object.assign(caption, { action: 'CREATE' as 'CREATE' })
@ -251,7 +253,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
}
// This caption is not on the server, just remove it from our array
if (caption.action === 'CREATE') {
if (caption.action === 'CREATE' || caption.action === 'UPDATE') {
removeElementFromArray(this.videoCaptions, caption)
return
}

View file

@ -6,6 +6,7 @@ import { SharedMainModule } from '@app/shared/shared-main'
import { SharedVideoLiveModule } from '@app/shared/shared-video-live'
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component'
import { VideoEditComponent } from './video-edit.component'
@NgModule({
@ -20,7 +21,8 @@ import { VideoEditComponent } from './video-edit.component'
declarations: [
VideoEditComponent,
VideoCaptionAddModalComponent
VideoCaptionAddModalComponent,
VideoCaptionEditModalComponent
],
exports: [

View file

@ -14,3 +14,10 @@ export const VIDEO_CAPTION_FILE_VALIDATOR: BuildFormValidator = {
required: $localize`Video caption file is required.`
}
}
export const VIDEO_CAPTION_FILE_CONTENT_VALIDATOR: BuildFormValidator = {
VALIDATORS: [ Validators.required ],
MESSAGES: {
required: $localize`Caption content is required.`
}
}

View file

@ -4,6 +4,8 @@ export interface VideoCaptionEdit {
label?: string
}
action?: 'CREATE' | 'REMOVE'
action?: 'CREATE' | 'REMOVE' | 'UPDATE'
captionfile?: any
}
export type VideoCaptionWithPathEdit = VideoCaptionEdit & { captionPath?: string }

View file

@ -8,6 +8,7 @@ import { VideoService } from '@app/shared/shared-main/video'
import { peertubeTranslate } from '@shared/core-utils/i18n'
import { ResultList, VideoCaption } from '@shared/models'
import { VideoCaptionEdit } from './video-caption-edit.model'
import { environment } from '../../../../environments/environment'
@Injectable()
export class VideoCaptionService {
@ -57,7 +58,7 @@ export class VideoCaptionService {
let obs: Observable<any> = of(undefined)
for (const videoCaption of videoCaptions) {
if (videoCaption.action === 'CREATE') {
if (videoCaption.action === 'CREATE' || videoCaption.action === 'UPDATE') {
obs = obs.pipe(switchMap(() => this.addCaption(videoId, videoCaption.language.id, videoCaption.captionfile)))
} else if (videoCaption.action === 'REMOVE') {
obs = obs.pipe(switchMap(() => this.removeCaption(videoId, videoCaption.language.id)))
@ -66,4 +67,8 @@ export class VideoCaptionService {
return obs
}
getCaptionContent ({ captionPath }: Pick<VideoCaption, 'captionPath'>) {
return this.authHttp.get(`${environment.originServerUrl}${captionPath}`, { responseType: 'text' })
}
}