Support audio upload in client

This commit is contained in:
Chocobozzz 2019-05-17 10:45:53 +02:00
parent 536598cfaf
commit 7b992a86b1
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
19 changed files with 192 additions and 107 deletions

View File

@ -57,10 +57,12 @@
</div>
<div class="form-group">
<my-image-upload
i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
previewWidth="200px" previewHeight="110px"
></my-image-upload>
<label i18n>Playlist thumbnail</label>
<my-preview-upload
i18n-inputLabel inputLabel="Edit" inputName="thumbnailfile" formControlName="thumbnailfile"
previewWidth="223px" previewHeight="122px"
></my-preview-upload>
</div>
</div>
</div>

View File

@ -1,6 +1,4 @@
import { FormReactive } from '@app/shared'
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
import { ServerService } from '@app/core'
import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model'
export abstract class MyAccountVideoPlaylistEdit extends FormReactive {

View File

@ -4,18 +4,6 @@
.action-button {
@include peertube-button-link;
@include button-with-icon(21px, 0, -2px);
font-weight: $font-semibold;
color: $grey-foreground-color;
background-color: $grey-background-color;
&:hover {
background-color: $grey-background-hover-color;
}
my-global-icon {
@include apply-svg-color($grey-foreground-color);
}
}
// In a table, try to minimize the space taken by this button

View File

@ -9,7 +9,7 @@ import { GlobalIconName } from '@app/shared/images/global-icon.component'
export class ButtonComponent {
@Input() label = ''
@Input() className: string = undefined
@Input() className: 'orange-button' | 'grey-button' = 'grey-button'
@Input() icon: GlobalIconName = undefined
@Input() title: string = undefined

View File

@ -1,6 +1,9 @@
<div class="root">
<div class="button-file">
<div class="button-file" [ngClass]="{ 'with-icon': !!icon }">
<my-global-icon *ngIf="icon" [iconName]="icon"></my-global-icon>
<span>{{ inputLabel }}</span>
<input
type="file"
[name]="inputName" [id]="inputName" [accept]="extensions"
@ -8,7 +11,5 @@
/>
</div>
<div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxFileSize | bytes }})</div>
<div class="filename" *ngIf="displayFilename === true && filename">{{ filename }}</div>
</div>

View File

@ -8,13 +8,11 @@
.button-file {
@include peertube-button-file(auto);
@include grey-button;
min-width: 190px;
&.with-icon {
@include button-with-icon;
}
.file-constraints {
margin-left: 5px;
font-size: 13px;
}
.filename {

View File

@ -2,6 +2,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@ang
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { Notifier } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { GlobalIconName } from '@app/shared/images/global-icon.component'
@Component({
selector: 'my-reactive-file',
@ -21,6 +22,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
@Input() extensions: string[] = []
@Input() maxFileSize: number
@Input() displayFilename = false
@Input() icon: GlobalIconName
@Output() fileChanged = new EventEmitter<Blob>()

View File

@ -1,9 +0,0 @@
<div class="root">
<my-reactive-file
[inputName]="inputName" [inputLabel]="inputLabel" [extensions]="videoImageExtensions" [maxFileSize]="maxVideoImageSize"
(fileChanged)="onFileChanged($event)"
></my-reactive-file>
<img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" />
<div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div>
</div>

View File

@ -1,18 +0,0 @@
@import '_variables';
@import '_mixins';
.root {
height: auto;
display: flex;
align-items: center;
.preview {
border: 2px solid grey;
border-radius: 4px;
margin-left: 50px;
&.no-image {
background-color: #ececec;
}
}
}

View File

@ -0,0 +1,13 @@
<div class="root">
<div class="preview-container">
<my-reactive-file
[inputName]="inputName" [inputLabel]="inputLabel" [extensions]="videoImageExtensions" [maxFileSize]="maxVideoImageSize"
icon="edit" (fileChanged)="onFileChanged($event)"
></my-reactive-file>
<img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" />
<div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div>
</div>
<div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxVideoImageSize | bytes }})</div>
</div>

View File

@ -0,0 +1,27 @@
@import '_variables';
@import '_mixins';
.root {
height: auto;
display: flex;
flex-direction: column;
.preview-container {
position: relative;
my-reactive-file {
position: absolute;
bottom: 10px;
left: 10px;
}
.preview {
border: 2px solid grey;
border-radius: 4px;
&.no-image {
background-color: #ececec;
}
}
}
}

View File

@ -1,27 +1,28 @@
import { Component, forwardRef, Input } from '@angular/core'
import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
import { ServerService } from '@app/core'
@Component({
selector: 'my-image-upload',
styleUrls: [ './image-upload.component.scss' ],
templateUrl: './image-upload.component.html',
selector: 'my-preview-upload',
styleUrls: [ './preview-upload.component.scss' ],
templateUrl: './preview-upload.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ImageUploadComponent),
useExisting: forwardRef(() => PreviewUploadComponent),
multi: true
}
]
})
export class ImageUploadComponent implements ControlValueAccessor {
export class PreviewUploadComponent implements OnInit, ControlValueAccessor {
@Input() inputLabel: string
@Input() inputName: string
@Input() previewWidth: string
@Input() previewHeight: string
imageSrc: SafeResourceUrl
allowedExtensionsMessage = ''
private file: File
@ -38,6 +39,10 @@ export class ImageUploadComponent implements ControlValueAccessor {
return this.serverService.getConfig().video.image.size.max
}
ngOnInit () {
this.allowedExtensionsMessage = this.videoImageExtensions.join(', ')
}
onFileChanged (file: File) {
this.file = file

View File

@ -69,7 +69,7 @@ import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/sha
import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
import { ImageUploadComponent } from '@app/shared/images/image-upload.component'
import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
@ -154,7 +154,7 @@ import { ClipboardModule } from 'ngx-clipboard'
ConfirmComponent,
GlobalIconComponent,
ImageUploadComponent
PreviewUploadComponent
],
exports: [
@ -218,7 +218,7 @@ import { ClipboardModule } from 'ngx-clipboard'
ConfirmComponent,
GlobalIconComponent,
ImageUploadComponent,
PreviewUploadComponent,
NumberFormatterPipe,
ObjectLengthPipe,

View File

@ -85,6 +85,11 @@ export class VideoEdit implements VideoUpdate {
const originallyPublishedAt = new Date(values['originallyPublishedAt'])
this.originallyPublishedAt = originallyPublishedAt.toISOString()
}
// Use the same file than the preview for the thumbnail
if (this.previewfile) {
this.thumbnailfile = this.previewfile
}
}
toFormPatch () {

View File

@ -187,18 +187,14 @@
<ng-template ngbTabContent>
<div class="row advanced-settings">
<div class="col-md-12 col-xl-8">
<div class="form-group">
<my-image-upload
i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
previewWidth="200px" previewHeight="110px"
></my-image-upload>
</div>
<div class="form-group">
<my-image-upload
i18n-inputLabel inputLabel="Upload preview" inputName="previewfile" formControlName="previewfile"
<label i18n for="previewfile">Video preview</label>
<my-preview-upload
i18n-inputLabel inputLabel="Edit" inputName="previewfile" formControlName="previewfile"
previewWidth="360px" previewHeight="200px"
></my-image-upload>
></my-preview-upload>
</div>
<div class="form-group">

View File

@ -100,7 +100,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
language: this.videoValidatorsService.VIDEO_LANGUAGE,
description: this.videoValidatorsService.VIDEO_DESCRIPTION,
tags: null,
thumbnailfile: null,
previewfile: null,
support: this.videoValidatorsService.VIDEO_SUPPORT,
schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT,

View File

@ -26,6 +26,27 @@
</select>
</div>
</div>
<ng-container *ngIf="isUploadingAudioFile">
<div class="form-group audio-preview">
<label i18n for="previewfileUpload">Video background image</label>
<div i18n class="audio-image-info">
Image that will be merged with your audio file.
<br />
The chosen image will be definitive and cannot be modified.
</div>
<my-preview-upload
i18n-inputLabel inputLabel="Edit" inputName="previewfileUpload" [(ngModel)]="previewfileUpload"
previewWidth="360px" previewHeight="200px"
></my-preview-upload>
</div>
<div class="form-group upload-audio-button">
<my-button className="orange-button" i18n-label [label]="getAudioUploadLabel()" icon="upload" (click)="uploadFirstStep(true)"></my-button>
</div>
</ng-container>
</div>
</div>

View File

@ -1,9 +1,20 @@
@import 'variables';
@import 'mixins';
.first-step-block .form-group-channel {
.first-step-block {
.form-group-channel {
margin-bottom: 20px;
margin-top: 35px;
}
.audio-image-info {
margin-bottom: 10px;
}
.audio-preview {
margin: 30px 0;
}
}
.upload-progress-cancel {

View File

@ -35,8 +35,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
userVideoQuotaUsed = 0
userVideoQuotaUsedDaily = 0
isUploadingAudioFile = false
isUploadingVideo = false
isUpdatingVideo = false
videoUploaded = false
videoUploadObservable: Subscription = null
videoUploadPercents = 0
@ -44,7 +46,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
id: 0,
uuid: ''
}
waitTranscodingEnabled = true
previewfileUpload: File
error: string
@ -100,6 +104,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
}
getVideoFile () {
return this.videofileInput.nativeElement.files[0]
}
getAudioUploadLabel () {
const videofile = this.getVideoFile()
if (!videofile) return this.i18n('Upload')
return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name })
}
fileChange () {
this.uploadFirstStep()
}
@ -114,38 +129,15 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
}
uploadFirstStep () {
const videofile = this.videofileInput.nativeElement.files[0]
uploadFirstStep (clickedOnButton = false) {
const videofile = this.getVideoFile()
if (!videofile) return
// Check global user quota
const bytePipes = new BytesPipe()
const videoQuota = this.authService.getUser().videoQuota
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
const msg = this.i18n(
'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
{
videoSize: bytePipes.transform(videofile.size, 0),
videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
videoQuota: bytePipes.transform(videoQuota, 0)
}
)
this.notifier.error(msg)
return
}
if (!this.checkGlobalUserQuota(videofile)) return
if (!this.checkDailyUserQuota(videofile)) return
// Check daily user quota
const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
const msg = this.i18n(
'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
{
videoSize: bytePipes.transform(videofile.size, 0),
quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
}
)
this.notifier.error(msg)
if (clickedOnButton === false && this.isAudioFile(videofile.name)) {
this.isUploadingAudioFile = true
return
}
@ -180,6 +172,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
formData.append('channelId', '' + channelId)
formData.append('videofile', videofile)
if (this.previewfileUpload) {
formData.append('previewfile', this.previewfileUpload)
formData.append('thumbnailfile', this.previewfileUpload)
}
this.isUploadingVideo = true
this.firstStepDone.emit(name)
@ -187,7 +184,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
name,
privacy,
nsfw,
channelId
channelId,
previewfile: this.previewfileUpload
})
this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
@ -251,4 +249,52 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
}
)
}
private checkGlobalUserQuota (videofile: File) {
const bytePipes = new BytesPipe()
// Check global user quota
const videoQuota = this.authService.getUser().videoQuota
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
const msg = this.i18n(
'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
{
videoSize: bytePipes.transform(videofile.size, 0),
videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
videoQuota: bytePipes.transform(videoQuota, 0)
}
)
this.notifier.error(msg)
return false
}
return true
}
private checkDailyUserQuota (videofile: File) {
const bytePipes = new BytesPipe()
// Check daily user quota
const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
const msg = this.i18n(
'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
{
videoSize: bytePipes.transform(videofile.size, 0),
quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
}
)
this.notifier.error(msg)
return false
}
return true
}
private isAudioFile (filename: string) {
return filename.endsWith('.mp3') || filename.endsWith('.flac') || filename.endsWith('.ogg')
}
}