Support audio upload in client
authorChocobozzz <me@florianbigard.com>
Fri, 17 May 2019 08:45:53 +0000 (10:45 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 17 May 2019 08:45:53 +0000 (10:45 +0200)
20 files changed:
client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
client/src/app/shared/buttons/button.component.scss
client/src/app/shared/buttons/button.component.ts
client/src/app/shared/forms/reactive-file.component.html
client/src/app/shared/forms/reactive-file.component.scss
client/src/app/shared/forms/reactive-file.component.ts
client/src/app/shared/images/image-upload.component.html [deleted file]
client/src/app/shared/images/image-upload.component.scss [deleted file]
client/src/app/shared/images/image-upload.component.ts [deleted file]
client/src/app/shared/images/preview-upload.component.html [new file with mode: 0644]
client/src/app/shared/images/preview-upload.component.scss [new file with mode: 0644]
client/src/app/shared/images/preview-upload.component.ts [new file with mode: 0644]
client/src/app/shared/shared.module.ts
client/src/app/shared/video/video-edit.model.ts
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts

index 303fc46f7033942268105c8c8ddfbf13df2ea8b4..82321459f5673bc70b717c030d04980f03baa5d8 100644 (file)
       </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>
index fbfb4c8f7acbcb2ff20b60c6061553ab315d2d04..81dd9a75fe40d759f24a5276ed896e3e9ec8bc49 100644 (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 {
index 04199a2a95c31e3dea21078dc36951405fae7157..7ec77f4c956374f44ef7f1ee353cc05dba740e96 100644 (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
index c2b69d31a618730de8c7a6f27409614a39ec90d7..6d34d07f4697ef4db120726c141b088757cc1fa1 100644 (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
 
index 7d691059d4fb8d6d3f6ff7398e56e73a74ce4bb0..f6bf5f9aeee207a3812380d717cd8d9c4abd4260 100644 (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>
index d89844264d84187cb984969fe05532d55e1a8dbb..84c23c1d63037694eb79e11576ce5cf31aeb796a 100644 (file)
@@ -8,13 +8,11 @@
 
   .button-file {
     @include peertube-button-file(auto);
+    @include grey-button;
 
-    min-width: 190px;
-  }
-
-  .file-constraints {
-    margin-left: 5px;
-    font-size: 13px;
+    &.with-icon {
+      @include button-with-icon;
+    }
   }
 
   .filename {
index f60c38e8de8052a4b23d20547fc634906975b161..b7a821d4ffc0b2b7cb730760813c3167b173d395 100644 (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>()
 
diff --git a/client/src/app/shared/images/image-upload.component.html b/client/src/app/shared/images/image-upload.component.html
deleted file mode 100644 (file)
index c09c862..0000000
+++ /dev/null
@@ -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>
diff --git a/client/src/app/shared/images/image-upload.component.scss b/client/src/app/shared/images/image-upload.component.scss
deleted file mode 100644 (file)
index b63963b..0000000
+++ /dev/null
@@ -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;
-    }
-  }
-}
diff --git a/client/src/app/shared/images/image-upload.component.ts b/client/src/app/shared/images/image-upload.component.ts
deleted file mode 100644 (file)
index 2da1592..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Component, forwardRef, Input } 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',
-  providers: [
-    {
-      provide: NG_VALUE_ACCESSOR,
-      useExisting: forwardRef(() => ImageUploadComponent),
-      multi: true
-    }
-  ]
-})
-export class ImageUploadComponent implements ControlValueAccessor {
-  @Input() inputLabel: string
-  @Input() inputName: string
-  @Input() previewWidth: string
-  @Input() previewHeight: string
-
-  imageSrc: SafeResourceUrl
-
-  private file: File
-
-  constructor (
-    private sanitizer: DomSanitizer,
-    private serverService: ServerService
-  ) {}
-
-  get videoImageExtensions () {
-    return this.serverService.getConfig().video.image.extensions
-  }
-
-  get maxVideoImageSize () {
-    return this.serverService.getConfig().video.image.size.max
-  }
-
-  onFileChanged (file: File) {
-    this.file = file
-
-    this.propagateChange(this.file)
-    this.updatePreview()
-  }
-
-  propagateChange = (_: any) => { /* empty */ }
-
-  writeValue (file: any) {
-    this.file = file
-    this.updatePreview()
-  }
-
-  registerOnChange (fn: (_: any) => void) {
-    this.propagateChange = fn
-  }
-
-  registerOnTouched () {
-    // Unused
-  }
-
-  private updatePreview () {
-    if (this.file) {
-      const url = URL.createObjectURL(this.file)
-      this.imageSrc = this.sanitizer.bypassSecurityTrustResourceUrl(url)
-    }
-  }
-}
diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/images/preview-upload.component.html
new file mode 100644 (file)
index 0000000..5e1d521
--- /dev/null
@@ -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>
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/images/preview-upload.component.scss
new file mode 100644 (file)
index 0000000..2570602
--- /dev/null
@@ -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;
+      }
+    }
+  }
+}
diff --git a/client/src/app/shared/images/preview-upload.component.ts b/client/src/app/shared/images/preview-upload.component.ts
new file mode 100644 (file)
index 0000000..44b7886
--- /dev/null
@@ -0,0 +1,74 @@
+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-preview-upload',
+  styleUrls: [ './preview-upload.component.scss' ],
+  templateUrl: './preview-upload.component.html',
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => PreviewUploadComponent),
+      multi: true
+    }
+  ]
+})
+export class PreviewUploadComponent implements OnInit, ControlValueAccessor {
+  @Input() inputLabel: string
+  @Input() inputName: string
+  @Input() previewWidth: string
+  @Input() previewHeight: string
+
+  imageSrc: SafeResourceUrl
+  allowedExtensionsMessage = ''
+
+  private file: File
+
+  constructor (
+    private sanitizer: DomSanitizer,
+    private serverService: ServerService
+  ) {}
+
+  get videoImageExtensions () {
+    return this.serverService.getConfig().video.image.extensions
+  }
+
+  get maxVideoImageSize () {
+    return this.serverService.getConfig().video.image.size.max
+  }
+
+  ngOnInit () {
+    this.allowedExtensionsMessage = this.videoImageExtensions.join(', ')
+  }
+
+  onFileChanged (file: File) {
+    this.file = file
+
+    this.propagateChange(this.file)
+    this.updatePreview()
+  }
+
+  propagateChange = (_: any) => { /* empty */ }
+
+  writeValue (file: any) {
+    this.file = file
+    this.updatePreview()
+  }
+
+  registerOnChange (fn: (_: any) => void) {
+    this.propagateChange = fn
+  }
+
+  registerOnTouched () {
+    // Unused
+  }
+
+  private updatePreview () {
+    if (this.file) {
+      const url = URL.createObjectURL(this.file)
+      this.imageSrc = this.sanitizer.bypassSecurityTrustResourceUrl(url)
+    }
+  }
+}
index ded65653f224ee1e93c7cbd1c289dbe62b49d6c7..39f1a69e2223fccc8de326989c36478d256a98dd 100644 (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,
index 1f633d427a4d0e0d08077eb1837384bd713f5919..67d8e7711d550da06e9dae5a24b3ca98b964b058 100644 (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 () {
index 99695204dd00b9c4cb5f993976fa2d80743bfe6a..28572d611e42a61278462c26d5f0b6287e9ebda7 100644 (file)
       <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">
index c80efd80272f1d371ee38fdff769a9e513c7aace..95d397b528701593abebdcc1d35ee8d21a34f1f0 100644 (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,
index 536769d2fb0d2c421db2ad18b8dba6d7900a60a8..3247a2bd6c381a03a44847c8c0f7724de5659829 100644 (file)
         </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>
 
index 8adf8f169c596033ee9deea1b2087893c0c9f946..684342f09311521e4983c8e48aa762f518bc0e90 100644 (file)
@@ -1,9 +1,20 @@
 @import 'variables';
 @import 'mixins';
 
-.first-step-block .form-group-channel {
-  margin-bottom: 20px;
-  margin-top: 35px;
+.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 {
index d6d4bad212f842c8c7c5abbdd872f7dc9ff929bc..73de25c59484a89a179d8e62c30c114666acb7da 100644 (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')
+  }
 }