From c729caf6cc34630877a0e5a1bda1719384cd0c8a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 11 Feb 2022 10:51:33 +0100 Subject: Add basic video editor support --- .../edit-custom-config.component.ts | 3 + .../edit-vod-transcoding.component.html | 25 +++ .../edit-vod-transcoding.component.ts | 9 + .../overview/videos/video-list.component.scss | 1 + .../+my-library/my-videos/my-videos.component.ts | 8 +- client/src/app/+video-editor/edit/index.ts | 2 + .../edit/video-editor-edit.component.html | 88 +++++++++ .../edit/video-editor-edit.component.scss | 76 ++++++++ .../edit/video-editor-edit.component.ts | 202 +++++++++++++++++++++ .../edit/video-editor-edit.resolver.ts | 18 ++ client/src/app/+video-editor/index.ts | 1 + client/src/app/+video-editor/shared/index.ts | 1 + .../+video-editor/shared/video-editor.service.ts | 28 +++ .../+video-editor/video-editor-routing.module.ts | 30 +++ .../src/app/+video-editor/video-editor.module.ts | 27 +++ .../action-buttons/action-buttons.component.ts | 1 + .../shared/information/video-alert.component.html | 4 + .../shared/information/video-alert.component.ts | 4 + client/src/app/app-routing.module.ts | 6 + .../src/app/shared/shared-forms/form-reactive.ts | 2 +- .../shared/shared-forms/form-validator.service.ts | 2 +- .../shared-forms/timestamp-input.component.html | 3 +- .../shared-forms/timestamp-input.component.scss | 14 +- .../shared-forms/timestamp-input.component.ts | 2 + .../video-actions-dropdown.component.ts | 23 ++- .../video-miniature.component.ts | 4 + 26 files changed, 575 insertions(+), 9 deletions(-) create mode 100644 client/src/app/+video-editor/edit/index.ts create mode 100644 client/src/app/+video-editor/edit/video-editor-edit.component.html create mode 100644 client/src/app/+video-editor/edit/video-editor-edit.component.scss create mode 100644 client/src/app/+video-editor/edit/video-editor-edit.component.ts create mode 100644 client/src/app/+video-editor/edit/video-editor-edit.resolver.ts create mode 100644 client/src/app/+video-editor/index.ts create mode 100644 client/src/app/+video-editor/shared/index.ts create mode 100644 client/src/app/+video-editor/shared/video-editor.service.ts create mode 100644 client/src/app/+video-editor/video-editor-routing.module.ts create mode 100644 client/src/app/+video-editor/video-editor.module.ts (limited to 'client/src') diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index f2eaa3033..e3b6f8305 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts @@ -197,6 +197,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { resolutions: {} } }, + videoEditor: { + enabled: null + }, autoBlacklist: { videos: { ofUsers: { diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html index 1158f027b..2be855756 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.html @@ -192,4 +192,29 @@ + +
+
+
VIDEO EDITOR
+
+ Allows your users to edit their video (cut, add intro/outro, add a watermark etc) +
+
+ +
+ + +
+ + + ⚠️ You need to enable transcoding first to enable video editor + + +
+
+
+
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.ts index 3397c3dbd..948c10b69 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-vod-transcoding.component.ts @@ -71,6 +71,8 @@ export class EditVODTranscodingComponent implements OnInit, OnChanges { } private checkTranscodingFields () { + const transcodingControl = this.form.get('transcoding.enabled') + const videoEditorControl = this.form.get('videoEditor.enabled') const hlsControl = this.form.get('transcoding.hls.enabled') const webtorrentControl = this.form.get('transcoding.webtorrent.enabled') @@ -95,5 +97,12 @@ export class EditVODTranscodingComponent implements OnInit, OnChanges { webtorrentControl.enable() } }) + + transcodingControl.valueChanges + .subscribe(newValue => { + if (newValue === false) { + videoEditorControl.setValue(false) + } + }) } } diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss index 543cb433c..616b9bc6b 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.scss +++ b/client/src/app/+admin/overview/videos/video-list.component.scss @@ -1,5 +1,6 @@ @use '_variables' as *; @use '_mixins' as *; + my-embed { display: block; max-width: 500px; diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index 261e87f99..c998b7c49 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts @@ -9,7 +9,7 @@ import { AdvancedInputFilter } from '@app/shared/shared-forms' import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' -import { VideoChannel, VideoSortField } from '@shared/models' +import { VideoChannel, VideoSortField, VideoState } from '@shared/models' import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.component' @Component({ @@ -204,6 +204,12 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { private buildActions () { this.videoActions = [ + { + label: $localize`Editor`, + linkBuilder: ({ video }) => [ '/video-editor/edit', video.uuid ], + isDisplayed: ({ video }) => video.state.id === VideoState.PUBLISHED, + iconName: 'film' + }, { label: $localize`Display live information`, handler: ({ video }) => this.displayLiveInformation(video), 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 @@ +export * from './video-editor-edit.component' +export * 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 @@ +
+

Edit {{ video.name }}

+ +
+
+ +
+

CUT VIDEO

+ +
Set a new start/end.
+ +
+ + +
+ +
+ + +
+
+ +
+

ADD INTRO

+ +
Concatenate a file at the beginning of the video.
+ +
+ +
+
+ +
+

ADD OUTRO

+ +
Concatenate a file at the end of the video.
+ +
+ +
+
+ +
+

ADD WATERMARK

+ +
Add a watermark image to the video.
+ +
+ +
+
+ + +
+ + +
+
+ + +
+ +
+ + +
    +
  1. {{ task }}
  2. +
+
+
+
+
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 @@ +@use '_variables' as *; +@use '_mixins' as *; + +.columns { + display: flex; + + .information { + width: 100%; + margin-left: 50px; + + > div { + margin-bottom: 30px; + } + + @media screen and (max-width: $small-view) { + display: none; + } + } +} + +h1 { + font-size: 20px; +} + +h2 { + font-weight: $font-bold; + font-size: 16px; + color: pvar(--mainColor); + background-color: pvar(--mainBackgroundColor); + padding: 0 5px; + width: fit-content; + margin: -8px 0 0; +} + +.section { + $min-width: 600px; + + @include padding-left(10px); + + min-width: $min-width; + + margin-bottom: 50px; + border: 1px solid $separator-border-color; + border-radius: 5px; + width: fit-content; + + .form-group, + .description { + @include margin-left(5px); + } + + .description { + color: pvar(--greyForegroundColor); + margin-top: 5px; + margin-bottom: 15px; + } + + @media screen and (max-width: $min-width) { + min-width: none; + } +} + +my-timestamp-input { + display: block; +} + +my-embed { + display: block; + max-width: 500px; + width: 100%; +} + +my-reactive-file { + display: block; + width: fit-content; +} 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 @@ +import { Component, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { ConfirmService, Notifier, ServerService } from '@app/core' +import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' +import { Video, VideoDetails } from '@app/shared/shared-main' +import { LoadingBarService } from '@ngx-loading-bar/core' +import { secondsToTime } from '@shared/core-utils' +import { VideoEditorTask, VideoEditorTaskCut } from '@shared/models' +import { VideoEditorService } from '../shared' + +@Component({ + selector: 'my-video-editor-edit', + templateUrl: './video-editor-edit.component.html', + styleUrls: [ './video-editor-edit.component.scss' ] +}) +export class VideoEditorEditComponent extends FormReactive implements OnInit { + isRunningEdition = false + + video: VideoDetails + + constructor ( + protected formValidatorService: FormValidatorService, + private serverService: ServerService, + private notifier: Notifier, + private router: Router, + private route: ActivatedRoute, + private videoEditorService: VideoEditorService, + private loadingBar: LoadingBarService, + private confirmService: ConfirmService + ) { + super() + } + + ngOnInit () { + this.video = this.route.snapshot.data.video + + const defaultValues = { + cut: { + start: 0, + end: this.video.duration + } + } + + this.buildForm({ + cut: { + start: null, + end: null + }, + 'add-intro': { + file: null + }, + 'add-outro': { + file: null + }, + 'add-watermark': { + file: null + } + }, defaultValues) + } + + get videoExtensions () { + return this.serverService.getHTMLConfig().video.file.extensions + } + + get imageExtensions () { + return this.serverService.getHTMLConfig().video.image.extensions + } + + async runEdition () { + if (this.isRunningEdition) return + + const title = $localize`Are you sure you want to edit "${this.video.name}"?` + const listHTML = this.getTasksSummary().map(t => `
  • ${t}
  • `).join('') + + // eslint-disable-next-line max-len + const confirmHTML = $localize`The current video will be overwritten by this edited video and you won't be able to recover it.

    ` + + $localize`As a reminder, the following tasks will be executed:
      ${listHTML}
    ` + + if (await this.confirmService.confirm(confirmHTML, title) !== true) return + + this.isRunningEdition = true + + const tasks = this.buildTasks() + + this.loadingBar.useRef().start() + + return this.videoEditorService.editVideo(this.video.uuid, tasks) + .subscribe({ + next: () => { + this.notifier.success($localize`Video updated.`) + this.router.navigateByUrl(Video.buildWatchUrl(this.video)) + }, + + error: err => { + this.loadingBar.useRef().complete() + this.isRunningEdition = false + this.notifier.error(err.message) + console.error(err) + } + }) + } + + getIntroOutroTooltip () { + return $localize`(extensions: ${this.videoExtensions.join(', ')})` + } + + getWatermarkTooltip () { + return $localize`(extensions: ${this.imageExtensions.join(', ')})` + } + + noEdition () { + return this.buildTasks().length === 0 + } + + getTasksSummary () { + const tasks = this.buildTasks() + + return tasks.map(t => { + if (t.name === 'add-intro') { + return $localize`"${this.getFilename(t.options.file)}" will be added at the beggining of the video` + } + + if (t.name === 'add-outro') { + return $localize`"${this.getFilename(t.options.file)}" will be added at the end of the video` + } + + if (t.name === 'add-watermark') { + return $localize`"${this.getFilename(t.options.file)}" image watermark will be added to the video` + } + + if (t.name === 'cut') { + const { start, end } = t.options + + if (start !== undefined && end !== undefined) { + return $localize`Video will begin at ${secondsToTime(start)} and stop at ${secondsToTime(end)}` + } + + if (start !== undefined) { + return $localize`Video will begin at ${secondsToTime(start)}` + } + + if (end !== undefined) { + return $localize`Video will stop at ${secondsToTime(end)}` + } + } + + return '' + }) + } + + private getFilename (obj: any) { + return obj.name + } + + private buildTasks () { + const tasks: VideoEditorTask[] = [] + const value = this.form.value + + const cut = value['cut'] + if (cut['start'] !== 0 || cut['end'] !== this.video.duration) { + + const options: VideoEditorTaskCut['options'] = {} + if (cut['start'] !== 0) options.start = cut['start'] + if (cut['end'] !== this.video.duration) options.end = cut['end'] + + tasks.push({ + name: 'cut', + options + }) + } + + if (value['add-intro']?.['file']) { + tasks.push({ + name: 'add-intro', + options: { + file: value['add-intro']['file'] + } + }) + } + + if (value['add-outro']?.['file']) { + tasks.push({ + name: 'add-outro', + options: { + file: value['add-outro']['file'] + } + }) + } + + if (value['add-watermark']?.['file']) { + tasks.push({ + name: 'add-watermark', + options: { + file: value['add-watermark']['file'] + } + }) + } + + return tasks + } + +} 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 @@ + +import { Injectable } from '@angular/core' +import { ActivatedRouteSnapshot, Resolve } from '@angular/router' +import { VideoService } from '@app/shared/shared-main' + +@Injectable() +export class VideoEditorEditResolver implements Resolve { + constructor ( + private videoService: VideoService + ) { + } + + resolve (route: ActivatedRouteSnapshot) { + const videoId: string = route.params['videoId'] + + return this.videoService.getVideo({ videoId }) + } +} diff --git a/client/src/app/+video-editor/index.ts b/client/src/app/+video-editor/index.ts new file mode 100644 index 000000000..5a9e9fdd0 --- /dev/null +++ b/client/src/app/+video-editor/index.ts @@ -0,0 +1 @@ +export * from './video-editor.module' diff --git a/client/src/app/+video-editor/shared/index.ts b/client/src/app/+video-editor/shared/index.ts new file mode 100644 index 000000000..eaf88b6f4 --- /dev/null +++ b/client/src/app/+video-editor/shared/index.ts @@ -0,0 +1 @@ +export * from './video-editor.service' diff --git a/client/src/app/+video-editor/shared/video-editor.service.ts b/client/src/app/+video-editor/shared/video-editor.service.ts new file mode 100644 index 000000000..5b7053039 --- /dev/null +++ b/client/src/app/+video-editor/shared/video-editor.service.ts @@ -0,0 +1,28 @@ +import { catchError } from 'rxjs' +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' +import { RestExtractor } from '@app/core' +import { objectToFormData } from '@app/helpers' +import { VideoService } from '@app/shared/shared-main' +import { VideoEditorCreateEdition, VideoEditorTask } from '@shared/models' + +@Injectable() +export class VideoEditorService { + + constructor ( + private authHttp: HttpClient, + private restExtractor: RestExtractor + ) {} + + editVideo (videoId: number | string, tasks: VideoEditorTask[]) { + const url = VideoService.BASE_VIDEO_URL + '/' + videoId + '/editor/edit' + const body: VideoEditorCreateEdition = { + tasks + } + + const data = objectToFormData(body) + + return this.authHttp.post(url, data) + .pipe(catchError(err => this.restExtractor.handleError(err))) + } +} diff --git a/client/src/app/+video-editor/video-editor-routing.module.ts b/client/src/app/+video-editor/video-editor-routing.module.ts new file mode 100644 index 000000000..9f37a0dae --- /dev/null +++ b/client/src/app/+video-editor/video-editor-routing.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core' +import { RouterModule, Routes } from '@angular/router' +import { VideoEditorEditResolver } from './edit' +import { VideoEditorEditComponent } from './edit/video-editor-edit.component' + +const videoEditorRoutes: Routes = [ + { + path: '', + children: [ + { + path: 'edit/:videoId', + component: VideoEditorEditComponent, + data: { + meta: { + title: $localize`Edit video` + } + }, + resolve: { + video: VideoEditorEditResolver + } + } + ] + } +] + +@NgModule({ + imports: [ RouterModule.forChild(videoEditorRoutes) ], + exports: [ RouterModule ] +}) +export class VideoEditorRoutingModule {} diff --git a/client/src/app/+video-editor/video-editor.module.ts b/client/src/app/+video-editor/video-editor.module.ts new file mode 100644 index 000000000..7bbebc17b --- /dev/null +++ b/client/src/app/+video-editor/video-editor.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core' +import { SharedFormModule } from '@app/shared/shared-forms' +import { SharedMainModule } from '@app/shared/shared-main' +import { VideoEditorEditComponent, VideoEditorEditResolver } from './edit' +import { VideoEditorService } from './shared' +import { VideoEditorRoutingModule } from './video-editor-routing.module' + +@NgModule({ + imports: [ + VideoEditorRoutingModule, + + SharedMainModule, + SharedFormModule + ], + + declarations: [ + VideoEditorEditComponent + ], + + exports: [], + + providers: [ + VideoEditorService, + VideoEditorEditResolver + ] +}) +export class VideoEditorModule { } diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts index e59238ffe..6e8a64f46 100644 --- a/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts +++ b/client/src/app/+videos/+video-watch/shared/action-buttons/action-buttons.component.ts @@ -35,6 +35,7 @@ export class ActionButtonsComponent implements OnInit, OnChanges { playlist: false, download: true, update: true, + editor: true, blacklist: true, delete: true, report: true, diff --git a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html index 0c4d46714..c6ffb1abd 100644 --- a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html +++ b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.html @@ -14,6 +14,10 @@ The video is being transcoded, it may not work properly. +
    + The video is being edited, it may not work properly. +
    +
    The video is being moved to an external server, it may not work properly.
    diff --git a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts index a3d3fa6fb..79b56705f 100644 --- a/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts +++ b/client/src/app/+videos/+video-watch/shared/information/video-alert.component.ts @@ -14,6 +14,10 @@ export class VideoAlertComponent { return this.video && this.video.state.id === VideoState.TO_TRANSCODE } + isVideoToEdit () { + return this.video && this.video.state.id === VideoState.TO_EDIT + } + isVideoTranscodingFailed () { return this.video && this.video.state.id === VideoState.TRANSCODING_FAILED } diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index b5afc9c92..cd499845b 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -143,6 +143,12 @@ const routes: Routes = [ canActivateChild: [ MetaGuard ] }, + { + path: 'video-editor', + loadChildren: () => import('./+video-editor/video-editor.module').then(m => m.VideoEditorModule), + canActivateChild: [ MetaGuard ] + }, + // Matches /@:actorName { matcher: (url): UrlMatchResult => { diff --git a/client/src/app/shared/shared-forms/form-reactive.ts b/client/src/app/shared/shared-forms/form-reactive.ts index 07a12c6f6..6b3a6c773 100644 --- a/client/src/app/shared/shared-forms/form-reactive.ts +++ b/client/src/app/shared/shared-forms/form-reactive.ts @@ -24,7 +24,7 @@ export abstract class FormReactive { this.formErrors = formErrors this.validationMessages = validationMessages - this.form.statusChanges.subscribe(async status => { + this.form.statusChanges.subscribe(async () => { // FIXME: remove when https://github.com/angular/angular/issues/41519 is fixed await this.waitPendingCheck() diff --git a/client/src/app/shared/shared-forms/form-validator.service.ts b/client/src/app/shared/shared-forms/form-validator.service.ts index 0fe50ac9b..f67d5bb33 100644 --- a/client/src/app/shared/shared-forms/form-validator.service.ts +++ b/client/src/app/shared/shared-forms/form-validator.service.ts @@ -30,7 +30,7 @@ export class FormValidatorService { if (field?.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string } - const defaultValue = defaultValues[name] || '' + const defaultValue = defaultValues[name] ?? '' if (field?.VALIDATORS) group[name] = [ defaultValue, field.VALIDATORS ] else group[name] = [ defaultValue ] diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.html b/client/src/app/shared/shared-forms/timestamp-input.component.html index c57a4b32c..c89a7b019 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.html +++ b/client/src/app/shared/shared-forms/timestamp-input.component.html @@ -1,4 +1,5 @@ diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.scss b/client/src/app/shared/shared-forms/timestamp-input.component.scss index d2358c027..27d6fa173 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.scss +++ b/client/src/app/shared/shared-forms/timestamp-input.component.scss @@ -1,10 +1,10 @@ @use '_variables' as *; +@use '_mixins' as *; p-inputmask { ::ng-deep input { width: 80px; font-size: 15px; - border: 0; &:focus-within, &:focus { @@ -16,4 +16,16 @@ p-inputmask { opacity: 0.5; } } + + &.border-disabled { + ::ng-deep input { + border: 0; + } + } + + &:not(.border-disabled) { + ::ng-deep input { + @include peertube-input-text(80px); + } + } } diff --git a/client/src/app/shared/shared-forms/timestamp-input.component.ts b/client/src/app/shared/shared-forms/timestamp-input.component.ts index 3fc705905..79ca63673 100644 --- a/client/src/app/shared/shared-forms/timestamp-input.component.ts +++ b/client/src/app/shared/shared-forms/timestamp-input.component.ts @@ -18,6 +18,8 @@ export class TimestampInputComponent implements ControlValueAccessor, OnInit { @Input() maxTimestamp: number @Input() timestamp: number @Input() disabled = false + @Input() inputName: string + @Input() disableBorder = true @Output() inputBlur = new EventEmitter() diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts index c2a318285..abbfc63f8 100644 --- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts @@ -1,8 +1,8 @@ import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core' -import { AuthService, ConfirmService, Notifier, ScreenService } from '@app/core' +import { AuthService, ConfirmService, Notifier, ScreenService, ServerService } from '@app/core' import { BlocklistService, VideoBlockComponent, VideoBlockService, VideoReportComponent } from '@app/shared/shared-moderation' import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' -import { VideoCaption } from '@shared/models' +import { VideoCaption, VideoState } from '@shared/models' import { Actor, DropdownAction, @@ -29,6 +29,7 @@ export type VideoActionsDisplayType = { liveInfo?: boolean removeFiles?: boolean transcoding?: boolean + editor?: boolean } @Component({ @@ -59,7 +60,8 @@ export class VideoActionsDropdownComponent implements OnChanges { mute: true, liveInfo: false, removeFiles: false, - transcoding: false + transcoding: false, + editor: true } @Input() placement = 'left' @@ -89,7 +91,8 @@ export class VideoActionsDropdownComponent implements OnChanges { private videoBlocklistService: VideoBlockService, private screenService: ScreenService, private videoService: VideoService, - private redundancyService: RedundancyService + private redundancyService: RedundancyService, + private serverService: ServerService ) { } get user () { @@ -149,6 +152,12 @@ export class VideoActionsDropdownComponent implements OnChanges { return this.video.isUpdatableBy(this.user) } + isVideoEditable () { + return this.serverService.getHTMLConfig().videoEditor.enabled && + this.video.state?.id === VideoState.PUBLISHED && + this.video.isUpdatableBy(this.user) + } + isVideoRemovable () { return this.video.isRemovableBy(this.user) } @@ -329,6 +338,12 @@ export class VideoActionsDropdownComponent implements OnChanges { iconName: 'edit', isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.update && this.isVideoUpdatable() }, + { + label: $localize`Editor`, + linkBuilder: ({ video }) => [ '/video-editor/edit', video.uuid ], + iconName: 'film', + isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.editor && this.isVideoEditable() + }, { label: $localize`Block`, handler: () => this.showBlockModal(), diff --git a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts index 847e401ed..7de9fc8e2 100644 --- a/client/src/app/shared/shared-video-miniature/video-miniature.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-miniature.component.ts @@ -195,6 +195,10 @@ export class VideoMiniatureComponent implements OnInit { return $localize`To import` } + if (video.state.id === VideoState.TO_EDIT) { + return $localize`To edit` + } + return '' } -- cgit v1.2.3