]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
Fix lint
[github/Chocobozzz/PeerTube.git] / client / src / app / videos / +video-edit / video-add-components / video-upload.component.ts
index 3ec89ff62655a5cd14b8a2bd0b03de11d872aa41..8023459453c17328d8bae12875dbca6479b18ef3 100644 (file)
@@ -1,12 +1,11 @@
 import { HttpEventType, HttpResponse } from '@angular/common/http'
 import { HttpEventType, HttpResponse } from '@angular/common/http'
-import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
+import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
 import { Router } from '@angular/router'
 import { LoadingBarService } from '@ngx-loading-bar/core'
 import { Router } from '@angular/router'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { NotificationsService } from 'angular2-notifications'
 import { BytesPipe } from 'ngx-pipes'
 import { Subscription } from 'rxjs'
 import { VideoPrivacy } from '../../../../../../shared/models/videos'
 import { BytesPipe } from 'ngx-pipes'
 import { Subscription } from 'rxjs'
 import { VideoPrivacy } from '../../../../../../shared/models/videos'
-import { AuthService, ServerService } from '../../../core'
+import { AuthService, Notifier, ServerService } from '../../../core'
 import { VideoEdit } from '../../../shared/video/video-edit.model'
 import { VideoService } from '../../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { VideoEdit } from '../../../shared/video/video-edit.model'
 import { VideoService } from '../../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -14,26 +13,32 @@ import { VideoSend } from '@app/videos/+video-edit/video-add-components/video-se
 import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service'
 import { FormValidatorService, UserService } from '@app/shared'
 import { VideoCaptionService } from '@app/shared/video-caption'
 import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service'
 import { FormValidatorService, UserService } from '@app/shared'
 import { VideoCaptionService } from '@app/shared/video-caption'
+import { scrollToTop } from '@app/shared/misc/utils'
 
 @Component({
   selector: 'my-video-upload',
   templateUrl: './video-upload.component.html',
   styleUrls: [
     '../shared/video-edit.component.scss',
 
 @Component({
   selector: 'my-video-upload',
   templateUrl: './video-upload.component.html',
   styleUrls: [
     '../shared/video-edit.component.scss',
-    './video-upload.component.scss'
+    './video-upload.component.scss',
+    './video-send.scss'
   ]
 })
 export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate {
   @Output() firstStepDone = new EventEmitter<string>()
   ]
 })
 export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate {
   @Output() firstStepDone = new EventEmitter<string>()
-  @ViewChild('videofileInput') videofileInput
+  @Output() firstStepError = new EventEmitter<void>()
+  @ViewChild('videofileInput') videofileInput: ElementRef<HTMLInputElement>
 
   // So that it can be accessed in the template
   readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
 
   userVideoQuotaUsed = 0
 
   // So that it can be accessed in the template
   readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
 
   userVideoQuotaUsed = 0
+  userVideoQuotaUsedDaily = 0
 
 
+  isUploadingAudioFile = false
   isUploadingVideo = false
   isUpdatingVideo = false
   isUploadingVideo = false
   isUpdatingVideo = false
+
   videoUploaded = false
   videoUploadObservable: Subscription = null
   videoUploadPercents = 0
   videoUploaded = false
   videoUploadObservable: Subscription = null
   videoUploadPercents = 0
@@ -42,12 +47,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     uuid: ''
   }
 
     uuid: ''
   }
 
+  waitTranscodingEnabled = true
+  previewfileUpload: File
+
+  error: string
+
   protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC
 
   constructor (
     protected formValidatorService: FormValidatorService,
     protected loadingBar: LoadingBarService,
   protected readonly DEFAULT_VIDEO_PRIVACY = VideoPrivacy.PUBLIC
 
   constructor (
     protected formValidatorService: FormValidatorService,
     protected loadingBar: LoadingBarService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected serverService: ServerService,
     protected videoService: VideoService,
     protected authService: AuthService,
     protected serverService: ServerService,
     protected videoService: VideoService,
@@ -60,14 +70,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
   }
 
   get videoExtensions () {
   }
 
   get videoExtensions () {
-    return this.serverService.getConfig().video.file.extensions.join(',')
+    return this.serverConfig.video.file.extensions.join(', ')
   }
 
   ngOnInit () {
     super.ngOnInit()
 
     this.userService.getMyVideoQuotaUsed()
   }
 
   ngOnInit () {
     super.ngOnInit()
 
     this.userService.getMyVideoQuotaUsed()
-      .subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
+        .subscribe(data => {
+          this.userVideoQuotaUsed = data.videoQuotaUsed
+          this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily
+        })
   }
 
   ngOnDestroy () {
   }
 
   ngOnDestroy () {
@@ -79,7 +92,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
 
     if (this.videoUploaded === true) {
       // FIXME: cannot concatenate strings inside i18n service :/
 
     if (this.videoUploaded === true) {
       // FIXME: cannot concatenate strings inside i18n service :/
-      text = this.i18n('Your video was uploaded to your account and is private.') +
+      text = this.i18n('Your video was uploaded to your account and is private.') + ' ' +
         this.i18n('But associated data (tags, description...) will be lost, are you sure you want to leave this page?')
     } else {
       text = this.i18n('Your video is not uploaded yet, are you sure you want to leave this page?')
         this.i18n('But associated data (tags, description...) will be lost, are you sure you want to leave this page?')
     } else {
       text = this.i18n('Your video is not uploaded yet, are you sure you want to leave this page?')
@@ -91,6 +104,22 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     }
   }
 
     }
   }
 
+  getVideoFile () {
+    return this.videofileInput.nativeElement.files[0]
+  }
+
+  setVideoFile (files: FileList) {
+    this.videofileInput.nativeElement.files = files
+    this.fileChange()
+  }
+
+  getAudioUploadLabel () {
+    const videofile = this.getVideoFile()
+    if (!videofile) return this.i18n('Upload')
+
+    return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name })
+  }
+
   fileChange () {
     this.uploadFirstStep()
   }
   fileChange () {
     this.uploadFirstStep()
   }
@@ -101,36 +130,23 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
       this.isUploadingVideo = false
       this.videoUploadPercents = 0
       this.videoUploadObservable = null
       this.isUploadingVideo = false
       this.videoUploadPercents = 0
       this.videoUploadObservable = null
-      this.notificationsService.info(this.i18n('Info'), this.i18n('Upload cancelled'))
+      this.notifier.info(this.i18n('Upload cancelled'))
     }
   }
 
     }
   }
 
-  uploadFirstStep () {
-    const videofile = this.videofileInput.nativeElement.files[0] as File
+  uploadFirstStep (clickedOnButton = false) {
+    const videofile = this.getVideoFile()
     if (!videofile) return
 
     if (!videofile) return
 
-    // Cannot upload videos > 8GB for now
-    if (videofile.size > 8 * 1024 * 1024 * 1024) {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 8GB'))
-      return
-    }
-
-    const videoQuota = this.authService.getUser().videoQuota
-    if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
-      const bytePipes = new BytesPipe()
+    if (!this.checkGlobalUserQuota(videofile)) return
+    if (!this.checkDailyUserQuota(videofile)) return
 
 
-      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.notificationsService.error(this.i18n('Error'), msg)
+    if (clickedOnButton === false && this.isAudioFile(videofile.name)) {
+      this.isUploadingAudioFile = true
       return
     }
 
       return
     }
 
+    // Build name field
     const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
     let name: string
 
     const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
     let name: string
 
@@ -138,10 +154,16 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     if (nameWithoutExtension.length < 3) name = videofile.name
     else name = nameWithoutExtension
 
     if (nameWithoutExtension.length < 3) name = videofile.name
     else name = nameWithoutExtension
 
+    // Force user to wait transcoding for unsupported video types in web browsers
+    if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) {
+      this.waitTranscodingEnabled = false
+    }
+
     const privacy = this.firstStepPrivacyId.toString()
     const privacy = this.firstStepPrivacyId.toString()
-    const nsfw = false
+    const nsfw = this.serverConfig.instance.isNSFW
     const waitTranscoding = true
     const commentsEnabled = true
     const waitTranscoding = true
     const commentsEnabled = true
+    const downloadEnabled = true
     const channelId = this.firstStepChannelId.toString()
 
     const formData = new FormData()
     const channelId = this.firstStepChannelId.toString()
 
     const formData = new FormData()
@@ -150,10 +172,16 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     formData.append('privacy', VideoPrivacy.PRIVATE.toString())
     formData.append('nsfw', '' + nsfw)
     formData.append('commentsEnabled', '' + commentsEnabled)
     formData.append('privacy', VideoPrivacy.PRIVATE.toString())
     formData.append('nsfw', '' + nsfw)
     formData.append('commentsEnabled', '' + commentsEnabled)
+    formData.append('downloadEnabled', '' + downloadEnabled)
     formData.append('waitTranscoding', '' + waitTranscoding)
     formData.append('channelId', '' + channelId)
     formData.append('videofile', videofile)
 
     formData.append('waitTranscoding', '' + waitTranscoding)
     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)
 
     this.isUploadingVideo = true
     this.firstStepDone.emit(name)
 
@@ -161,7 +189,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
       name,
       privacy,
       nsfw,
       name,
       privacy,
       nsfw,
-      channelId
+      channelId,
+      previewfile: this.previewfileUpload
     })
 
     this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe(
     })
 
     this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe(
@@ -182,11 +211,18 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
         this.isUploadingVideo = false
         this.videoUploadPercents = 0
         this.videoUploadObservable = null
         this.isUploadingVideo = false
         this.videoUploadPercents = 0
         this.videoUploadObservable = null
-        this.notificationsService.error(this.i18n('Error'), err.message)
+        this.firstStepError.emit()
+        this.notifier.error(err.message)
       }
     )
   }
 
       }
     )
   }
 
+  isPublishingButtonDisabled () {
+    return !this.form.valid ||
+      this.isUpdatingVideo === true ||
+      this.videoUploaded !== true
+  }
+
   updateSecondStep () {
     if (this.checkForm() === false) {
       return
   updateSecondStep () {
     if (this.checkForm() === false) {
       return
@@ -205,15 +241,65 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
             this.isUpdatingVideo = false
             this.isUploadingVideo = false
 
             this.isUpdatingVideo = false
             this.isUploadingVideo = false
 
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.'))
+            this.notifier.success(this.i18n('Video published.'))
             this.router.navigate([ '/videos/watch', video.uuid ])
           },
 
           err => {
             this.router.navigate([ '/videos/watch', video.uuid ])
           },
 
           err => {
-            this.isUpdatingVideo = false
-            this.notificationsService.error(this.i18n('Error'), err.message)
+            this.error = err.message
+            scrollToTop()
             console.error(err)
           }
         )
   }
             console.error(err)
           }
         )
   }
+
+  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) {
+    const extensions = [ '.mp3', '.flac', '.ogg', '.wma', '.wav' ]
+
+    return extensions.some(e => filename.endsWith(e))
+  }
 }
 }