aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-07-12 19:02:00 +0200
committerChocobozzz <me@florianbigard.com>2018-07-16 11:50:08 +0200
commit40e87e9ecc54e3513fb586928330a7855eb192c6 (patch)
treeaf1111ecba85f9cd8286811ff332a67cf21be2f6 /client/src/app/shared
parentd4557fd3ecc8d4ed4fb0e5c868929bc36c959ed2 (diff)
downloadPeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.gz
PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.tar.zst
PeerTube-40e87e9ecc54e3513fb586928330a7855eb192c6.zip
Implement captions/subtitles
Diffstat (limited to 'client/src/app/shared')
-rw-r--r--client/src/app/shared/forms/form-validators/custom-config-validators.service.ts10
-rw-r--r--client/src/app/shared/forms/form-validators/index.ts1
-rw-r--r--client/src/app/shared/forms/form-validators/video-captions-validators.service.ts27
-rw-r--r--client/src/app/shared/forms/index.ts1
-rw-r--r--client/src/app/shared/forms/reactive-file.component.html14
-rw-r--r--client/src/app/shared/forms/reactive-file.component.scss24
-rw-r--r--client/src/app/shared/forms/reactive-file.component.ts75
-rw-r--r--client/src/app/shared/misc/utils.ts10
-rw-r--r--client/src/app/shared/shared.module.ts10
-rw-r--r--client/src/app/shared/video-caption/index.ts1
-rw-r--r--client/src/app/shared/video-caption/video-caption-edit.model.ts9
-rw-r--r--client/src/app/shared/video-caption/video-caption.service.ts61
-rw-r--r--client/src/app/shared/video/video.model.ts2
-rw-r--r--client/src/app/shared/video/video.service.ts4
14 files changed, 242 insertions, 7 deletions
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
index 1b36bbc6b..0c2489a9d 100644
--- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
@@ -9,6 +9,7 @@ export class CustomConfigValidatorsService {
9 readonly INSTANCE_SHORT_DESCRIPTION: BuildFormValidator 9 readonly INSTANCE_SHORT_DESCRIPTION: BuildFormValidator
10 readonly SERVICES_TWITTER_USERNAME: BuildFormValidator 10 readonly SERVICES_TWITTER_USERNAME: BuildFormValidator
11 readonly CACHE_PREVIEWS_SIZE: BuildFormValidator 11 readonly CACHE_PREVIEWS_SIZE: BuildFormValidator
12 readonly CACHE_CAPTIONS_SIZE: BuildFormValidator
12 readonly SIGNUP_LIMIT: BuildFormValidator 13 readonly SIGNUP_LIMIT: BuildFormValidator
13 readonly ADMIN_EMAIL: BuildFormValidator 14 readonly ADMIN_EMAIL: BuildFormValidator
14 readonly TRANSCODING_THREADS: BuildFormValidator 15 readonly TRANSCODING_THREADS: BuildFormValidator
@@ -44,6 +45,15 @@ export class CustomConfigValidatorsService {
44 } 45 }
45 } 46 }
46 47
48 this.CACHE_CAPTIONS_SIZE = {
49 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
50 MESSAGES: {
51 'required': this.i18n('Captions cache size is required.'),
52 'min': this.i18n('Captions cache size must be greater than 1.'),
53 'pattern': this.i18n('Captions cache size must be a number.')
54 }
55 }
56
47 this.SIGNUP_LIMIT = { 57 this.SIGNUP_LIMIT = {
48 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], 58 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
49 MESSAGES: { 59 MESSAGES: {
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
index 487683088..60d735ef7 100644
--- a/client/src/app/shared/forms/form-validators/index.ts
+++ b/client/src/app/shared/forms/form-validators/index.ts
@@ -8,3 +8,4 @@ export * from './video-abuse-validators.service'
8export * from './video-channel-validators.service' 8export * from './video-channel-validators.service'
9export * from './video-comment-validators.service' 9export * from './video-comment-validators.service'
10export * from './video-validators.service' 10export * from './video-validators.service'
11export * from './video-captions-validators.service'
diff --git a/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts b/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts
new file mode 100644
index 000000000..d1b4667bb
--- /dev/null
+++ b/client/src/app/shared/forms/form-validators/video-captions-validators.service.ts
@@ -0,0 +1,27 @@
1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms'
3import { Injectable } from '@angular/core'
4import { BuildFormValidator } from '@app/shared'
5
6@Injectable()
7export class VideoCaptionsValidatorsService {
8 readonly VIDEO_CAPTION_LANGUAGE: BuildFormValidator
9 readonly VIDEO_CAPTION_FILE: BuildFormValidator
10
11 constructor (private i18n: I18n) {
12
13 this.VIDEO_CAPTION_LANGUAGE = {
14 VALIDATORS: [ Validators.required ],
15 MESSAGES: {
16 'required': this.i18n('Video caption language is required.')
17 }
18 }
19
20 this.VIDEO_CAPTION_FILE = {
21 VALIDATORS: [ Validators.required ],
22 MESSAGES: {
23 'required': this.i18n('Video caption file is required.')
24 }
25 }
26 }
27}
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts
index 7464bb022..41c321c4c 100644
--- a/client/src/app/shared/forms/index.ts
+++ b/client/src/app/shared/forms/index.ts
@@ -1,2 +1,3 @@
1export * from './form-validators' 1export * from './form-validators'
2export * from './form-reactive' 2export * from './form-reactive'
3export * from './reactive-file.component'
diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/forms/reactive-file.component.html
new file mode 100644
index 000000000..9fb1c9e3e
--- /dev/null
+++ b/client/src/app/shared/forms/reactive-file.component.html
@@ -0,0 +1,14 @@
1<div class="root">
2 <div class="button-file">
3 <span>{{ inputLabel }}</span>
4 <input
5 type="file"
6 [name]="inputName" [id]="inputName" [accept]="extensions"
7 (change)="fileChange($event)"
8 />
9 </div>
10
11 <div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxFileSize | bytes }})</div>
12
13 <div class="filename" *ngIf="displayFilename === true && filename">{{ filename }}</div>
14</div>
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/forms/reactive-file.component.scss
new file mode 100644
index 000000000..d89844264
--- /dev/null
+++ b/client/src/app/shared/forms/reactive-file.component.scss
@@ -0,0 +1,24 @@
1@import '_variables';
2@import '_mixins';
3
4.root {
5 height: auto;
6 display: flex;
7 align-items: center;
8
9 .button-file {
10 @include peertube-button-file(auto);
11
12 min-width: 190px;
13 }
14
15 .file-constraints {
16 margin-left: 5px;
17 font-size: 13px;
18 }
19
20 .filename {
21 font-weight: $font-semibold;
22 margin-left: 5px;
23 }
24}
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts
new file mode 100644
index 000000000..f5758b643
--- /dev/null
+++ b/client/src/app/shared/forms/reactive-file.component.ts
@@ -0,0 +1,75 @@
1import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { NotificationsService } from 'angular2-notifications'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5
6@Component({
7 selector: 'my-reactive-file',
8 styleUrls: [ './reactive-file.component.scss' ],
9 templateUrl: './reactive-file.component.html',
10 providers: [
11 {
12 provide: NG_VALUE_ACCESSOR,
13 useExisting: forwardRef(() => ReactiveFileComponent),
14 multi: true
15 }
16 ]
17})
18export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
19 @Input() inputLabel: string
20 @Input() inputName: string
21 @Input() extensions: string[] = []
22 @Input() maxFileSize: number
23 @Input() displayFilename = false
24
25 @Output() fileChanged = new EventEmitter<Blob>()
26
27 allowedExtensionsMessage = ''
28
29 private file: File
30
31 constructor (
32 private notificationsService: NotificationsService,
33 private i18n: I18n
34 ) {}
35
36 get filename () {
37 if (!this.file) return ''
38
39 return this.file.name
40 }
41
42 ngOnInit () {
43 this.allowedExtensionsMessage = this.extensions.join(', ')
44 }
45
46 fileChange (event: any) {
47 if (event.target.files && event.target.files.length) {
48 const [ file ] = event.target.files
49
50 if (file.size > this.maxFileSize) {
51 this.notificationsService.error(this.i18n('Error'), this.i18n('This file is too large.'))
52 return
53 }
54
55 this.file = file
56
57 this.propagateChange(this.file)
58 this.fileChanged.emit(this.file)
59 }
60 }
61
62 propagateChange = (_: any) => { /* empty */ }
63
64 writeValue (file: any) {
65 this.file = file
66 }
67
68 registerOnChange (fn: (_: any) => void) {
69 this.propagateChange = fn
70 }
71
72 registerOnTouched () {
73 // Unused
74 }
75}
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 53aff1b24..8381745f5 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -81,7 +81,7 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
81 } 81 }
82 82
83 if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { 83 if (obj[key] !== null && typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
84 objectToFormData(obj[ key ], fd, key) 84 objectToFormData(obj[ key ], fd, formKey)
85 } else { 85 } else {
86 fd.append(formKey, obj[ key ]) 86 fd.append(formKey, obj[ key ])
87 } 87 }
@@ -96,6 +96,11 @@ function lineFeedToHtml (obj: object, keyToNormalize: string) {
96 }) 96 })
97} 97}
98 98
99function removeElementFromArray <T> (arr: T[], elem: T) {
100 const index = arr.indexOf(elem)
101 if (index !== -1) arr.splice(index, 1)
102}
103
99export { 104export {
100 objectToUrlEncoded, 105 objectToUrlEncoded,
101 getParameterByName, 106 getParameterByName,
@@ -104,5 +109,6 @@ export {
104 dateToHuman, 109 dateToHuman,
105 immutableAssign, 110 immutableAssign,
106 objectToFormData, 111 objectToFormData,
107 lineFeedToHtml 112 lineFeedToHtml,
113 removeElementFromArray
108} 114}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 97e49e7ab..c3f4bf88b 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -37,12 +37,14 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
37import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' 37import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
38import { 38import {
39 CustomConfigValidatorsService, 39 CustomConfigValidatorsService,
40 LoginValidatorsService, 40 LoginValidatorsService, ReactiveFileComponent,
41 ResetPasswordValidatorsService, 41 ResetPasswordValidatorsService,
42 UserValidatorsService, VideoAbuseValidatorsService, VideoChannelValidatorsService, VideoCommentValidatorsService, VideoValidatorsService 42 UserValidatorsService, VideoAbuseValidatorsService, VideoChannelValidatorsService, VideoCommentValidatorsService, VideoValidatorsService
43} from '@app/shared/forms' 43} from '@app/shared/forms'
44import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar' 44import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calendar'
45import { ScreenService } from '@app/shared/misc/screen.service' 45import { ScreenService } from '@app/shared/misc/screen.service'
46import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
47import { VideoCaptionService } from '@app/shared/video-caption'
46 48
47@NgModule({ 49@NgModule({
48 imports: [ 50 imports: [
@@ -74,7 +76,8 @@ import { ScreenService } from '@app/shared/misc/screen.service'
74 FromNowPipe, 76 FromNowPipe,
75 MarkdownTextareaComponent, 77 MarkdownTextareaComponent,
76 InfiniteScrollerDirective, 78 InfiniteScrollerDirective,
77 HelpComponent 79 HelpComponent,
80 ReactiveFileComponent
78 ], 81 ],
79 82
80 exports: [ 83 exports: [
@@ -102,6 +105,7 @@ import { ScreenService } from '@app/shared/misc/screen.service'
102 MarkdownTextareaComponent, 105 MarkdownTextareaComponent,
103 InfiniteScrollerDirective, 106 InfiniteScrollerDirective,
104 HelpComponent, 107 HelpComponent,
108 ReactiveFileComponent,
105 109
106 NumberFormatterPipe, 110 NumberFormatterPipe,
107 ObjectLengthPipe, 111 ObjectLengthPipe,
@@ -119,6 +123,7 @@ import { ScreenService } from '@app/shared/misc/screen.service'
119 AccountService, 123 AccountService,
120 MarkdownService, 124 MarkdownService,
121 VideoChannelService, 125 VideoChannelService,
126 VideoCaptionService,
122 127
123 FormValidatorService, 128 FormValidatorService,
124 CustomConfigValidatorsService, 129 CustomConfigValidatorsService,
@@ -129,6 +134,7 @@ import { ScreenService } from '@app/shared/misc/screen.service'
129 VideoChannelValidatorsService, 134 VideoChannelValidatorsService,
130 VideoCommentValidatorsService, 135 VideoCommentValidatorsService,
131 VideoValidatorsService, 136 VideoValidatorsService,
137 VideoCaptionsValidatorsService,
132 138
133 I18nPrimengCalendarService, 139 I18nPrimengCalendarService,
134 ScreenService, 140 ScreenService,
diff --git a/client/src/app/shared/video-caption/index.ts b/client/src/app/shared/video-caption/index.ts
new file mode 100644
index 000000000..c48a70558
--- /dev/null
+++ b/client/src/app/shared/video-caption/index.ts
@@ -0,0 +1 @@
export * from './video-caption.service'
diff --git a/client/src/app/shared/video-caption/video-caption-edit.model.ts b/client/src/app/shared/video-caption/video-caption-edit.model.ts
new file mode 100644
index 000000000..732f20158
--- /dev/null
+++ b/client/src/app/shared/video-caption/video-caption-edit.model.ts
@@ -0,0 +1,9 @@
1export interface VideoCaptionEdit {
2 language: {
3 id: string
4 label?: string
5 }
6
7 action?: 'CREATE' | 'REMOVE'
8 captionfile?: any
9}
diff --git a/client/src/app/shared/video-caption/video-caption.service.ts b/client/src/app/shared/video-caption/video-caption.service.ts
new file mode 100644
index 000000000..4ae8ebd0a
--- /dev/null
+++ b/client/src/app/shared/video-caption/video-caption.service.ts
@@ -0,0 +1,61 @@
1import { catchError, map } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { forkJoin, Observable } from 'rxjs'
5import { ResultList } from '../../../../../shared'
6import { RestExtractor, RestService } from '../rest'
7import { VideoCaption } from '../../../../../shared/models/videos/video-caption.model'
8import { VideoService } from '@app/shared/video/video.service'
9import { objectToFormData } from '@app/shared/misc/utils'
10import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
11
12@Injectable()
13export class VideoCaptionService {
14 constructor (
15 private authHttp: HttpClient,
16 private restService: RestService,
17 private restExtractor: RestExtractor
18 ) {}
19
20 listCaptions (videoId: number | string): Observable<ResultList<VideoCaption>> {
21 return this.authHttp.get<ResultList<VideoCaption>>(VideoService.BASE_VIDEO_URL + videoId + '/captions')
22 .pipe(catchError(res => this.restExtractor.handleError(res)))
23 }
24
25 removeCaption (videoId: number | string, language: string) {
26 return this.authHttp.delete(VideoService.BASE_VIDEO_URL + videoId + '/captions/' + language)
27 .pipe(
28 map(this.restExtractor.extractDataBool),
29 catchError(res => this.restExtractor.handleError(res))
30 )
31 }
32
33 addCaption (videoId: number | string, language: string, captionfile: File) {
34 const body = { captionfile }
35 const data = objectToFormData(body)
36
37 return this.authHttp.put(VideoService.BASE_VIDEO_URL + videoId + '/captions/' + language, data)
38 .pipe(
39 map(this.restExtractor.extractDataBool),
40 catchError(res => this.restExtractor.handleError(res))
41 )
42 }
43
44 updateCaptions (videoId: number | string, videoCaptions: VideoCaptionEdit[]) {
45 const observables: Observable<any>[] = []
46
47 for (const videoCaption of videoCaptions) {
48 if (videoCaption.action === 'CREATE') {
49 observables.push(
50 this.addCaption(videoId, videoCaption.language.id, videoCaption.captionfile)
51 )
52 } else if (videoCaption.action === 'REMOVE') {
53 observables.push(
54 this.removeCaption(videoId, videoCaption.language.id)
55 )
56 }
57 }
58
59 return forkJoin(observables)
60 }
61}
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 5c820a227..6b1a299ea 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -1,7 +1,7 @@
1import { User } from '../' 1import { User } from '../'
2import { Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared' 2import { Video as VideoServerModel, VideoPrivacy, VideoState } from '../../../../../shared'
3import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 3import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
4import { VideoConstant } from '../../../../../shared/models/videos/video.model' 4import { VideoConstant } from '../../../../../shared/models/videos/video-constant.model'
5import { getAbsoluteAPIUrl } from '../misc/utils' 5import { getAbsoluteAPIUrl } from '../misc/utils'
6import { ServerConfig } from '../../../../../shared/models' 6import { ServerConfig } from '../../../../../shared/models'
7import { Actor } from '@app/shared/actor/actor.model' 7import { Actor } from '@app/shared/actor/actor.model'
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 9498a06fe..b4c1f10f9 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -28,8 +28,8 @@ import { ServerService } from '@app/core'
28 28
29@Injectable() 29@Injectable()
30export class VideoService { 30export class VideoService {
31 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 31 static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
32 private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.' 32 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/videos.'
33 33
34 constructor ( 34 constructor (
35 private authHttp: HttpClient, 35 private authHttp: HttpClient,