]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Merge branch 'release/v1.3.0' into develop
authorChocobozzz <me@florianbigard.com>
Tue, 21 May 2019 09:21:56 +0000 (11:21 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 21 May 2019 09:21:56 +0000 (11:21 +0200)
111 files changed:
.travis.yml
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
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/+my-account/my-account-videos/my-account-videos.component.html
client/src/app/shared/buttons/button.component.scss
client/src/app/shared/buttons/button.component.ts
client/src/app/shared/buttons/delete-button.component.html
client/src/app/shared/buttons/edit-button.component.html
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/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 [moved from client/src/app/shared/images/image-upload.component.ts with 73% similarity]
client/src/app/shared/shared.module.ts
client/src/app/shared/video/modals/video-download.component.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
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/player/peertube-player-manager.ts
config/default.yaml
config/production.yaml.example
config/test-2.yaml
config/test.yaml
package.json
scripts/create-transcoding-job.ts
scripts/travis.sh
server/assets/default-audio-background.jpg [new file with mode: 0644]
server/controllers/api/config.ts
server/controllers/api/videos/index.ts
server/controllers/static.ts
server/helpers/express-utils.ts
server/helpers/ffmpeg-utils.ts
server/initializers/config.ts
server/initializers/constants.ts
server/initializers/installer.ts
server/lib/emailer.ts
server/lib/files-cache/videos-preview-cache.ts
server/lib/job-queue/handlers/video-file-import.ts
server/lib/job-queue/handlers/video-import.ts
server/lib/job-queue/handlers/video-transcoding.ts
server/lib/thumbnail.ts
server/lib/video-transcoding.ts
server/models/video/thumbnail.ts
server/models/video/video-file.ts
server/tests/api/activitypub/client.ts
server/tests/api/activitypub/fetch.ts
server/tests/api/activitypub/refresher.ts
server/tests/api/activitypub/security.ts
server/tests/api/check-params/config.ts
server/tests/api/index-1.ts [deleted file]
server/tests/api/index-2.ts [deleted file]
server/tests/api/index-3.ts [deleted file]
server/tests/api/index-4.ts [deleted file]
server/tests/api/index.ts
server/tests/api/notifications/index.ts
server/tests/api/notifications/user-notifications.ts
server/tests/api/redundancy/redundancy.ts
server/tests/api/search/search-activitypub-video-channels.ts
server/tests/api/search/search-activitypub-videos.ts
server/tests/api/search/search-videos.ts
server/tests/api/server/config.ts
server/tests/api/server/contact-form.ts
server/tests/api/server/email.ts
server/tests/api/server/follow-constraints.ts
server/tests/api/server/follows-moderation.ts
server/tests/api/server/follows.ts
server/tests/api/server/handle-down.ts
server/tests/api/server/jobs.ts
server/tests/api/server/logs.ts
server/tests/api/travis-1.sh [new file with mode: 0644]
server/tests/api/travis-2.sh [new file with mode: 0644]
server/tests/api/travis-3.sh [new file with mode: 0644]
server/tests/api/travis-4.sh [new file with mode: 0644]
server/tests/api/users/blocklist.ts
server/tests/api/users/user-subscriptions.ts
server/tests/api/users/users-multiple-servers.ts
server/tests/api/users/users-verification.ts
server/tests/api/users/users.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/services.ts
server/tests/api/videos/single-server.ts
server/tests/api/videos/video-abuse.ts
server/tests/api/videos/video-change-ownership.ts
server/tests/api/videos/video-channels.ts
server/tests/api/videos/video-comments.ts
server/tests/api/videos/video-hls.ts
server/tests/api/videos/video-playlists.ts
server/tests/api/videos/video-transcoder.ts
server/tests/api/videos/videos-views-cleaner.ts
server/tests/cli/optimize-old-videos.ts
server/tests/fixtures/preview.jpg
server/tests/fixtures/sample.ogg [new file with mode: 0644]
server/tests/fixtures/video_short1-preview.webm.jpg
shared/extra-utils/miscs/sql.ts
shared/extra-utils/server/config.ts
shared/extra-utils/server/servers.ts
shared/extra-utils/users/user-notifications.ts
shared/extra-utils/videos/video-playlists.ts
shared/extra-utils/videos/videos.ts
shared/models/server/custom-config.model.ts
support/doc/production.md
yarn.lock

index 5fa41fb43b16eab8adc0fcedbd2c717b16f28fb4..8b3ec94d9725a42a8ca0aab3b01fe5f56f42c38a 100644 (file)
@@ -29,8 +29,8 @@ install:
   - CC=gcc-4.9 CXX=g++-4.9 yarn install
 
 before_script:
-  - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.0.2-64bit-static.tar.xz"
-  - tar xf ffmpeg-release-4.0.2-64bit-static.tar.xz
+  - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.0.3-64bit-static.tar.xz"
+  - tar xf ffmpeg-release-4.0.3-64bit-static.tar.xz
   - mkdir -p $HOME/bin
   - cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin
   - export PATH=$HOME/bin:$PATH
index 637484622ca64dcf9bcd3f7a6199f57f23eb5cfa..44fc6dc26b4c88971088bc202a896c70ed295ade 100644 (file)
               ></my-peertube-checkbox>
             </div>
 
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles"
+                i18n-labelText labelText="Allow audio files upload"
+                i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload"
+              ></my-peertube-checkbox>
+            </div>
+
             <div class="form-group">
               <label i18n for="transcodingThreads">Transcoding threads</label>
               <div class="peertube-select-container">
index e64750713a26efe9fdc93503fc257a1b559da237..c238a6c8183777d2529ad4c46681e26a265e55e8 100644 (file)
@@ -116,6 +116,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
         enabled: null,
         threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
         allowAdditionalExtensions: null,
+        allowAudioFiles: null,
         resolutions: {}
       },
       autoBlacklist: {
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 84d4648001c12114275cb2b66aef1655dbf69bdb..2854093c46ecf41bfc8d1a486f0d3cf921c0602f 100644 (file)
@@ -20,7 +20,7 @@
     <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
 
     <my-button i18n-label label="Change ownership"
-               className="action-button-change-ownership"
+               className="action-button-change-ownership grey-button"
                icon="im-with-her"
                (click)="changeOwnership($event, video)"
     ></my-button>
index 04199a2a95c31e3dea21078dc36951405fae7157..99d7f51c1b5bd2e7e45d852f4f282fd2015ab038 100644 (file)
@@ -5,16 +5,9 @@
   @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);
+  // FIXME: Firefox does not apply global .orange-button icon color
+  &.orange-button {
+    @include apply-svg-color(#fff)
   }
 }
 
index c2b69d31a618730de8c7a6f27409614a39ec90d7..cf334e8d59c1d641c4737d67ccff519ff4cb095e 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 = 'grey-button'
   @Input() icon: GlobalIconName = undefined
   @Input() title: string = undefined
 
index b4acb9d32a4ecf974a9ee33fa4b334fc92dc1afa..25196fbd5650f74f5eeeafedd44101f0c9bc3be6 100644 (file)
@@ -1,4 +1,4 @@
-<span class="action-button action-button-delete" [title]="title" role="button">
+<span class="action-button action-button-delete grey-button" [title]="title" role="button">
   <my-global-icon iconName="delete"></my-global-icon>
 
   <span class="button-label" *ngIf="label">{{ label }}</span>
index da3addbae2a517a91816e12f0afc5f71b9bbee97..3d7cd47803ef3cc591f9e9af238d645d24e7b005 100644 (file)
@@ -1,4 +1,4 @@
-<a class="action-button action-button-edit" [routerLink]="routerLink" i18n-title title="Edit">
+<a class="action-button action-button-edit grey-button" [routerLink]="routerLink" i18n-title title="Edit">
   <my-global-icon iconName="edit"></my-global-icon>
 
   <span class="button-label" *ngIf="label">{{ label }}</span>
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/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;
+      }
+    }
+  }
+}
similarity index 73%
rename from client/src/app/shared/images/image-upload.component.ts
rename to client/src/app/shared/images/preview-upload.component.ts
index 2da1592ff269bc4a7bdaf38ab4f2aeb3c6af1b36..44b78866e0e330fe89aff8e0c282422a9dcb07f9 100644 (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
 
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 d6d10d29ed86b7d5f7303957fdc6e9894fd7d628..a07560f8761756552d172689f5844d21160d7abd 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, ElementRef, ViewChild } from '@angular/core'
 import { VideoDetails } from '../../../shared/video/video-details.model'
-import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Notifier } from '@app/core'
 
@@ -16,6 +16,7 @@ export class VideoDownloadComponent {
   resolutionId: number | string = -1
 
   video: VideoDetails
+  activeModal: NgbActiveModal
 
   constructor (
     private notifier: Notifier,
@@ -26,9 +27,7 @@ export class VideoDownloadComponent {
   show (video: VideoDetails) {
     this.video = video
 
-    const m = this.modalService.open(this.modal)
-    m.result.then(() => this.onClose())
-     .catch(() => this.onClose())
+    this.activeModal = this.modalService.open(this.modal)
 
     this.resolutionId = this.video.files[0].resolution.id
   }
@@ -39,6 +38,7 @@ export class VideoDownloadComponent {
 
   download () {
     window.location.assign(this.getLink())
+    this.activeModal.close()
   }
 
   getLink () {
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')
+  }
 }
index 631504eab6141472bf3e4389a519731864a65063..55109dc324d5ae646594610394bf0a4d1ece80a7 100644 (file)
@@ -545,8 +545,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   private flushPlayer () {
     // Remove player if it exists
     if (this.player) {
-      this.player.dispose()
-      this.player = undefined
+      try {
+        this.player.dispose()
+        this.player = undefined
+      } catch (err) {
+        console.error('Cannot dispose player.', err)
+      }
     }
   }
 
index 6cdd543725604d32b08ddbb6aee2e6f3bec5bceb..31cbc7dfd4c17c3b18f6b3cbcd2572de57968512 100644 (file)
@@ -117,8 +117,17 @@ export class PeertubePlayerManager {
       videojs(options.common.playerElement, videojsOptions, function (this: any) {
         const player = this
 
-        player.tech_.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
-        player.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options))
+        let alreadyFallback = false
+
+        player.tech_.one('error', () => {
+          if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
+          alreadyFallback = true
+        })
+
+        player.one('error', () => {
+          if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
+          alreadyFallback = true
+        })
 
         self.addContextMenu(mode, player, options.common.embedUrl)
 
index 37ef4366ff1a8a30ce902a189c1b8ce4c3528999..fcbbf17e86d2b5c700a594bf2c7d9cc9c18c3c49 100644 (file)
@@ -53,6 +53,12 @@ smtp:
   ca_file: null # Used for self signed certificates
   from_address: 'admin@example.com'
 
+email:
+  body:
+    signature: "PeerTube"
+  object:
+    prefix: "[PeerTube]"
+
 # From the project root directory
 storage:
   tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
@@ -174,6 +180,8 @@ transcoding:
   enabled: true
   # Allow your users to upload .mkv, .mov, .avi, .flv videos
   allow_additional_extensions: true
+  # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
+  allow_audio_files: true
   threads: 1
   resolutions: # Only created if the original video has a higher resolution, uses more storage!
     240p: false
index f84e156709e1e526e07f05119cc9fe6d11964c2c..0ab99ac458b30210918531ff3300dcac38771bc2 100644 (file)
@@ -188,6 +188,8 @@ transcoding:
   enabled: true
   # Allow your users to upload .mkv, .mov, .avi, .flv videos
   allow_additional_extensions: true
+  # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
+  allow_audio_files: true
   threads: 1
   resolutions: # Only created if the original video has a higher resolution, uses more storage!
     240p: false
index a5515afa4f23d6acd66c2a8db73b9052c58e24a7..de7300366374710800fc4c66409b4db7bb2d8040 100644 (file)
@@ -31,3 +31,4 @@ signup:
 transcoding:
   enabled: true
   allow_additional_extensions: true
+  allow_audio_files: true
index 6825308405a497ba31d72241dea95af0b21c2466..7dabe433c162fee82b9469fe4e063bbb5be3c03e 100644 (file)
@@ -55,6 +55,7 @@ signup:
 transcoding:
   enabled: true
   allow_additional_extensions: false
+  allow_audio_files: false
   threads: 2
   resolutions:
     240p: true
index 1dbf33a0a2ec182a7a04ed0358f9ba89a09ffd7a..6f789c5bf1aadd93c210063eb73bb785c51b0d5d 100644 (file)
     "maildev": "^1.0.0-rc3",
     "marked-man": "^0.4.2",
     "mocha": "^6.0.0",
+    "mocha-parallel-tests": "^2.1.0",
     "nodemon": "^1.18.6",
     "sass-lint": "^1.12.1",
     "source-map-support": "^0.5.0",
index 4a677eacb1e1b2e937920146eb2725c5bf4765df..2b7cb5177e2e4c0ec790a3a97010a81d023baeff 100755 (executable)
@@ -2,6 +2,7 @@ import * as program from 'commander'
 import { VideoModel } from '../server/models/video/video'
 import { initDatabaseModels } from '../server/initializers'
 import { JobQueue } from '../server/lib/job-queue'
+import { VideoTranscodingPayload } from '../server/lib/job-queue/handlers/video-transcoding'
 
 program
   .option('-v, --video [videoUUID]', 'Video UUID')
@@ -31,15 +32,9 @@ async function run () {
   const video = await VideoModel.loadByUUIDWithFile(program['video'])
   if (!video) throw new Error('Video not found.')
 
-  const dataInput = {
-    videoUUID: video.uuid,
-    isNewVideo: false,
-    resolution: undefined
-  }
-
-  if (program.resolution !== undefined) {
-    dataInput.resolution = program.resolution
-  }
+  const dataInput: VideoTranscodingPayload = program.resolution !== undefined
+   ? { type: 'new-resolution' as 'new-resolution', videoUUID: video.uuid, isNewVideo: false, resolution: program.resolution }
+   : { type: 'optimize' as 'optimize', videoUUID: video.uuid, isNewVideo: false }
 
   await JobQueue.Instance.init()
   await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
index 3557816c8c48f53b3a06a0d87baa64bc56a0b1db..c38bd2cabc12cfa926cd09822a2f5046ec9e8864 100755 (executable)
@@ -20,16 +20,16 @@ elif [ "$1" = "cli" ]; then
     mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/cli/index.ts
 elif [ "$1" = "api-1" ]; then
     npm run build:server
-    mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-1.ts
+    sh ./server/tests/api/travis-1.sh 2
 elif [ "$1" = "api-2" ]; then
     npm run build:server
-    mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-2.ts
+    sh ./server/tests/api/travis-2.sh 2
 elif [ "$1" = "api-3" ]; then
     npm run build:server
-    mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-3.ts
+    sh ./server/tests/api/travis-3.sh 2
 elif [ "$1" = "api-4" ]; then
     npm run build:server
-    mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-4.ts
+    sh ./server/tests/api/travis-4.sh 2
 elif [ "$1" = "lint" ]; then
     npm run tslint -- --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" "shared/**/*.ts"
 
diff --git a/server/assets/default-audio-background.jpg b/server/assets/default-audio-background.jpg
new file mode 100644 (file)
index 0000000..a19173e
Binary files /dev/null and b/server/assets/default-audio-background.jpg differ
index 40012c03bef334295639542e9b14e734e4ede1f6..d9ce6a15330e534f3e284f384a549ef4ca35ef65 100644 (file)
@@ -255,6 +255,7 @@ function customConfig (): CustomConfig {
     transcoding: {
       enabled: CONFIG.TRANSCODING.ENABLED,
       allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
+      allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
       threads: CONFIG.TRANSCODING.THREADS,
       resolutions: {
         '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
index 1a18a8ae80993da568d81f9490d6015bfe4026de..40a2c972b9ccef040387534637d179d15f690637 100644 (file)
@@ -6,7 +6,14 @@ import { logger } from '../../../helpers/logger'
 import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
 import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
 import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
-import { MIMETYPES, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
+import {
+  DEFAULT_AUDIO_RESOLUTION,
+  MIMETYPES,
+  VIDEO_CATEGORIES,
+  VIDEO_LANGUAGES,
+  VIDEO_LICENCES,
+  VIDEO_PRIVACIES
+} from '../../../initializers/constants'
 import {
   changeVideoChannelShare,
   federateVideoIfNeeded,
@@ -54,6 +61,7 @@ import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
 import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
+import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
 
 const auditLogger = auditLoggerFactory('videos')
 const videosRouter = express.Router()
@@ -191,18 +199,19 @@ async function addVideo (req: express.Request, res: express.Response) {
   const video = new VideoModel(videoData)
   video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
 
-  // Build the file object
-  const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path)
-  const fps = await getVideoFileFPS(videoPhysicalFile.path)
-
   const videoFileData = {
     extname: extname(videoPhysicalFile.filename),
-    resolution: videoFileResolution,
-    size: videoPhysicalFile.size,
-    fps
+    size: videoPhysicalFile.size
   }
   const videoFile = new VideoFileModel(videoFileData)
 
+  if (!videoFile.isAudio()) {
+    videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path)
+    videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution
+  } else {
+    videoFile.resolution = DEFAULT_AUDIO_RESOLUTION
+  }
+
   // Move physical file
   const videoDir = CONFIG.STORAGE.VIDEOS_DIR
   const destination = join(videoDir, video.getVideoFilename(videoFile))
@@ -279,9 +288,21 @@ async function addVideo (req: express.Request, res: express.Response) {
 
   if (video.state === VideoState.TO_TRANSCODE) {
     // Put uuid because we don't have id auto incremented for now
-    const dataInput = {
-      videoUUID: videoCreated.uuid,
-      isNewVideo: true
+    let dataInput: VideoTranscodingPayload
+
+    if (videoFile.isAudio()) {
+      dataInput = {
+        type: 'merge-audio' as 'merge-audio',
+        resolution: DEFAULT_AUDIO_RESOLUTION,
+        videoUUID: videoCreated.uuid,
+        isNewVideo: true
+      }
+    } else {
+      dataInput = {
+        type: 'optimize' as 'optimize',
+        videoUUID: videoCreated.uuid,
+        isNewVideo: true
+      }
     }
 
     await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
index 05019fcc2139b9c7f9706b8b7e74652aa3e71d9b..d57dba6ce24eae3f0ee07b56d7d80b95eb0c65e8 100644 (file)
@@ -181,7 +181,7 @@ async function getVideoCaption (req: express.Request, res: express.Response) {
   return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
 }
 
-async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function generateNodeinfo (req: express.Request, res: express.Response) {
   const { totalVideos } = await VideoModel.getStats()
   const { totalLocalVideoComments } = await VideoCommentModel.getStats()
   const { totalUsers } = await UserModel.getStats()
index e0a1d56a587759adad40cdec568594b2ee41900a..00f3f198bc2195087a69333cf4c46ab37f2d204d 100644 (file)
@@ -74,7 +74,18 @@ function createReqFiles (
     },
 
     filename: async (req, file, cb) => {
-      const extension = mimeTypes[ file.mimetype ] || extname(file.originalname)
+      let extension: string
+      const fileExtension = extname(file.originalname)
+      const extensionFromMimetype = mimeTypes[ file.mimetype ]
+
+      // Take the file extension if we don't understand the mime type
+      // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file
+      if (fileExtension === '.ogg' || fileExtension === '.ogv' || !extensionFromMimetype) {
+        extension = fileExtension
+      } else {
+        extension = extensionFromMimetype
+      }
+
       let randomString = ''
 
       try {
index 76b744de8543c7bd6e35a0d0a941e933143dea3d..c180da832135cc1c3bc4e4ea743d4efad53eb4bb 100644 (file)
@@ -1,6 +1,6 @@
 import * as ffmpeg from 'fluent-ffmpeg'
 import { dirname, join } from 'path'
-import { getTargetBitrate, VideoResolution } from '../../shared/models/videos'
+import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos'
 import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
 import { processImage } from './image-utils'
 import { logger } from './logger'
@@ -31,7 +31,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
 }
 
 async function getVideoFileSize (path: string) {
-  const videoStream = await getVideoFileStream(path)
+  const videoStream = await getVideoStreamFromFile(path)
 
   return {
     width: videoStream.width,
@@ -49,7 +49,7 @@ async function getVideoFileResolution (path: string) {
 }
 
 async function getVideoFileFPS (path: string) {
-  const videoStream = await getVideoFileStream(path)
+  const videoStream = await getVideoStreamFromFile(path)
 
   for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
     const valuesText: string = videoStream[key]
@@ -117,25 +117,50 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
   }
 }
 
-type TranscodeOptions = {
+type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio'
+
+interface BaseTranscodeOptions {
+  type: TranscodeOptionsType
   inputPath: string
   outputPath: string
   resolution: VideoResolution
   isPortraitMode?: boolean
+}
 
-  hlsPlaylist?: {
+interface HLSTranscodeOptions extends BaseTranscodeOptions {
+  type: 'hls'
+  hlsPlaylist: {
     videoFilename: string
   }
 }
 
+interface QuickTranscodeOptions extends BaseTranscodeOptions {
+  type: 'quick-transcode'
+}
+
+interface VideoTranscodeOptions extends BaseTranscodeOptions {
+  type: 'video'
+}
+
+interface MergeAudioTranscodeOptions extends BaseTranscodeOptions {
+  type: 'merge-audio'
+  audioPath: string
+}
+
+type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions
+
 function transcode (options: TranscodeOptions) {
   return new Promise<void>(async (res, rej) => {
     try {
       let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
         .output(options.outputPath)
 
-      if (options.hlsPlaylist) {
+      if (options.type === 'quick-transcode') {
+        command = await buildQuickTranscodeCommand(command)
+      } else if (options.type === 'hls') {
         command = await buildHLSCommand(command, options)
+      } else if (options.type === 'merge-audio') {
+        command = await buildAudioMergeCommand(command, options)
       } else {
         command = await buildx264Command(command, options)
       }
@@ -151,7 +176,7 @@ function transcode (options: TranscodeOptions) {
           return rej(err)
         })
         .on('end', () => {
-          return onTranscodingSuccess(options)
+          return fixHLSPlaylistIfNeeded(options)
             .then(() => res())
             .catch(err => rej(err))
         })
@@ -162,6 +187,30 @@ function transcode (options: TranscodeOptions) {
   })
 }
 
+async function canDoQuickTranscode (path: string): Promise<boolean> {
+  // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway)
+  const videoStream = await getVideoStreamFromFile(path)
+  const parsedAudio = await audio.get(path)
+  const fps = await getVideoFileFPS(path)
+  const bitRate = await getVideoFileBitrate(path)
+  const resolution = await getVideoFileResolution(path)
+
+  // check video params
+  if (videoStream[ 'codec_name' ] !== 'h264') return false
+  if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
+  if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
+
+    // check audio params (if audio stream exists)
+  if (parsedAudio.audioStream) {
+    if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false
+
+    const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ])
+    if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false
+  }
+
+  return true
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -169,16 +218,19 @@ export {
   getVideoFileResolution,
   getDurationFromVideoFile,
   generateImageFromVideoFile,
+  TranscodeOptions,
+  TranscodeOptionsType,
   transcode,
   getVideoFileFPS,
   computeResolutionsToTranscode,
   audio,
-  getVideoFileBitrate
+  getVideoFileBitrate,
+  canDoQuickTranscode
 }
 
 // ---------------------------------------------------------------------------
 
-async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
+async function buildx264Command (command: ffmpeg.FfmpegCommand, options: VideoTranscodeOptions) {
   let fps = await getVideoFileFPS(options.inputPath)
   // On small/medium resolutions, limit FPS
   if (
@@ -189,7 +241,7 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
     fps = VIDEO_TRANSCODING_FPS.AVERAGE
   }
 
-  command = await presetH264(command, options.resolution, fps)
+  command = await presetH264(command, options.inputPath, options.resolution, fps)
 
   if (options.resolution !== undefined) {
     // '?x720' or '720x?' for example
@@ -208,7 +260,29 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
   return command
 }
 
-async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
+async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) {
+  command = command.loop(undefined)
+
+  command = await presetH264VeryFast(command, options.audioPath, options.resolution)
+
+  command = command.input(options.audioPath)
+                   .videoFilter('scale=trunc(iw/2)*2:trunc(ih/2)*2') // Avoid "height not divisible by 2" error
+                   .outputOption('-tune stillimage')
+                   .outputOption('-shortest')
+
+  return command
+}
+
+async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
+  command = await presetCopy(command)
+
+  command = command.outputOption('-map_metadata -1') // strip all metadata
+                   .outputOption('-movflags faststart')
+
+  return command
+}
+
+async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
   const videoPath = getHLSVideoPath(options)
 
   command = await presetCopy(command)
@@ -224,26 +298,26 @@ async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: Transcod
   return command
 }
 
-function getHLSVideoPath (options: TranscodeOptions) {
+function getHLSVideoPath (options: HLSTranscodeOptions) {
   return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}`
 }
 
-async function onTranscodingSuccess (options: TranscodeOptions) {
-  if (!options.hlsPlaylist) return
+async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) {
+  if (options.type !== 'hls') return
 
-  // Fix wrong mapping with some ffmpeg versions
   const fileContent = await readFile(options.outputPath)
 
   const videoFileName = options.hlsPlaylist.videoFilename
   const videoFilePath = getHLSVideoPath(options)
 
+  // Fix wrong mapping with some ffmpeg versions
   const newContent = fileContent.toString()
                                 .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`)
 
   await writeFile(options.outputPath, newContent)
 }
 
-function getVideoFileStream (path: string) {
+function getVideoStreamFromFile (path: string) {
   return new Promise<any>((res, rej) => {
     ffmpeg.ffprobe(path, (err, metadata) => {
       if (err) return rej(err)
@@ -263,44 +337,27 @@ function getVideoFileStream (path: string) {
  * and quality. Superfast and ultrafast will give you better
  * performance, but then quality is noticeably worse.
  */
-async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> {
-  let localCommand = await presetH264(command, resolution, fps)
+async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string, resolution: VideoResolution, fps?: number) {
+  let localCommand = await presetH264(command, input, resolution, fps)
+
   localCommand = localCommand.outputOption('-preset:v veryfast')
-             .outputOption([ '--aq-mode=2', '--aq-strength=1.3' ])
+
   /*
   MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html
   Our target situation is closer to a livestream than a stream,
   since we want to reduce as much a possible the encoding burden,
-  altough not to the point of a livestream where there is a hard
+  although not to the point of a livestream where there is a hard
   constraint on the frames per second to be encoded.
-
-  why '--aq-mode=2 --aq-strength=1.3' instead of '-profile:v main'?
-    Make up for most of the loss of grain and macroblocking
-    with less computing power.
   */
 
   return localCommand
 }
 
-/**
- * A preset optimised for a stillimage audio video
- */
-async function presetStillImageWithAudio (
-  command: ffmpeg.FfmpegCommand,
-  resolution: VideoResolution,
-  fps: number
-): Promise<ffmpeg.FfmpegCommand> {
-  let localCommand = await presetH264VeryFast(command, resolution, fps)
-  localCommand = localCommand.outputOption('-tune stillimage')
-
-  return localCommand
-}
-
 /**
  * A toolbox to play with audio
  */
 namespace audio {
-  export const get = (option: ffmpeg.FfmpegCommand | string) => {
+  export const get = (option: string) => {
     // without position, ffprobe considers the last input only
     // we make it consider the first input only
     // if you pass a file path to pos, then ffprobe acts on that file directly
@@ -322,11 +379,7 @@ namespace audio {
         return res({ absolutePath: data.format.filename })
       }
 
-      if (typeof option === 'string') {
-        return ffmpeg.ffprobe(option, parseFfprobe)
-      }
-
-      return option.ffprobe(parseFfprobe)
+      return ffmpeg.ffprobe(option, parseFfprobe)
     })
   }
 
@@ -368,7 +421,7 @@ namespace audio {
  * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel
  * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr
  */
-async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> {
+async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolution: VideoResolution, fps?: number) {
   let localCommand = command
     .format('mp4')
     .videoCodec('libx264')
@@ -379,7 +432,7 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol
     .outputOption('-map_metadata -1') // strip all metadata
     .outputOption('-movflags faststart')
 
-  const parsedAudio = await audio.get(localCommand)
+  const parsedAudio = await audio.get(input)
 
   if (!parsedAudio.audioStream) {
     localCommand = localCommand.noAudio()
@@ -388,28 +441,30 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol
       .audioCodec('libfdk_aac')
       .audioQuality(5)
   } else {
-    // we try to reduce the ceiling bitrate by making rough correspondances of bitrates
+    // we try to reduce the ceiling bitrate by making rough matches of bitrates
     // of course this is far from perfect, but it might save some space in the end
+    localCommand = localCommand.audioCodec('aac')
+
     const audioCodecName = parsedAudio.audioStream[ 'codec_name' ]
-    let bitrate: number
-    if (audio.bitrate[ audioCodecName ]) {
-      localCommand = localCommand.audioCodec('aac')
 
-      bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
+    if (audio.bitrate[ audioCodecName ]) {
+      const bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
       if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate)
     }
   }
 
-  // Constrained Encoding (VBV)
-  // https://slhck.info/video/2017/03/01/rate-control.html
-  // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
-  const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)
-  localCommand = localCommand.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`])
-
-  // Keyframe interval of 2 seconds for faster seeking and resolution switching.
-  // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
-  // https://superuser.com/a/908325
-  localCommand = localCommand.outputOption(`-g ${ fps * 2 }`)
+  if (fps) {
+    // Constrained Encoding (VBV)
+    // https://slhck.info/video/2017/03/01/rate-control.html
+    // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
+    const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)
+    localCommand = localCommand.outputOptions([ `-maxrate ${targetBitrate}`, `-bufsize ${targetBitrate * 2}` ])
+
+    // Keyframe interval of 2 seconds for faster seeking and resolution switching.
+    // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
+    // https://superuser.com/a/908325
+    localCommand = localCommand.outputOption(`-g ${fps * 2}`)
+  }
 
   return localCommand
 }
index 4f77e144d0ef19a17133e4535e1b97a64dcdebd6..2be300a57d66997a678280b6612993b00e3c4ffe 100644 (file)
@@ -44,6 +44,14 @@ const CONFIG = {
     CA_FILE: config.get<string>('smtp.ca_file'),
     FROM_ADDRESS: config.get<string>('smtp.from_address')
   },
+  EMAIL: {
+    BODY: {
+      SIGNATURE: config.get<string>('email.body.signature')
+    },
+    OBJECT: {
+      PREFIX: config.get<string>('email.object.prefix') + ' '
+    }
+  },
   STORAGE: {
     TMP_DIR: buildPath(config.get<string>('storage.tmp')),
     AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
@@ -140,6 +148,7 @@ const CONFIG = {
   TRANSCODING: {
     get ENABLED () { return config.get<boolean>('transcoding.enabled') },
     get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
+    get ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
     get THREADS () { return config.get<number>('transcoding.threads') },
     RESOLUTIONS: {
       get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
index 62778ae58443c137cae02503f148336f49026735..8a11101ff020cf08b5ca40ac09aba78a3b8ddda8 100644 (file)
@@ -1,10 +1,10 @@
 import { join } from 'path'
-import { JobType, VideoRateType, VideoState } from '../../shared/models'
+import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models'
 import { ActivityPubActorType } from '../../shared/models/activitypub'
 import { FollowState } from '../../shared/models/actors'
 import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
 // Do not use barrels, remain constants as independent as possible
-import { isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
+import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils'
 import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
 import { invert } from 'lodash'
 import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
@@ -228,7 +228,7 @@ let CONSTRAINTS_FIELDS = {
         max: 2 * 1024 * 1024 // 2MB
       }
     },
-    EXTNAME: buildVideosExtname(),
+    EXTNAME: [] as string[],
     INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
     DURATION: { min: 0 }, // Number
     TAGS: { min: 0, max: 5 }, // Number of total tags
@@ -300,6 +300,8 @@ const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
   KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
 }
 
+const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P
+
 const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
   LIKE: 'like',
   DISLIKE: 'dislike'
@@ -380,8 +382,18 @@ const VIDEO_PLAYLIST_TYPES = {
 }
 
 const MIMETYPES = {
+  AUDIO: {
+    MIMETYPE_EXT: {
+      'audio/mpeg': '.mp3',
+      'audio/mp3': '.mp3',
+      'application/ogg': '.ogg',
+      'audio/ogg': '.ogg',
+      'audio/flac': '.flac'
+    },
+    EXT_MIMETYPE: null as { [ id: string ]: string }
+  },
   VIDEO: {
-    MIMETYPE_EXT: buildVideoMimetypeExt(),
+    MIMETYPE_EXT: null as { [ id: string ]: string },
     EXT_MIMETYPE: null as { [ id: string ]: string }
   },
   IMAGE: {
@@ -403,7 +415,7 @@ const MIMETYPES = {
     }
   }
 }
-MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT)
 
 // ---------------------------------------------------------------------------
 
@@ -429,7 +441,7 @@ const ACTIVITY_PUB = {
   COLLECTION_ITEMS_PER_PAGE: 10,
   FETCH_PAGE_LIMIT: 100,
   URL_MIME_TYPES: {
-    VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT),
+    VIDEO: [] as string[],
     TORRENT: [ 'application/x-bittorrent' ],
     MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
   },
@@ -497,8 +509,8 @@ const THUMBNAILS_SIZE = {
   height: 122
 }
 const PREVIEWS_SIZE = {
-  width: 560,
-  height: 315
+  width: 850,
+  height: 480
 }
 const AVATARS_SIZE = {
   width: 120,
@@ -543,6 +555,10 @@ const REDUNDANCY = {
 
 const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
 
+const ASSETS_PATH = {
+  DEFAULT_AUDIO_BACKGROUND: join(root(), 'server', 'assets', 'default-audio-background.jpg')
+}
+
 // ---------------------------------------------------------------------------
 
 const CUSTOM_HTML_TAG_COMMENTS = {
@@ -612,6 +628,7 @@ if (isTestInstance() === true) {
 }
 
 updateWebserverUrls()
+updateWebserverConfig()
 
 registerConfigChangedHandler(() => {
   updateWebserverUrls()
@@ -681,12 +698,14 @@ export {
   RATES_LIMIT,
   MIMETYPES,
   CRAWL_REQUEST_CONCURRENCY,
+  DEFAULT_AUDIO_RESOLUTION,
   JOB_COMPLETED_LIFETIME,
   HTTP_SIGNATURE,
   VIDEO_IMPORT_STATES,
   VIDEO_VIEW_LIFETIME,
   CONTACT_FORM_LIFETIME,
   VIDEO_PLAYLIST_PRIVACIES,
+  ASSETS_PATH,
   loadLanguages,
   buildLanguages
 }
@@ -700,15 +719,21 @@ function buildVideoMimetypeExt () {
     'video/mp4': '.mp4'
   }
 
-  if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
-    Object.assign(data, {
-      'video/quicktime': '.mov',
-      'video/x-msvideo': '.avi',
-      'video/x-flv': '.flv',
-      'video/x-matroska': '.mkv',
-      'application/octet-stream': '.mkv',
-      'video/avi': '.avi'
-    })
+  if (CONFIG.TRANSCODING.ENABLED) {
+    if (CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
+      Object.assign(data, {
+        'video/quicktime': '.mov',
+        'video/x-msvideo': '.avi',
+        'video/x-flv': '.flv',
+        'video/x-matroska': '.mkv',
+        'application/octet-stream': '.mkv',
+        'video/avi': '.avi'
+      })
+    }
+
+    if (CONFIG.TRANSCODING.ALLOW_AUDIO_FILES) {
+      Object.assign(data, MIMETYPES.AUDIO.MIMETYPE_EXT)
+    }
   }
 
   return data
@@ -724,16 +749,15 @@ function updateWebserverUrls () {
 }
 
 function updateWebserverConfig () {
-  CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
-
   MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
   MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+  ACTIVITY_PUB.URL_MIME_TYPES.VIDEO = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
+
+  CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
 }
 
 function buildVideosExtname () {
-  return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS
-    ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
-    : [ '.mp4', '.ogv', '.webm' ]
+  return Object.keys(MIMETYPES.VIDEO.EXT_MIMETYPE)
 }
 
 function loadLanguages () {
index 127449577a041c04c8720a2344651055dc50ccbe..33970f0fae9195b4cc46c2e4107e93eb8956db7e 100644 (file)
@@ -128,6 +128,8 @@ async function createOAuthAdminIfNotExist () {
 
     // Our password is weak so do not validate it
     validatePassword = false
+  } else if (process.env.PT_INITIAL_ROOT_PASSWORD) {
+    password = process.env.PT_INITIAL_ROOT_PASSWORD
   } else {
     password = passwordGenerator(16, true)
   }
index 8c06e9751e9ea7af181afc9f68e07a87b1a60fd9..c4a5a5853f2a0cea4153a4641d55f2675e959deb 100644 (file)
@@ -100,11 +100,11 @@ class Emailer {
       `You can view it on ${videoUrl} ` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: channelName + ' just published a new video',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + channelName + ' just published a new video',
       text
     }
 
@@ -119,11 +119,11 @@ class Emailer {
       `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: 'New follower on your channel ' + followingName,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New follower on your channel ' + followingName,
       text
     }
 
@@ -137,11 +137,11 @@ class Emailer {
       `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: 'New instance follower',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New instance follower',
       text
     }
 
@@ -157,11 +157,11 @@ class Emailer {
       `You can view it on ${videoUrl} ` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: `Your video ${video.name} is published`,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video ${video.name} is published`,
       text
     }
 
@@ -177,11 +177,11 @@ class Emailer {
       `You can view the imported video on ${videoUrl} ` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
       text
     }
 
@@ -197,11 +197,11 @@ class Emailer {
       `See your videos import dashboard for more information: ${importUrl}` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
       text
     }
 
@@ -219,11 +219,11 @@ class Emailer {
       `You can view it on ${commentUrl} ` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: 'New comment on your video ' + video.name,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New comment on your video ' + video.name,
       text
     }
 
@@ -241,11 +241,11 @@ class Emailer {
       `You can view the comment on ${commentUrl} ` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: 'Mention on video ' + video.name,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Mention on video ' + video.name,
       text
     }
 
@@ -258,11 +258,11 @@ class Emailer {
     const text = `Hi,\n\n` +
       `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: '[PeerTube] Received a video abuse',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Received a video abuse',
       text
     }
 
@@ -281,11 +281,11 @@ class Emailer {
       `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
       `\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: '[PeerTube] An auto-blacklisted video is awaiting review',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
       text
     }
 
@@ -296,11 +296,11 @@ class Emailer {
     const text = `Hi,\n\n` +
       `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: '[PeerTube] New user registration on ' + WEBSERVER.HOST,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
       text
     }
 
@@ -318,11 +318,11 @@ class Emailer {
       blockedString +
       '\n\n' +
       'Cheers,\n' +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: `[PeerTube] Video ${videoName} blacklisted`,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${videoName} blacklisted`,
       text
     }
 
@@ -336,11 +336,11 @@ class Emailer {
       `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` +
       '\n\n' +
       'Cheers,\n' +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to,
-      subject: `[PeerTube] Video ${video.name} unblacklisted`,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${video.name} unblacklisted`,
       text
     }
 
@@ -353,11 +353,11 @@ class Emailer {
       `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
       `If you are not the person who initiated this request, please ignore this email.\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to: [ to ],
-      subject: 'Reset your PeerTube password',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Reset your password',
       text
     }
 
@@ -370,11 +370,11 @@ class Emailer {
       `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
       `If you are not the person who initiated this request, please ignore this email.\n\n` +
       `Cheers,\n` +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const emailPayload: EmailPayload = {
       to: [ to ],
-      subject: 'Verify your PeerTube email',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Verify your email',
       text
     }
 
@@ -390,12 +390,12 @@ class Emailer {
       blockedString +
       '\n\n' +
       'Cheers,\n' +
-      `PeerTube.`
+      `${CONFIG.EMAIL.BODY.SIGNATURE}`
 
     const to = user.email
     const emailPayload: EmailPayload = {
       to: [ to ],
-      subject: '[PeerTube] Account ' + blockedWord,
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Account ' + blockedWord,
       text
     }
 
@@ -415,7 +415,7 @@ class Emailer {
       fromDisplayName: fromEmail,
       replyTo: fromEmail,
       to: [ CONFIG.ADMIN.EMAIL ],
-      subject: '[PeerTube] Contact form submitted',
+      subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Contact form submitted',
       text
     }
 
index 14be7f24a57f75e1cfa63deb6548519e31806b4e..a68619d076da9372bd6c6b00ad056d6da6e1b626 100644 (file)
@@ -21,7 +21,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
     const video = await VideoModel.loadByUUIDWithFile(videoUUID)
     if (!video) return undefined
 
-    if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) }
+    if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() }
 
     return this.loadRemoteFile(videoUUID)
   }
index 921d9a083081fc1ec76bd62612e49f73a43edaf9..8cacb0ef3ea3b902f788b2d4e0f92fb1540b8320 100644 (file)
@@ -1,7 +1,7 @@
 import * as Bull from 'bull'
 import { logger } from '../../../helpers/logger'
 import { VideoModel } from '../../../models/video/video'
-import { publishVideoIfNeeded } from './video-transcoding'
+import { publishNewResolutionIfNeeded } from './video-transcoding'
 import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
 import { copy, stat } from 'fs-extra'
 import { VideoFileModel } from '../../../models/video/video-file'
@@ -25,7 +25,7 @@ async function processVideoFileImport (job: Bull.Job) {
 
   await updateVideoFile(video, payload.filePath)
 
-  await publishVideoIfNeeded(video)
+  await publishNewResolutionIfNeeded(video)
   return video
 }
 
index 1650916a67b7f2a5bd0efb3ecfd834eafb88b04d..50e159245f0362f055eb045b0c4a0e5abbf5b1b8 100644 (file)
@@ -209,6 +209,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
       // Put uuid because we don't have id auto incremented for now
       const dataInput = {
+        type: 'optimize' as 'optimize',
         videoUUID: videoImportUpdated.Video.uuid,
         isNewVideo: true
       }
index 48cac517e73203551da76a1b0dec8435a2a8f286..e9b84ecd66722aec9bcdedb0655ba874ae4bd022 100644 (file)
@@ -8,18 +8,39 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { sequelizeTypescript } from '../../../initializers'
 import * as Bluebird from 'bluebird'
 import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
-import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding'
+import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
 import { Notifier } from '../../notifier'
 import { CONFIG } from '../../../initializers/config'
 
-export type VideoTranscodingPayload = {
+interface BaseTranscodingPayload {
   videoUUID: string
-  resolution?: VideoResolution
   isNewVideo?: boolean
+}
+
+interface HLSTranscodingPayload extends BaseTranscodingPayload {
+  type: 'hls'
+  isPortraitMode?: boolean
+  resolution: VideoResolution
+}
+
+interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
+  type: 'new-resolution'
   isPortraitMode?: boolean
-  generateHlsPlaylist?: boolean
+  resolution: VideoResolution
+}
+
+interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
+  type: 'merge-audio'
+  resolution: VideoResolution
+}
+
+interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
+  type: 'optimize'
 }
 
+export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload
+  | OptimizeTranscodingPayload | MergeAudioTranscodingPayload
+
 async function processVideoTranscoding (job: Bull.Job) {
   const payload = job.data as VideoTranscodingPayload
   logger.info('Processing video file in job %d.', job.id)
@@ -31,14 +52,18 @@ async function processVideoTranscoding (job: Bull.Job) {
     return undefined
   }
 
-  if (payload.generateHlsPlaylist) {
+  if (payload.type === 'hls') {
     await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false)
 
     await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video)
-  } else if (payload.resolution) { // Transcoding in other resolution
+  } else if (payload.type === 'new-resolution') {
     await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false)
 
-    await retryTransactionWrapper(publishVideoIfNeeded, video, payload)
+    await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
+  } else if (payload.type === 'merge-audio') {
+    await mergeAudioVideofile(video, payload.resolution)
+
+    await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
   } else {
     await optimizeVideofile(video)
 
@@ -62,7 +87,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
   })
 }
 
-async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) {
+async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
   const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
     let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -94,7 +119,7 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi
   await createHlsJobIfEnabled(payload)
 }
 
-async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) {
+async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) {
   if (videoArg === undefined) return undefined
 
   // Outside the transaction (IO on disk)
@@ -120,6 +145,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
 
       for (const resolution of resolutionsEnabled) {
         const dataInput = {
+          type: 'new-resolution' as 'new-resolution',
           videoUUID: videoDatabase.uuid,
           resolution
         }
@@ -149,27 +175,27 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
   if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
   if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
 
-  await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution }))
+  const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })
+  await createHlsJobIfEnabled(hlsPayload)
 }
 
 // ---------------------------------------------------------------------------
 
 export {
   processVideoTranscoding,
-  publishVideoIfNeeded
+  publishNewResolutionIfNeeded
 }
 
 // ---------------------------------------------------------------------------
 
-function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) {
+function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: number, isPortraitMode?: boolean }) {
   // Generate HLS playlist?
   if (payload && CONFIG.TRANSCODING.HLS.ENABLED) {
     const hlsTranscodingPayload = {
+      type: 'hls' as 'hls',
       videoUUID: payload.videoUUID,
       resolution: payload.resolution,
-      isPortraitMode: payload.isPortraitMode,
-
-      generateHlsPlaylist: true
+      isPortraitMode: payload.isPortraitMode
     }
 
     return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload })
index 950b14c3bd18187a019f8213bcfbfff2f715c6f4..18bdcded400822069bc002e768b00ff9ba87d90b 100644 (file)
@@ -1,7 +1,7 @@
 import { VideoFileModel } from '../models/video/video-file'
 import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
 import { CONFIG } from '../initializers/config'
-import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
+import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants'
 import { VideoModel } from '../models/video/video'
 import { ThumbnailModel } from '../models/video/thumbnail'
 import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
@@ -45,8 +45,10 @@ function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel,
 function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
   const input = video.getVideoFilePath(videoFile)
 
-  const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type)
-  const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width })
+  const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
+  const thumbnailCreator = videoFile.isAudio()
+    ? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
+    : () => generateImageFromVideoFile(input, basePath, filename, { height, width })
 
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
 }
index 0fe0ff12a4c0aa2fb4f15caba7a547bcaaf33471..8d786e0ef5ea5ba16e7f79e96557072dbc4af98b 100644 (file)
@@ -1,6 +1,6 @@
 import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
 import { join } from 'path'
-import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils'
+import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils'
 import { ensureDir, move, remove, stat } from 'fs-extra'
 import { logger } from '../helpers/logger'
 import { VideoResolution } from '../../shared/models/videos'
@@ -11,15 +11,24 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla
 import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
 import { CONFIG } from '../initializers/config'
 
+/**
+ * Optimize the original video file and replace it. The resolution is not changed.
+ */
 async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
+  const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
   const newExtname = '.mp4'
 
   const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile()
   const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
-  const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
+  const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
 
-  const transcodeOptions = {
+  const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath)
+    ? 'quick-transcode'
+    : 'video'
+
+  const transcodeOptions: TranscodeOptions = {
+    type: transcodeType as any, // FIXME: typing issue
     inputPath: videoInputPath,
     outputPath: videoTranscodedPath,
     resolution: inputVideoFile.resolution
@@ -32,18 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
     await remove(videoInputPath)
 
     // Important to do this before getVideoFilename() to take in account the new file extension
-    inputVideoFile.set('extname', newExtname)
+    inputVideoFile.extname = newExtname
 
     const videoOutputPath = video.getVideoFilePath(inputVideoFile)
-    await move(videoTranscodedPath, videoOutputPath)
-    const stats = await stat(videoOutputPath)
-    const fps = await getVideoFileFPS(videoOutputPath)
 
-    inputVideoFile.set('size', stats.size)
-    inputVideoFile.set('fps', fps)
-
-    await video.createTorrentAndSetInfoHash(inputVideoFile)
-    await inputVideoFile.save()
+    await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
   } catch (err) {
     // Auto destruction...
     video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err }))
@@ -52,8 +54,12 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
   }
 }
 
+/**
+ * Transcode the original video file to a lower resolution.
+ */
 async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
+  const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
   const extname = '.mp4'
 
   // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
@@ -66,27 +72,49 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
     videoId: video.id
   })
   const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile))
+  const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))
 
   const transcodeOptions = {
+    type: 'video' as 'video',
     inputPath: videoInputPath,
-    outputPath: videoOutputPath,
+    outputPath: videoTranscodedPath,
     resolution,
     isPortraitMode: isPortrait
   }
 
   await transcode(transcodeOptions)
 
-  const stats = await stat(videoOutputPath)
-  const fps = await getVideoFileFPS(videoOutputPath)
+  return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
+}
+
+async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
+  const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
+  const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
+  const newExtname = '.mp4'
+
+  const inputVideoFile = video.getOriginalFile()
 
-  newVideoFile.set('size', stats.size)
-  newVideoFile.set('fps', fps)
+  const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
+  const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
 
-  await video.createTorrentAndSetInfoHash(newVideoFile)
+  const transcodeOptions = {
+    type: 'merge-audio' as 'merge-audio',
+    inputPath: video.getPreview().getPath(),
+    outputPath: videoTranscodedPath,
+    audioPath: audioInputPath,
+    resolution
+  }
+
+  await transcode(transcodeOptions)
 
-  await newVideoFile.save()
+  await remove(audioInputPath)
 
-  video.VideoFiles.push(newVideoFile)
+  // Important to do this before getVideoFilename() to take in account the new file extension
+  inputVideoFile.extname = newExtname
+
+  const videoOutputPath = video.getVideoFilePath(inputVideoFile)
+
+  return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
 }
 
 async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
@@ -97,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
   const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
 
   const transcodeOptions = {
+    type: 'hls' as 'hls',
     inputPath: videoInputPath,
     outputPath,
     resolution,
@@ -125,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
   })
 }
 
+// ---------------------------------------------------------------------------
+
 export {
   generateHlsPlaylist,
   optimizeVideofile,
-  transcodeOriginalVideofile
+  transcodeOriginalVideofile,
+  mergeAudioVideofile
+}
+
+// ---------------------------------------------------------------------------
+
+async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
+  const stats = await stat(transcodingPath)
+  const fps = await getVideoFileFPS(transcodingPath)
+
+  await move(transcodingPath, outputPath)
+
+  videoFile.set('size', stats.size)
+  videoFile.set('fps', fps)
+
+  await video.createTorrentAndSetInfoHash(videoFile)
+
+  const updatedVideoFile = await videoFile.save()
+
+  // Add it if this is a new created file
+  if (video.VideoFiles.some(f => f.id === videoFile.id) === false) {
+    video.VideoFiles.push(updatedVideoFile)
+  }
+
+  return video
 }
index 206e9a3d62b4021d946242b7db0989ac7ffe3b91..8faf0adbaca2c3c8a6ff32335b8149e6156192a5 100644 (file)
@@ -107,10 +107,12 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
     return WEBSERVER.URL + staticPath + this.filename
   }
 
-  removeThumbnail () {
+  getPath () {
     const directory = ThumbnailModel.types[this.type].directory
-    const thumbnailPath = join(directory, this.filename)
+    return join(directory, this.filename)
+  }
 
-    return remove(thumbnailPath)
+  removeThumbnail () {
+    return remove(this.getPath())
   }
 }
index 2203a7abaf547ec3da32cba5f7a95e79aa75e93a..05c4907594fbbd36c7d86e0e8a5373c3fe24a664 100644 (file)
@@ -24,6 +24,7 @@ import { VideoModel } from './video'
 import { VideoRedundancyModel } from '../redundancy/video-redundancy'
 import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
 import { FindOptions, QueryTypes, Transaction } from 'sequelize'
+import { MIMETYPES } from '../../initializers/constants'
 
 @Table({
   tableName: 'videoFile',
@@ -161,6 +162,10 @@ export class VideoFileModel extends Model<VideoFileModel> {
       }))
   }
 
+  isAudio () {
+    return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
+  }
+
   hasSameUniqueKeysThan (other: VideoFileModel) {
     return this.fps === other.fps &&
       this.resolution === other.resolution &&
index edf588c166fbc8d64b953ec680f329670395819a..34c6be49be9be139a418fa1b54baf16340d4a5ab 100644 (file)
@@ -3,6 +3,7 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  cleanupTests,
   doubleFollow,
   flushAndRunMultipleServers,
   flushTests,
@@ -39,7 +40,7 @@ describe('Test activitypub', function () {
     const object = res.body
 
     expect(object.type).to.equal('Person')
-    expect(object.id).to.equal('http://localhost:9001/accounts/root')
+    expect(object.id).to.equal('http://localhost:' + servers[0].port + '/accounts/root')
     expect(object.name).to.equal('root')
     expect(object.preferredUsername).to.equal('root')
   })
@@ -49,17 +50,17 @@ describe('Test activitypub', function () {
     const object = res.body
 
     expect(object.type).to.equal('Video')
-    expect(object.id).to.equal('http://localhost:9001/videos/watch/' + videoUUID)
+    expect(object.id).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID)
     expect(object.name).to.equal('video')
   })
 
   it('Should redirect to the origin video object', async function () {
     const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + videoUUID, 302)
 
-    expect(res.header.location).to.equal('http://localhost:9001/videos/watch/' + videoUUID)
+    expect(res.header.location).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID)
   })
 
-  after(function () {
-    killallServers(servers)
+  after(async function () {
+    await cleanupTests(servers)
   })
 })
index 7240bb0fb494ebac59ddb0eabb8d413b2d927cab..3a1c0d321272685c9848908a311dcc6724e42005 100644 (file)
@@ -3,6 +3,7 @@
 import 'mocha'
 
 import {
+  cleanupTests,
   closeAllSequelize,
   createUser,
   doubleFollow,
@@ -48,8 +49,16 @@ describe('Test ActivityPub fetcher', function () {
     const badVideoUUID = res.body.video.uuid
     await uploadVideo(servers[0].url, userAccessToken, { name: 'video user' })
 
-    await setActorField(1, 'http://localhost:9001/accounts/user1', 'url', 'http://localhost:9002/accounts/user1')
-    await setVideoField(1, badVideoUUID, 'url', 'http://localhost:9003/videos/watch/' + badVideoUUID)
+    {
+      const to = 'http://localhost:' + servers[0].port + '/accounts/user1'
+      const value = 'http://localhost:' + servers[1].port + '/accounts/user1'
+      await setActorField(servers[0].internalServerNumber, to, 'url', value)
+    }
+
+    {
+      const value = 'http://localhost:' + servers[2].port + '/videos/watch/' + badVideoUUID
+      await setVideoField(servers[0].internalServerNumber, badVideoUUID, 'url', value)
+    }
   })
 
   it('Should add only the video with a valid actor URL', async function () {
@@ -78,7 +87,9 @@ describe('Test ActivityPub fetcher', function () {
   })
 
   after(async function () {
-    killallServers(servers)
+    this.timeout(10000)
+
+    await cleanupTests(servers)
 
     await closeAllSequelize(servers)
   })
index 9be9aa495b42b8e35009410e75a3163b0008736f..921ee874c7e9893b2f955c6cd1f561bdc7a06746 100644 (file)
@@ -2,13 +2,14 @@
 
 import 'mocha'
 import {
+  cleanupTests, closeAllSequelize,
   createVideoPlaylist,
   doubleFollow,
   flushAndRunMultipleServers,
   generateUserAccessToken,
   getVideo,
   getVideoPlaylist,
-  killallServers, rateVideo,
+  killallServers,
   reRunServer,
   ServerInfo,
   setAccessTokensToServers,
@@ -48,26 +49,26 @@ describe('Test AP refresher', function () {
     }
 
     {
-      const a1 = await generateUserAccessToken(servers[1], 'user1')
-      await uploadVideo(servers[1].url, a1, { name: 'video4' })
+      const a1 = await generateUserAccessToken(servers[ 1 ], 'user1')
+      await uploadVideo(servers[ 1 ].url, a1, { name: 'video4' })
 
-      const a2 = await generateUserAccessToken(servers[1], 'user2')
-      await uploadVideo(servers[1].url, a2, { name: 'video5' })
+      const a2 = await generateUserAccessToken(servers[ 1 ], 'user2')
+      await uploadVideo(servers[ 1 ].url, a2, { name: 'video5' })
     }
 
     {
-      const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
-      const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
+      const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id }
+      const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs })
       playlistUUID1 = res.body.videoPlaylist.uuid
     }
 
     {
-      const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
-      const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
+      const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id }
+      const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs })
       playlistUUID2 = res.body.videoPlaylist.uuid
     }
 
-    await doubleFollow(servers[0], servers[1])
+    await doubleFollow(servers[ 0 ], servers[ 1 ])
   })
 
   describe('Videos refresher', function () {
@@ -78,7 +79,7 @@ describe('Test AP refresher', function () {
       await wait(10000)
 
       // Change UUID so the remote server returns a 404
-      await setVideoField(2, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
+      await setVideoField(servers[ 1 ].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
 
       await getVideo(servers[ 0 ].url, videoUUID1)
       await getVideo(servers[ 0 ].url, videoUUID2)
@@ -94,7 +95,7 @@ describe('Test AP refresher', function () {
 
       killallServers([ servers[ 1 ] ])
 
-      await setVideoField(2, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
+      await setVideoField(servers[ 1 ].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
 
       // Video will need a refresh
       await wait(10000)
@@ -121,15 +122,16 @@ describe('Test AP refresher', function () {
       await wait(10000)
 
       // Change actor name so the remote server returns a 404
-      await setActorField(2, 'http://localhost:9002/accounts/user2', 'preferredUsername', 'toto')
+      const to = 'http://localhost:' + servers[ 1 ].port + '/accounts/user2'
+      await setActorField(servers[ 1 ].internalServerNumber, to, 'preferredUsername', 'toto')
 
-      await getAccount(servers[ 0 ].url, 'user1@localhost:9002')
-      await getAccount(servers[ 0 ].url, 'user2@localhost:9002')
+      await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port)
+      await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port)
 
       await waitJobs(servers)
 
-      await getAccount(servers[ 0 ].url, 'user1@localhost:9002', 200)
-      await getAccount(servers[ 0 ].url, 'user2@localhost:9002', 404)
+      await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port, 200)
+      await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port, 404)
     })
   })
 
@@ -141,7 +143,7 @@ describe('Test AP refresher', function () {
       await wait(10000)
 
       // Change UUID so the remote server returns a 404
-      await setPlaylistField(2, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
+      await setPlaylistField(servers[ 1 ].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
 
       await getVideoPlaylist(servers[ 0 ].url, playlistUUID1)
       await getVideoPlaylist(servers[ 0 ].url, playlistUUID2)
@@ -153,7 +155,11 @@ describe('Test AP refresher', function () {
     })
   })
 
-  after(function () {
-    killallServers(servers)
+  after(async function () {
+    this.timeout(10000)
+
+    await cleanupTests(servers)
+
+    await closeAllSequelize(servers)
   })
 })
index 11e6859bfb360cfe52d72d80a86452e4a0d96591..dc960c5c3452f21e6cf3cc3875f44c6b6f2b852a 100644 (file)
@@ -3,9 +3,9 @@
 import 'mocha'
 
 import {
+  cleanupTests,
   closeAllSequelize,
   flushAndRunMultipleServers,
-  flushTests,
   killallServers,
   ServerInfo,
   setActorField
@@ -18,18 +18,26 @@ import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-u
 
 const expect = chai.expect
 
-function setKeysOfServer2 (serverNumber: number, publicKey: string, privateKey: string) {
+function setKeysOfServer (onServer: ServerInfo, ofServer: ServerInfo, publicKey: string, privateKey: string) {
   return Promise.all([
-    setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'publicKey', publicKey),
-    setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'privateKey', privateKey)
+    setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'publicKey', publicKey),
+    setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'privateKey', privateKey)
   ])
 }
 
-function setKeysOfServer3 (serverNumber: number, publicKey: string, privateKey: string) {
-  return Promise.all([
-    setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'publicKey', publicKey),
-    setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'privateKey', privateKey)
-  ])
+function getAnnounceWithoutContext (server2: ServerInfo) {
+  const json = require('./json/peertube/announce-without-context.json')
+  const result: typeof json = {}
+
+  for (const key of Object.keys(json)) {
+    if (Array.isArray(json[key])) {
+      result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`))
+    } else {
+      result[ key ] = json[ key ].replace(':9002', `:${server2.port}`)
+    }
+  }
+
+  return result
 }
 
 describe('Test ActivityPub security', function () {
@@ -38,13 +46,13 @@ describe('Test ActivityPub security', function () {
 
   const keys = require('./json/peertube/keys.json')
   const invalidKeys = require('./json/peertube/invalid-keys.json')
-  const baseHttpSignature = {
+  const baseHttpSignature = () => ({
     algorithm: HTTP_SIGNATURE.ALGORITHM,
     authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
-    keyId: 'acct:peertube@localhost:9002',
+    keyId: 'acct:peertube@localhost:' + servers[1].port,
     key: keys.privateKey,
     headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
-  }
+  })
 
   // ---------------------------------------------------------------
 
@@ -55,56 +63,56 @@ describe('Test ActivityPub security', function () {
 
     url = servers[0].url + '/inbox'
 
-    await setKeysOfServer2(1, keys.publicKey, keys.privateKey)
+    await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
 
-    const to = { url: 'http://localhost:9001/accounts/peertube' }
-    const by = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
+    const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
+    const by = { url: 'http://localhost:' + servers[1].port + '/accounts/peertube', privateKey: keys.privateKey }
     await makeFollowRequest(to, by)
   })
 
   describe('When checking HTTP signature', function () {
 
     it('Should fail with an invalid digest', async function () {
-      const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
+      const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
       const headers = {
         Digest: buildDigest({ hello: 'coucou' })
       }
 
-      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(403)
     })
 
     it('Should fail with an invalid date', async function () {
-      const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
+      const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
       const headers = buildGlobalHeaders(body)
       headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
 
-      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(403)
     })
 
     it('Should fail with bad keys', async function () {
-      await setKeysOfServer2(1, invalidKeys.publicKey, invalidKeys.privateKey)
-      await setKeysOfServer2(2, invalidKeys.publicKey, invalidKeys.privateKey)
+      await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
+      await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
 
-      const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
+      const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
       const headers = buildGlobalHeaders(body)
 
-      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(403)
     })
 
     it('Should succeed with a valid HTTP signature', async function () {
-      await setKeysOfServer2(1, keys.publicKey, keys.privateKey)
-      await setKeysOfServer2(2, keys.publicKey, keys.privateKey)
+      await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
+      await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
 
-      const body = activityPubContextify(require('./json/peertube/announce-without-context.json'))
+      const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
       const headers = buildGlobalHeaders(body)
 
-      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(204)
     })
@@ -112,28 +120,28 @@ describe('Test ActivityPub security', function () {
 
   describe('When checking Linked Data Signature', function () {
     before(async () => {
-      await setKeysOfServer3(3, keys.publicKey, keys.privateKey)
+      await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
 
-      const to = { url: 'http://localhost:9001/accounts/peertube' }
-      const by = { url: 'http://localhost:9003/accounts/peertube', privateKey: keys.privateKey }
+      const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
+      const by = { url: 'http://localhost:' + servers[2].port + '/accounts/peertube', privateKey: keys.privateKey }
       await makeFollowRequest(to, by)
     })
 
     it('Should fail with bad keys', async function () {
       this.timeout(10000)
 
-      await setKeysOfServer3(1, invalidKeys.publicKey, invalidKeys.privateKey)
-      await setKeysOfServer3(3, invalidKeys.publicKey, invalidKeys.privateKey)
+      await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
+      await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
 
-      const body = require('./json/peertube/announce-without-context.json')
-      body.actor = 'http://localhost:9003/accounts/peertube'
+      const body = getAnnounceWithoutContext(servers[1])
+      body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
 
-      const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
+      const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
       const signedBody = await buildSignedActivity(signer, body)
 
       const headers = buildGlobalHeaders(signedBody)
 
-      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(403)
     })
@@ -141,20 +149,20 @@ describe('Test ActivityPub security', function () {
     it('Should fail with an altered body', async function () {
       this.timeout(10000)
 
-      await setKeysOfServer3(1, keys.publicKey, keys.privateKey)
-      await setKeysOfServer3(3, keys.publicKey, keys.privateKey)
+      await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
+      await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
 
-      const body = require('./json/peertube/announce-without-context.json')
-      body.actor = 'http://localhost:9003/accounts/peertube'
+      const body = getAnnounceWithoutContext(servers[1])
+      body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
 
-      const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
+      const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
       const signedBody = await buildSignedActivity(signer, body)
 
-      signedBody.actor = 'http://localhost:9003/account/peertube'
+      signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
 
       const headers = buildGlobalHeaders(signedBody)
 
-      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(403)
     })
@@ -162,22 +170,24 @@ describe('Test ActivityPub security', function () {
     it('Should succeed with a valid signature', async function () {
       this.timeout(10000)
 
-      const body = require('./json/peertube/announce-without-context.json')
-      body.actor = 'http://localhost:9003/accounts/peertube'
+      const body = getAnnounceWithoutContext(servers[1])
+      body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
 
-      const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' }
+      const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
       const signedBody = await buildSignedActivity(signer, body)
 
       const headers = buildGlobalHeaders(signedBody)
 
-      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers)
+      const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
 
       expect(response.statusCode).to.equal(204)
     })
   })
 
   after(async function () {
-    killallServers(servers)
+    this.timeout(10000)
+
+    await cleanupTests(servers)
 
     await closeAllSequelize(servers)
   })
index 2a2ec606a5f8ef137b39fdc6dfa1deb1dcf5ae6c..8155e11aba0d9a62ca7aabaa60c7d74910eb381c 100644 (file)
@@ -59,6 +59,7 @@ describe('Test config API validators', function () {
     transcoding: {
       enabled: true,
       allowAdditionalExtensions: true,
+      allowAudioFiles: true,
       threads: 1,
       resolutions: {
         '240p': false,
diff --git a/server/tests/api/index-1.ts b/server/tests/api/index-1.ts
deleted file mode 100644 (file)
index 75cdd90..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-import './check-params'
-import './notifications'
-import './search'
diff --git a/server/tests/api/index-2.ts b/server/tests/api/index-2.ts
deleted file mode 100644 (file)
index ed93faa..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-import './server'
-import './users'
diff --git a/server/tests/api/index-3.ts b/server/tests/api/index-3.ts
deleted file mode 100644 (file)
index 39823b8..0000000
+++ /dev/null
@@ -1 +0,0 @@
-import './videos'
diff --git a/server/tests/api/index-4.ts b/server/tests/api/index-4.ts
deleted file mode 100644 (file)
index 7d8be2b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-import './redundancy'
-import './activitypub'
index bc140f860e02be6f3b865f3be4faff88bf682806..bac77ab2e91ef6421b420be7f23df8f59eb6503c 100644 (file)
@@ -1,5 +1,9 @@
 // Order of the tests we want to execute
-import './index-1'
-import './index-2'
-import './index-3'
-import './index-4'
+import './activitypub'
+import './check-params'
+import './notifications'
+import './redundancy'
+import './search'
+import './server'
+import './users'
+import './videos'
index 95ac8fc519cbea7f9d1b8414525c70b27a77eaf7..b573f850ee768bbed49e22400ed609f255cec245 100644 (file)
@@ -1 +1 @@
-export * from './user-notifications'
+import './user-notifications'
index f479e1785509c4ae9e75db7dae37482e74fbe6f3..662b64e05467f4ea0a9900eacf40f15c5e60d020 100644 (file)
@@ -114,11 +114,12 @@ describe('Test users notifications', function () {
   before(async function () {
     this.timeout(120000)
 
-    await MockSmtpServer.Instance.collectEmails(emails)
+    const port = await MockSmtpServer.Instance.collectEmails(emails)
 
     const overrideConfig = {
       smtp: {
-        hostname: 'localhost'
+        hostname: 'localhost',
+        port
       }
     }
     servers = await flushAndRunMultipleServers(3, overrideConfig)
@@ -194,7 +195,7 @@ describe('Test users notifications', function () {
     it('Should send a new video notification if the user follows the local video publisher', async function () {
       this.timeout(15000)
 
-      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001')
+      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[0].port)
       await waitJobs(servers)
 
       const { name, uuid } = await uploadVideoByLocalAccount(servers)
@@ -204,7 +205,7 @@ describe('Test users notifications', function () {
     it('Should send a new video notification from a remote account', async function () {
       this.timeout(50000) // Server 2 has transcoding enabled
 
-      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002')
+      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[1].port)
       await waitJobs(servers)
 
       const { name, uuid } = await uploadVideoByRemoteAccount(servers)
@@ -578,7 +579,9 @@ describe('Test users notifications', function () {
       const uuid = resVideo.body.video.uuid
 
       await waitJobs(servers)
-      const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'hello @user_1@localhost:9001 1')
+
+      const text1 = `hello @user_1@localhost:${servers[ 0 ].port} 1`
+      const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1)
       const server2ThreadId = resThread.body.comment.id
 
       await waitJobs(servers)
@@ -588,8 +591,8 @@ describe('Test users notifications', function () {
       const server1ThreadId = resThread2.body.data[0].id
       await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence')
 
-      const text = '@user_1@localhost:9001 hello 2 @root@localhost:9001'
-      await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text)
+      const text2 = `@user_1@localhost:${servers[ 0 ].port} hello 2 @root@localhost:${servers[ 0 ].port}`
+      await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2)
 
       await waitJobs(servers)
 
@@ -889,10 +892,10 @@ describe('Test users notifications', function () {
 
       await waitJobs(servers)
 
-      await checkNewInstanceFollower(baseParams, 'localhost:9003', 'presence')
+      await checkNewInstanceFollower(baseParams, 'localhost:' + servers[2].port, 'presence')
 
       const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
-      await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:9003', 'absence')
+      await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence')
     })
   })
 
@@ -933,29 +936,29 @@ describe('Test users notifications', function () {
     it('Should notify when a local channel is following one of our channel', async function () {
       this.timeout(10000)
 
-      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
+      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
       await waitJobs(servers)
 
       await checkNewActorFollow(baseParams, 'channel', 'root', 'super root name', myChannelName, 'presence')
 
-      await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
+      await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
     })
 
     it('Should notify when a remote channel is following one of our channel', async function () {
       this.timeout(10000)
 
-      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
       await waitJobs(servers)
 
       await checkNewActorFollow(baseParams, 'channel', 'root', 'super root 2 name', myChannelName, 'presence')
 
-      await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+      await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
     })
 
     it('Should notify when a local account is following one of our channel', async function () {
       this.timeout(10000)
 
-      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:9001')
+      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port)
 
       await waitJobs(servers)
 
@@ -965,7 +968,7 @@ describe('Test users notifications', function () {
     it('Should notify when a remote account is following one of our channel', async function () {
       this.timeout(10000)
 
-      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:9001')
+      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port)
 
       await waitJobs(servers)
 
@@ -1019,8 +1022,8 @@ describe('Test users notifications', function () {
       autoBlacklistTestsCustomConfig.transcoding.enabled = true
       await updateCustomConfig(servers[0].url, servers[0].accessToken, autoBlacklistTestsCustomConfig)
 
-      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
-      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
+      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
 
     })
 
@@ -1142,8 +1145,8 @@ describe('Test users notifications', function () {
     after(async () => {
       await updateCustomConfig(servers[0].url, servers[0].accessToken, currentCustomConfig)
 
-      await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
-      await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+      await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
+      await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
     })
   })
 
index e31329c255fbf97426f1b806beb8375e40ddd03a..6f2c5907661635e81b6da076cd7dd4533b069d79 100644 (file)
@@ -100,7 +100,7 @@ async function check1WebSeed (videoUUID?: string) {
   if (!videoUUID) videoUUID = video1Server2UUID
 
   const webseeds = [
-    'http://localhost:9002/static/webseed/' + videoUUID
+    `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}`
   ]
 
   for (const server of servers) {
@@ -118,8 +118,8 @@ async function check2Webseeds (videoUUID?: string) {
   if (!videoUUID) videoUUID = video1Server2UUID
 
   const webseeds = [
-    'http://localhost:9001/static/redundancy/' + videoUUID,
-    'http://localhost:9002/static/webseed/' + videoUUID
+    `http://localhost:${servers[ 0 ].port}/static/redundancy/${videoUUID}`,
+    `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}`
   ]
 
   for (const server of servers) {
@@ -145,7 +145,12 @@ async function check2Webseeds (videoUUID?: string) {
     }
   }
 
-  for (const directory of [ 'test1/redundancy', 'test2/videos' ]) {
+  const directories = [
+    'test' + servers[0].internalServerNumber + '/redundancy',
+    'test' + servers[1].internalServerNumber + '/videos'
+  ]
+
+  for (const directory of directories) {
     const files = await readdir(join(root(), directory))
     expect(files).to.have.length.at.least(4)
 
@@ -194,7 +199,12 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
     await checkSegmentHash(baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist)
   }
 
-  for (const directory of [ 'test1/redundancy/hls', 'test2/streaming-playlists/hls' ]) {
+  const directories = [
+    'test' + servers[0].internalServerNumber + '/redundancy/hls',
+    'test' + servers[1].internalServerNumber + '/streaming-playlists/hls'
+  ]
+
+  for (const directory of directories) {
     const files = await readdir(join(root(), directory, videoUUID))
     expect(files).to.have.length.at.least(4)
 
@@ -239,8 +249,8 @@ async function enableRedundancyOnServer1 () {
 
   const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
   const follows: ActorFollow[] = res.body.data
-  const server2 = follows.find(f => f.following.host === 'localhost:9002')
-  const server3 = follows.find(f => f.following.host === 'localhost:9003')
+  const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
+  const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
 
   expect(server3).to.not.be.undefined
   expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -254,8 +264,8 @@ async function disableRedundancyOnServer1 () {
 
   const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
   const follows: ActorFollow[] = res.body.data
-  const server2 = follows.find(f => f.following.host === 'localhost:9002')
-  const server3 = follows.find(f => f.following.host === 'localhost:9003')
+  const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
+  const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
 
   expect(server3).to.not.be.undefined
   expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -475,12 +485,12 @@ describe('Test videos redundancy', function () {
       await wait(10000)
 
       try {
-        await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001')
+        await checkContains(servers, 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
       } catch {
         // Maybe a server deleted a redundancy in the scheduler
         await wait(2000)
 
-        await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001')
+        await checkContains(servers, 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
       }
     })
 
@@ -491,7 +501,7 @@ describe('Test videos redundancy', function () {
 
       await wait(15000)
 
-      await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A9001')
+      await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
     })
 
     after(async function () {
index 4d1ceb767aacc194ef652113a886e446cab57cf3..8a008b8c63214232320bef327599458c7f6ccfb4 100644 (file)
@@ -3,16 +3,17 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
-  addVideoChannel, cleanupTests,
+  addVideoChannel,
+  cleanupTests,
   createUser,
   deleteVideoChannel,
   flushAndRunMultipleServers,
-  flushTests,
-  getVideoChannelsList, getVideoChannelVideos,
-  killallServers,
+  getVideoChannelsList,
+  getVideoChannelVideos,
   ServerInfo,
   setAccessTokensToServers,
-  updateMyUser, updateVideo,
+  updateMyUser,
+  updateVideo,
   updateVideoChannel,
   uploadVideo,
   userLogin,
@@ -24,7 +25,7 @@ import { searchVideoChannel } from '../../../../shared/extra-utils/search/video-
 
 const expect = chai.expect
 
-describe('Test ActivityPub video channels search', function () {
+describe('Test ActivityPub video channels search', function () {
   let servers: ServerInfo[]
   let userServer2Token: string
   let videoServer2UUID: string
@@ -67,7 +68,7 @@ describe('Test a ActivityPub video channels search', function () {
 
   it('Should not find a remote video channel', async function () {
     {
-      const search = 'http://localhost:9002/video-channels/channel1_server3'
+      const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3'
       const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken)
 
       expect(res.body.total).to.equal(0)
@@ -77,7 +78,7 @@ describe('Test a ActivityPub video channels search', function () {
 
     {
       // Without token
-      const search = 'http://localhost:9002/video-channels/channel1_server2'
+      const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
       const res = await searchVideoChannel(servers[0].url, search)
 
       expect(res.body.total).to.equal(0)
@@ -88,8 +89,8 @@ describe('Test a ActivityPub video channels search', function () {
 
   it('Should search a local video channel', async function () {
     const searches = [
-      'http://localhost:9001/video-channels/channel1_server1',
-      'channel1_server1@localhost:9001'
+      'http://localhost:' + servers[ 0 ].port + '/video-channels/channel1_server1',
+      'channel1_server1@localhost:' + servers[ 0 ].port
     ]
 
     for (const search of searches) {
@@ -105,8 +106,8 @@ describe('Test a ActivityPub video channels search', function () {
 
   it('Should search a remote video channel with URL or handle', async function () {
     const searches = [
-      'http://localhost:9002/video-channels/channel1_server2',
-      'channel1_server2@localhost:9002'
+      'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2',
+      'channel1_server2@localhost:' + servers[ 1 ].port
     ]
 
     for (const search of searches) {
@@ -134,13 +135,13 @@ describe('Test a ActivityPub video channels search', function () {
 
     await waitJobs(servers)
 
-    const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:9002', 0, 5)
+    const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5)
     expect(res.body.total).to.equal(0)
     expect(res.body.data).to.have.lengthOf(0)
   })
 
   it('Should list video channel videos of server 2 with token', async function () {
-    const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:9002', 0, 5)
+    const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5)
 
     expect(res.body.total).to.equal(1)
     expect(res.body.data[0].name).to.equal('video 1 server 2')
@@ -156,7 +157,7 @@ describe('Test a ActivityPub video channels search', function () {
     // Expire video channel
     await wait(10000)
 
-    const search = 'http://localhost:9002/video-channels/channel1_server2'
+    const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
     const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.have.lengthOf(1)
@@ -179,12 +180,13 @@ describe('Test a ActivityPub video channels search', function () {
     // Expire video channel
     await wait(10000)
 
-    const search = 'http://localhost:9002/video-channels/channel1_server2'
+    const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
     await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
 
     await waitJobs(servers)
 
-    const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:9002', 0, 5, '-createdAt')
+    const videoChannelName = 'channel1_server2@localhost:' + servers[ 1 ].port
+    const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt')
 
     expect(res.body.total).to.equal(2)
     expect(res.body.data[0].name).to.equal('video 2 server 2')
@@ -200,7 +202,8 @@ describe('Test a ActivityPub video channels search', function () {
     // Expire video
     await wait(10000)
 
-    const res = await searchVideoChannel(servers[0].url, 'http://localhost:9002/video-channels/channel1_server2', servers[0].accessToken)
+    const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
+    const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
     expect(res.body.total).to.equal(0)
     expect(res.body.data).to.have.lengthOf(0)
   })
index e039961cb5d4d279b68a27da0e6ecc54b9249868..dbfefadda60d7dbda9b4de12e08ceb7ff711f863 100644 (file)
@@ -4,25 +4,24 @@ import * as chai from 'chai'
 import 'mocha'
 import {
   addVideoChannel,
+  cleanupTests,
   flushAndRunMultipleServers,
-  flushTests,
   getVideosList,
-  killallServers,
   removeVideo,
+  searchVideo,
   searchVideoWithToken,
   ServerInfo,
   setAccessTokensToServers,
   updateVideo,
   uploadVideo,
-  wait,
-  searchVideo, cleanupTests
+  wait
 } from '../../../../shared/extra-utils'
 import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
 
 const expect = chai.expect
 
-describe('Test ActivityPub videos search', function () {
+describe('Test ActivityPub videos search', function () {
   let servers: ServerInfo[]
   let videoServer1UUID: string
   let videoServer2UUID: string
@@ -49,7 +48,8 @@ describe('Test a ActivityPub videos search', function () {
 
   it('Should not find a remote video', async function () {
     {
-      const res = await searchVideoWithToken(servers[ 0 ].url, 'http://localhost:9002/videos/watch/43', servers[ 0 ].accessToken)
+      const search = 'http://localhost:' + servers[1].port + '/videos/watch/43'
+      const res = await searchVideoWithToken(servers[ 0 ].url, search, servers[ 0 ].accessToken)
 
       expect(res.body.total).to.equal(0)
       expect(res.body.data).to.be.an('array')
@@ -58,7 +58,8 @@ describe('Test a ActivityPub videos search', function () {
 
     {
       // Without token
-      const res = await searchVideo(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID)
+      const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
+      const res = await searchVideo(servers[0].url, search)
 
       expect(res.body.total).to.equal(0)
       expect(res.body.data).to.be.an('array')
@@ -67,7 +68,8 @@ describe('Test a ActivityPub videos search', function () {
   })
 
   it('Should search a local video', async function () {
-    const res = await searchVideo(servers[0].url, 'http://localhost:9001/videos/watch/' + videoServer1UUID)
+    const search = 'http://localhost:' + servers[0].port + '/videos/watch/' + videoServer1UUID
+    const res = await searchVideo(servers[0].url, search)
 
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.be.an('array')
@@ -76,7 +78,8 @@ describe('Test a ActivityPub videos search', function () {
   })
 
   it('Should search a remote video', async function () {
-    const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken)
+    const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
+    const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
 
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.be.an('array')
@@ -114,12 +117,13 @@ describe('Test a ActivityPub videos search', function () {
     await wait(10000)
 
     // Will run refresh async
-    await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken)
+    const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
+    await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
 
     // Wait refresh
     await wait(5000)
 
-    const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken)
+    const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
     expect(res.body.total).to.equal(1)
     expect(res.body.data).to.have.lengthOf(1)
 
@@ -139,12 +143,13 @@ describe('Test a ActivityPub videos search', function () {
     await wait(10000)
 
     // Will run refresh async
-    await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken)
+    const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
+    await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
 
     // Wait refresh
     await wait(5000)
 
-    const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken)
+    const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
     expect(res.body.total).to.equal(0)
     expect(res.body.data).to.have.lengthOf(0)
   })
index 1a086b33affdfee1b3b1ddfebcb43bde2f9cb9c8..92cc0dc716b2f41359e1b6e0eaad72820fa0723e 100644 (file)
@@ -4,21 +4,19 @@ import * as chai from 'chai'
 import 'mocha'
 import {
   advancedVideosSearch,
-  flushTests,
-  killallServers,
+  cleanupTests,
   flushAndRunServer,
+  immutableAssign,
   searchVideo,
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo,
-  wait,
-  immutableAssign,
-  cleanupTests
+  wait
 } from '../../../../shared/extra-utils'
 
 const expect = chai.expect
 
-describe('Test videos search', function () {
+describe('Test videos search', function () {
   let server: ServerInfo = null
   let startDate: string
 
index c0d11914bf2d047557c1c57b68701b498500d8ce..8ea21158ad33212414293fb09a05259caabc2b12 100644 (file)
@@ -11,17 +11,17 @@ import {
   getAbout,
   getConfig,
   getCustomConfig,
-  killallServers,
+  killallServers, parallelTests,
   registerUser,
-  reRunServer,
+  reRunServer, ServerInfo,
   setAccessTokensToServers,
-  updateCustomConfig
+  updateCustomConfig, uploadVideo
 } from '../../../../shared/extra-utils'
 import { ServerConfig } from '../../../../shared/models'
 
 const expect = chai.expect
 
-function checkInitialConfig (data: CustomConfig) {
+function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
   expect(data.instance.name).to.equal('PeerTube')
   expect(data.instance.shortDescription).to.equal(
     'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser ' +
@@ -45,13 +45,14 @@ function checkInitialConfig (data: CustomConfig) {
   expect(data.signup.limit).to.equal(4)
   expect(data.signup.requiresEmailVerification).to.be.false
 
-  expect(data.admin.email).to.equal('admin1@example.com')
+  expect(data.admin.email).to.equal('admin' + server.internalServerNumber + '@example.com')
   expect(data.contactForm.enabled).to.be.true
 
   expect(data.user.videoQuota).to.equal(5242880)
   expect(data.user.videoQuotaDaily).to.equal(-1)
   expect(data.transcoding.enabled).to.be.false
   expect(data.transcoding.allowAdditionalExtensions).to.be.false
+  expect(data.transcoding.allowAudioFiles).to.be.false
   expect(data.transcoding.threads).to.equal(2)
   expect(data.transcoding.resolutions['240p']).to.be.true
   expect(data.transcoding.resolutions['360p']).to.be.true
@@ -89,7 +90,11 @@ function checkUpdatedConfig (data: CustomConfig) {
   expect(data.signup.limit).to.equal(5)
   expect(data.signup.requiresEmailVerification).to.be.false
 
-  expect(data.admin.email).to.equal('superadmin1@example.com')
+  // We override admin email in parallel tests, so skip this exception
+  if (parallelTests() === false) {
+    expect(data.admin.email).to.equal('superadmin1@example.com')
+  }
+
   expect(data.contactForm.enabled).to.be.false
 
   expect(data.user.videoQuota).to.equal(5242881)
@@ -98,6 +103,7 @@ function checkUpdatedConfig (data: CustomConfig) {
   expect(data.transcoding.enabled).to.be.true
   expect(data.transcoding.threads).to.equal(1)
   expect(data.transcoding.allowAdditionalExtensions).to.be.true
+  expect(data.transcoding.allowAudioFiles).to.be.true
   expect(data.transcoding.resolutions['240p']).to.be.false
   expect(data.transcoding.resolutions['360p']).to.be.true
   expect(data.transcoding.resolutions['480p']).to.be.true
@@ -118,6 +124,7 @@ describe('Test config', function () {
 
   before(async function () {
     this.timeout(30000)
+
     server = await flushAndRunServer(1)
     await setAccessTokensToServers([ server ])
   })
@@ -153,6 +160,9 @@ describe('Test config', function () {
     expect(data.video.file.extensions).to.contain('.webm')
     expect(data.video.file.extensions).to.contain('.ogv')
 
+    await uploadVideo(server.url, server.accessToken, { fixture: 'video_short.mkv' }, 400)
+    await uploadVideo(server.url, server.accessToken, { fixture: 'sample.ogg' }, 400)
+
     expect(data.contactForm.enabled).to.be.true
   })
 
@@ -160,7 +170,7 @@ describe('Test config', function () {
     const res = await getCustomConfig(server.url, server.accessToken)
     const data = res.body as CustomConfig
 
-    checkInitialConfig(data)
+    checkInitialConfig(server, data)
   })
 
   it('Should update the customized configuration', async function () {
@@ -210,6 +220,7 @@ describe('Test config', function () {
       transcoding: {
         enabled: true,
         allowAdditionalExtensions: true,
+        allowAudioFiles: true,
         threads: 1,
         resolutions: {
           '240p': false,
@@ -264,6 +275,12 @@ describe('Test config', function () {
     expect(data.video.file.extensions).to.contain('.ogv')
     expect(data.video.file.extensions).to.contain('.flv')
     expect(data.video.file.extensions).to.contain('.mkv')
+    expect(data.video.file.extensions).to.contain('.mp3')
+    expect(data.video.file.extensions).to.contain('.ogg')
+    expect(data.video.file.extensions).to.contain('.flac')
+
+    await uploadVideo(server.url, server.accessToken, { fixture: 'video_short.mkv' }, 200)
+    await uploadVideo(server.url, server.accessToken, { fixture: 'sample.ogg' }, 200)
   })
 
   it('Should have the configuration updated after a restart', async function () {
@@ -297,7 +314,7 @@ describe('Test config', function () {
     const res = await getCustomConfig(server.url, server.accessToken)
     const data = res.body
 
-    checkInitialConfig(data)
+    checkInitialConfig(server, data)
   })
 
   after(async function () {
index ba51198b3de72b35d3de072342c41647e8236479..87e55060ca050da199331a44650d4e05b3926d31 100644 (file)
@@ -24,11 +24,12 @@ describe('Test contact form', function () {
   before(async function () {
     this.timeout(30000)
 
-    await MockSmtpServer.Instance.collectEmails(emails)
+    const port = await MockSmtpServer.Instance.collectEmails(emails)
 
     const overrideConfig = {
       smtp: {
-        hostname: 'localhost'
+        hostname: 'localhost',
+        port
       }
     }
     server = await flushAndRunServer(1, overrideConfig)
@@ -53,7 +54,7 @@ describe('Test contact form', function () {
 
     expect(email['from'][0]['address']).equal('test-admin@localhost')
     expect(email['from'][0]['name']).equal('toto@example.com')
-    expect(email['to'][0]['address']).equal('admin1@example.com')
+    expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
     expect(email['subject']).contains('Contact form')
     expect(email['text']).contains('my super message')
   })
index bacdf1b1b7fd9cd3d2ae41fbecd999f62c9cc28a..5929a3adbe6fc004f6d4da0420abb6e356d0a927 100644 (file)
@@ -7,18 +7,18 @@ import {
   askResetPassword,
   askSendVerifyEmail,
   blockUser,
-  createUser, removeVideoFromBlacklist,
+  cleanupTests,
+  createUser,
+  flushAndRunServer,
+  removeVideoFromBlacklist,
   reportVideoAbuse,
   resetPassword,
-  flushAndRunServer,
+  ServerInfo,
+  setAccessTokensToServers,
   unblockUser,
   uploadVideo,
   userLogin,
-  verifyEmail,
-  flushTests,
-  killallServers,
-  ServerInfo,
-  setAccessTokensToServers, cleanupTests
+  verifyEmail
 } from '../../../../shared/extra-utils'
 import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
 import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -37,15 +37,17 @@ describe('Test emails', function () {
     username: 'user_1',
     password: 'super_password'
   }
+  let emailPort: number
 
   before(async function () {
     this.timeout(30000)
 
-    await MockSmtpServer.Instance.collectEmails(emails)
+    emailPort = await MockSmtpServer.Instance.collectEmails(emails)
 
     const overrideConfig = {
       smtp: {
-        hostname: 'localhost'
+        hostname: 'localhost',
+        port: emailPort
       }
     }
     server = await flushAndRunServer(1, overrideConfig)
@@ -87,7 +89,7 @@ describe('Test emails', function () {
 
       const email = emails[0]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains('password')
@@ -132,9 +134,9 @@ describe('Test emails', function () {
 
       const email = emails[1]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
-      expect(email['to'][0]['address']).equal('admin1@example.com')
+      expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
       expect(email['subject']).contains('abuse')
       expect(email['text']).contains(videoUUID)
     })
@@ -153,7 +155,7 @@ describe('Test emails', function () {
 
       const email = emails[2]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains(' blocked')
@@ -171,7 +173,7 @@ describe('Test emails', function () {
 
       const email = emails[3]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains(' unblocked')
@@ -191,7 +193,7 @@ describe('Test emails', function () {
 
       const email = emails[4]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains(' blacklisted')
@@ -209,7 +211,7 @@ describe('Test emails', function () {
 
       const email = emails[5]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains(' unblacklisted')
@@ -229,7 +231,7 @@ describe('Test emails', function () {
 
       const email = emails[6]
 
-      expect(email['from'][0]['name']).equal('localhost:9001')
+      expect(email['from'][0]['name']).equal('localhost:' + server.port)
       expect(email['from'][0]['address']).equal('test-admin@localhost')
       expect(email['to'][0]['address']).equal('user_1@example.com')
       expect(email['subject']).contains('Verify')
index 4285a9e7a864f18ee71caa84072801cec1ca48bc..ac3ff37f0f474eec1fc5d08306d1350233d4e00b 100644 (file)
@@ -3,16 +3,16 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  cleanupTests,
   doubleFollow,
+  flushAndRunMultipleServers,
   getAccountVideos,
   getVideo,
   getVideoChannelVideos,
   getVideoWithToken,
-  flushAndRunMultipleServers,
-  killallServers,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo, cleanupTests
+  uploadVideo
 } from '../../../../shared/extra-utils'
 import { unfollow } from '../../../../shared/extra-utils/server/follows'
 import { userLogin } from '../../../../shared/extra-utils/users/login'
@@ -66,28 +66,30 @@ describe('Test follow constraints', function () {
       })
 
       it('Should list local account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9001', 0, 5)
+        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[0].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9002', 0, 5)
+        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[1].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list local channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9001', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[0].port
+        const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9002', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[1].port
+        const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
@@ -104,28 +106,30 @@ describe('Test follow constraints', function () {
       })
 
       it('Should list local account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9001', 0, 5)
+        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[0].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9002', 0, 5)
+        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[1].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list local channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9001', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[0].port
+        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9002', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[1].port
+        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
@@ -152,28 +156,30 @@ describe('Test follow constraints', function () {
       })
 
       it('Should list local account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9001', 0, 5)
+        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[0].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should not list remote account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9002', 0, 5)
+        const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[1].port, 0, 5)
 
         expect(res.body.total).to.equal(0)
         expect(res.body.data).to.have.lengthOf(0)
       })
 
       it('Should list local channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9001', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[0].port
+        const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should not list remote channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9002', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[1].port
+        const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(0)
         expect(res.body.data).to.have.lengthOf(0)
@@ -190,28 +196,30 @@ describe('Test follow constraints', function () {
       })
 
       it('Should list local account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9001', 0, 5)
+        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[0].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote account videos', async function () {
-        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9002', 0, 5)
+        const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[1].port, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list local channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9001', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[0].port
+        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
       })
 
       it('Should list remote channel videos', async function () {
-        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9002', 0, 5)
+        const videoChannelName = 'root_channel@localhost:' + servers[1].port
+        const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
 
         expect(res.body.total).to.equal(1)
         expect(res.body.data).to.have.lengthOf(1)
index 2a3a4d5c823516c1ddc9e5147bcad733c9d192a6..a82acdb34a4c8417e7e1e770337873f27259379a 100644 (file)
@@ -3,9 +3,9 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
-  acceptFollower, cleanupTests,
+  acceptFollower,
+  cleanupTests,
   flushAndRunMultipleServers,
-  killallServers,
   ServerInfo,
   setAccessTokensToServers,
   updateCustomSubConfig
@@ -14,8 +14,8 @@ import {
   follow,
   getFollowersListPaginationAndSort,
   getFollowingListPaginationAndSort,
-  removeFollower,
-  rejectFollower
+  rejectFollower,
+  removeFollower
 } from '../../../../shared/extra-utils/server/follows'
 import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
 import { ActorFollow } from '../../../../shared/models/actors'
@@ -29,8 +29,8 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
 
     const follow = res.body.data[0] as ActorFollow
     expect(follow.state).to.equal(state)
-    expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
-    expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
+    expect(follow.follower.url).to.equal('http://localhost:' + servers[0].port + '/accounts/peertube')
+    expect(follow.following.url).to.equal('http://localhost:' + servers[1].port + '/accounts/peertube')
   }
 
   {
@@ -39,8 +39,8 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
 
     const follow = res.body.data[0] as ActorFollow
     expect(follow.state).to.equal(state)
-    expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube')
-    expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube')
+    expect(follow.follower.url).to.equal('http://localhost:' + servers[0].port + '/accounts/peertube')
+    expect(follow.following.url).to.equal('http://localhost:' + servers[1].port + '/accounts/peertube')
   }
 }
 
@@ -151,7 +151,7 @@ describe('Test follows moderation', function () {
   })
 
   it('Should accept a follower', async function () {
-    await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:9001')
+    await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:' + servers[0].port)
     await waitJobs(servers)
 
     await checkServer1And2HasFollowers(servers)
@@ -178,7 +178,7 @@ describe('Test follows moderation', function () {
       expect(res.body.total).to.equal(1)
     }
 
-    await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:9001')
+    await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:' + servers[0].port)
     await waitJobs(servers)
 
     await checkServer1And2HasFollowers(servers)
index 397093cdbd275d974bbc6b1be65b796c7135bda9..e8d6f5138f2e2eb94ab4efa35b5a14f332fae20d 100644 (file)
@@ -8,7 +8,6 @@ import { cleanupTests, completeVideoCheck } from '../../../../shared/extra-utils
 import {
   flushAndRunMultipleServers,
   getVideosList,
-  killallServers,
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
@@ -89,8 +88,8 @@ describe('Test follows', function () {
     res = await getFollowingListPaginationAndSort(servers[0].url, 1, 1, 'createdAt')
     follows = follows.concat(res.body.data)
 
-    const server2Follow = follows.find(f => f.following.host === 'localhost:9002')
-    const server3Follow = follows.find(f => f.following.host === 'localhost:9003')
+    const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
+    const server3Follow = follows.find(f => f.following.host === 'localhost:' + servers[2].port)
 
     expect(server2Follow).to.not.be.undefined
     expect(server3Follow).to.not.be.undefined
@@ -100,12 +99,12 @@ describe('Test follows', function () {
 
   it('Should search followings on server 1', async function () {
     {
-      const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':9002')
+      const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':' + servers[1].port)
       const follows = res.body.data
 
       expect(res.body.total).to.equal(1)
       expect(follows.length).to.equal(1)
-      expect(follows[ 0 ].following.host).to.equal('localhost:9002')
+      expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[1].port)
     }
 
     {
@@ -136,18 +135,18 @@ describe('Test follows', function () {
       expect(res.body.total).to.equal(1)
       expect(follows).to.be.an('array')
       expect(follows.length).to.equal(1)
-      expect(follows[0].follower.host).to.equal('localhost:9001')
+      expect(follows[0].follower.host).to.equal('localhost:' + servers[0].port)
     }
   })
 
   it('Should search followers on server 2', async function () {
     {
-      const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', '9001')
+      const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', servers[0].port + '')
       const follows = res.body.data
 
       expect(res.body.total).to.equal(1)
       expect(follows.length).to.equal(1)
-      expect(follows[ 0 ].following.host).to.equal('localhost:9003')
+      expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[2].port)
     }
 
     {
@@ -169,16 +168,16 @@ describe('Test follows', function () {
   })
 
   it('Should have the correct follows counts', async function () {
-    await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
-    await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
-    await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
 
     // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
-    await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
-    await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
 
-    await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 1)
-    await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 1)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
   })
 
   it('Should unfollow server 3 on server 1', async function () {
@@ -197,7 +196,7 @@ describe('Test follows', function () {
     expect(follows).to.be.an('array')
     expect(follows.length).to.equal(1)
 
-    expect(follows[0].following.host).to.equal('localhost:9002')
+    expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
   })
 
   it('Should not have server 1 as follower on server 3 anymore', async function () {
@@ -210,14 +209,14 @@ describe('Test follows', function () {
   })
 
   it('Should have the correct follows counts 2', async function () {
-    await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 1)
-    await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 1)
+    await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
 
-    await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
-    await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
+    await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
 
-    await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 0)
-    await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 0, 0)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 0)
+    await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 0, 0)
   })
 
   it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
@@ -310,15 +309,15 @@ describe('Test follows', function () {
     })
 
     it('Should have the correct follows counts 3', async function () {
-      await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2)
-      await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0)
-      await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0)
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
+      await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
 
-      await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1)
-      await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0)
+      await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
+      await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
 
-      await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 2)
-      await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0)
+      await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 2)
+      await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
     })
 
     it('Should have propagated videos', async function () {
@@ -344,7 +343,7 @@ describe('Test follows', function () {
         support: 'my super support text',
         account: {
           name: 'root',
-          host: 'localhost:9003'
+          host: 'localhost:' + servers[2].port
         },
         isLocal,
         commentsEnabled: true,
@@ -384,7 +383,7 @@ describe('Test follows', function () {
       expect(comment.videoId).to.equal(video4.id)
       expect(comment.id).to.equal(comment.threadId)
       expect(comment.account.name).to.equal('root')
-      expect(comment.account.host).to.equal('localhost:9003')
+      expect(comment.account.host).to.equal('localhost:' + servers[2].port)
       expect(comment.totalReplies).to.equal(3)
       expect(dateIsValid(comment.createdAt as string)).to.be.true
       expect(dateIsValid(comment.updatedAt as string)).to.be.true
index 19010dbc17dc3bdbe5f034056cbb42c64380fdc8..068654d8c4f3906b73c4ce21bed57b6b013e8f3f 100644 (file)
@@ -60,48 +60,50 @@ describe('Test handle downs', function () {
     privacy: VideoPrivacy.UNLISTED
   })
 
-  const checkAttributes = {
-    name: 'my super name for server 1',
-    category: 5,
-    licence: 4,
-    language: 'ja',
-    nsfw: true,
-    description: 'my super description for server 1',
-    support: 'my super support text for server 1',
-    account: {
-      name: 'root',
-      host: 'localhost:9001'
-    },
-    isLocal: false,
-    duration: 10,
-    tags: [ 'tag1p1', 'tag2p1' ],
-    privacy: VideoPrivacy.PUBLIC,
-    commentsEnabled: true,
-    downloadEnabled: true,
-    channel: {
-      name: 'root_channel',
-      displayName: 'Main root channel',
-      description: '',
-      isLocal: false
-    },
-    fixture: 'video_short1.webm',
-    files: [
-      {
-        resolution: 720,
-        size: 572456
-      }
-    ]
-  }
-
-  const unlistedCheckAttributes = immutableAssign(checkAttributes, {
-    privacy: VideoPrivacy.UNLISTED
-  })
+  let checkAttributes: any
+  let unlistedCheckAttributes: any
 
   before(async function () {
     this.timeout(30000)
 
     servers = await flushAndRunMultipleServers(3)
 
+    checkAttributes = {
+      name: 'my super name for server 1',
+      category: 5,
+      licence: 4,
+      language: 'ja',
+      nsfw: true,
+      description: 'my super description for server 1',
+      support: 'my super support text for server 1',
+      account: {
+        name: 'root',
+        host: 'localhost:' + servers[0].port
+      },
+      isLocal: false,
+      duration: 10,
+      tags: [ 'tag1p1', 'tag2p1' ],
+      privacy: VideoPrivacy.PUBLIC,
+      commentsEnabled: true,
+      downloadEnabled: true,
+      channel: {
+        name: 'root_channel',
+        displayName: 'Main root channel',
+        description: '',
+        isLocal: false
+      },
+      fixture: 'video_short1.webm',
+      files: [
+        {
+          resolution: 720,
+          size: 572456
+        }
+      ]
+    }
+    unlistedCheckAttributes = immutableAssign(checkAttributes, {
+      privacy: VideoPrivacy.UNLISTED
+    })
+
     // Get the access tokens
     await setAccessTokensToServers(servers)
   })
@@ -172,7 +174,7 @@ describe('Test handle downs', function () {
     const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
     expect(res.body.data).to.be.an('array')
     expect(res.body.data).to.have.lengthOf(1)
-    expect(res.body.data[0].follower.host).to.equal('localhost:9003')
+    expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port)
   })
 
   it('Should not have pending/processing jobs anymore', async function () {
index 6346546261a3b12c25fe2b0cdc30737d99b13d1c..3ab2fe1202aca5bff739fa7058113c9c5c482a77 100644 (file)
@@ -26,7 +26,7 @@ describe('Test jobs', function () {
   })
 
   it('Should create some jobs', async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' })
     await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' })
index 3644fa0d307de2bef79348383e274bf9219023ee..68f4421993ed276852720bcfe79ca13233ffac8f 100644 (file)
@@ -45,7 +45,7 @@ describe('Test logs', function () {
   })
 
   it('Should get logs with an end date', async function () {
-    this.timeout(10000)
+    this.timeout(20000)
 
     await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
     await waitJobs([ server ])
diff --git a/server/tests/api/travis-1.sh b/server/tests/api/travis-1.sh
new file mode 100644 (file)
index 0000000..db4021b
--- /dev/null
@@ -0,0 +1,10 @@
+#!/usr/bin/env sh
+
+set -eu
+
+checkParamFiles=$(find server/tests/api/check-params -type f | grep -v index.ts | xargs echo)
+notificationsFiles=$(find server/tests/api/notifications -type f | grep -v index.ts | xargs echo)
+searchFiles=$(find server/tests/api/search -type f | grep -v index.ts | xargs echo)
+
+MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
+    $notificationsFiles $searchFiles $checkParamFiles
diff --git a/server/tests/api/travis-2.sh b/server/tests/api/travis-2.sh
new file mode 100644 (file)
index 0000000..ba7a061
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+
+set -eu
+
+serverFiles=$(find server/tests/api/server -type f | grep -v index.ts | xargs echo)
+usersFiles=$(find server/tests/api/users -type f | grep -v index.ts | xargs echo)
+
+MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
+    $serverFiles $usersFiles
diff --git a/server/tests/api/travis-3.sh b/server/tests/api/travis-3.sh
new file mode 100644 (file)
index 0000000..8245722
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+set -eu
+
+videosFiles=$(find server/tests/api/videos -type f | grep -v index.ts | xargs echo)
+
+MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
+    $videosFiles
diff --git a/server/tests/api/travis-4.sh b/server/tests/api/travis-4.sh
new file mode 100644 (file)
index 0000000..8759861
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+
+set -eu
+
+redundancyFiles=$(find server/tests/api/redundancy -type f | grep -v index.ts | xargs echo)
+activitypubFiles=$(find server/tests/api/activitypub -type f | grep -v index.ts | xargs echo)
+
+MOCHA_PARALLEL=true mocha-parallel-tests --max-parallel $1 --timeout 5000 --exit --require ts-node/register --bail \
+    $redundancyFiles $activitypubFiles
index fbc57e0ef690a74f4f1d8cd56ddd40f0d6224fdb..c25e85ada6039edfbfc08eb684c5649dab3b443d 100644 (file)
@@ -144,7 +144,7 @@ describe('Test blocklist', function () {
       })
 
       it('Should block a remote account', async function () {
-        await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002')
+        await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
       })
 
       it('Should hide its videos', async function () {
@@ -209,7 +209,7 @@ describe('Test blocklist', function () {
           expect(block.byAccount.name).to.equal('root')
           expect(block.blockedAccount.displayName).to.equal('user2')
           expect(block.blockedAccount.name).to.equal('user2')
-          expect(block.blockedAccount.host).to.equal('localhost:9002')
+          expect(block.blockedAccount.host).to.equal('localhost:' + servers[1].port)
         }
 
         {
@@ -223,12 +223,12 @@ describe('Test blocklist', function () {
           expect(block.byAccount.name).to.equal('root')
           expect(block.blockedAccount.displayName).to.equal('user1')
           expect(block.blockedAccount.name).to.equal('user1')
-          expect(block.blockedAccount.host).to.equal('localhost:9001')
+          expect(block.blockedAccount.host).to.equal('localhost:' + servers[0].port)
         }
       })
 
       it('Should unblock the remote account', async function () {
-        await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002')
+        await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
       })
 
       it('Should display its videos', async function () {
@@ -260,7 +260,7 @@ describe('Test blocklist', function () {
       })
 
       it('Should block a remote server', async function () {
-        await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002')
+        await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
       })
 
       it('Should hide its videos', async function () {
@@ -291,11 +291,11 @@ describe('Test blocklist', function () {
         const block = blocks[ 0 ]
         expect(block.byAccount.displayName).to.equal('root')
         expect(block.byAccount.name).to.equal('root')
-        expect(block.blockedServer.host).to.equal('localhost:9002')
+        expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
       })
 
       it('Should unblock the remote server', async function () {
-        await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002')
+        await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
       })
 
       it('Should display its videos', function () {
@@ -324,7 +324,7 @@ describe('Test blocklist', function () {
       })
 
       it('Should block a remote account', async function () {
-        await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002')
+        await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
       })
 
       it('Should hide its videos', async function () {
@@ -387,7 +387,7 @@ describe('Test blocklist', function () {
           expect(block.byAccount.name).to.equal('peertube')
           expect(block.blockedAccount.displayName).to.equal('user2')
           expect(block.blockedAccount.name).to.equal('user2')
-          expect(block.blockedAccount.host).to.equal('localhost:9002')
+          expect(block.blockedAccount.host).to.equal('localhost:' + servers[1].port)
         }
 
         {
@@ -401,12 +401,12 @@ describe('Test blocklist', function () {
           expect(block.byAccount.name).to.equal('peertube')
           expect(block.blockedAccount.displayName).to.equal('user1')
           expect(block.blockedAccount.name).to.equal('user1')
-          expect(block.blockedAccount.host).to.equal('localhost:9001')
+          expect(block.blockedAccount.host).to.equal('localhost:' + servers[0].port)
         }
       })
 
       it('Should unblock the remote account', async function () {
-        await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002')
+        await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
       })
 
       it('Should display its videos', async function () {
@@ -446,7 +446,7 @@ describe('Test blocklist', function () {
       })
 
       it('Should block a remote server', async function () {
-        await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002')
+        await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
       })
 
       it('Should hide its videos', async function () {
@@ -478,11 +478,11 @@ describe('Test blocklist', function () {
         const block = blocks[ 0 ]
         expect(block.byAccount.displayName).to.equal('peertube')
         expect(block.byAccount.name).to.equal('peertube')
-        expect(block.blockedServer.host).to.equal('localhost:9002')
+        expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
       })
 
       it('Should unblock the remote server', async function () {
-        await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002')
+        await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
       })
 
       it('Should list all videos', async function () {
index 48811e647d172606b4db54aee8fce30270dd8212..c8a89d6be4653735f4f7514f504ef00128a53886 100644 (file)
@@ -71,8 +71,8 @@ describe('Test users subscriptions', function () {
   it('User of server 1 should follow user of server 3 and root of server 1', async function () {
     this.timeout(60000)
 
-    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
-    await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001')
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
 
     await waitJobs(servers)
 
@@ -116,22 +116,22 @@ describe('Test users subscriptions', function () {
 
   it('Should get subscription', async function () {
     {
-      const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:9003')
+      const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:' + servers[2].port)
       const videoChannel: VideoChannel = res.body
 
       expect(videoChannel.name).to.equal('user3_channel')
-      expect(videoChannel.host).to.equal('localhost:9003')
+      expect(videoChannel.host).to.equal('localhost:' + servers[2].port)
       expect(videoChannel.displayName).to.equal('Main user3 channel')
       expect(videoChannel.followingCount).to.equal(0)
       expect(videoChannel.followersCount).to.equal(1)
     }
 
     {
-      const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:9001')
+      const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:' + servers[0].port)
       const videoChannel: VideoChannel = res.body
 
       expect(videoChannel.name).to.equal('root_channel')
-      expect(videoChannel.host).to.equal('localhost:9001')
+      expect(videoChannel.host).to.equal('localhost:' + servers[0].port)
       expect(videoChannel.displayName).to.equal('Main root channel')
       expect(videoChannel.followingCount).to.equal(0)
       expect(videoChannel.followersCount).to.equal(1)
@@ -140,19 +140,19 @@ describe('Test users subscriptions', function () {
 
   it('Should return the existing subscriptions', async function () {
     const uris = [
-      'user3_channel@localhost:9003',
-      'root2_channel@localhost:9001',
-      'root_channel@localhost:9001',
-      'user3_channel@localhost:9001'
+      'user3_channel@localhost:' + servers[2].port,
+      'root2_channel@localhost:' + servers[0].port,
+      'root_channel@localhost:' + servers[0].port,
+      'user3_channel@localhost:' + servers[0].port
     ]
 
     const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris)
     const body = res.body
 
-    expect(body['user3_channel@localhost:9003']).to.be.true
-    expect(body['root2_channel@localhost:9001']).to.be.false
-    expect(body['root_channel@localhost:9001']).to.be.true
-    expect(body['user3_channel@localhost:9001']).to.be.false
+    expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true
+    expect(body['root2_channel@localhost:' + servers[0].port]).to.be.false
+    expect(body['root_channel@localhost:' + servers[0].port]).to.be.true
+    expect(body['user3_channel@localhost:' + servers[0].port]).to.be.false
   })
 
   it('Should list subscription videos', async function () {
@@ -291,7 +291,7 @@ describe('Test users subscriptions', function () {
   it('Should remove user of server 3 subscription', async function () {
     this.timeout(30000)
 
-    await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
+    await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
 
     await waitJobs(servers)
   })
@@ -312,7 +312,7 @@ describe('Test users subscriptions', function () {
   it('Should remove the root subscription and not display the videos anymore', async function () {
     this.timeout(30000)
 
-    await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001')
+    await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
 
     await waitJobs(servers)
 
@@ -340,7 +340,7 @@ describe('Test users subscriptions', function () {
   it('Should follow user of server 3 again', async function () {
     this.timeout(60000)
 
-    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003')
+    await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
 
     await waitJobs(servers)
 
index 9a971adb39350d557938d55314c9c51c8428d66f..988fdad3f9feb620c2065051681cabe6c4e4e2c8 100644 (file)
@@ -151,13 +151,13 @@ describe('Test users with multiple servers', function () {
     for (const server of servers) {
       const resAccounts = await getAccountsList(server.url, '-createdAt')
 
-      const rootServer1List = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:9001') as Account
+      const rootServer1List = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:' + servers[0].port) as Account
       expect(rootServer1List).not.to.be.undefined
 
       const resAccount = await getAccount(server.url, rootServer1List.name + '@' + rootServer1List.host)
       const rootServer1Get = resAccount.body as Account
       expect(rootServer1Get.name).to.equal('root')
-      expect(rootServer1Get.host).to.equal('localhost:9001')
+      expect(rootServer1Get.host).to.equal('localhost:' + servers[0].port)
       expect(rootServer1Get.displayName).to.equal('my super display name')
       expect(rootServer1Get.description).to.equal('my super description updated')
 
@@ -188,12 +188,12 @@ describe('Test users with multiple servers', function () {
     for (const server of servers) {
       const resAccounts = await getAccountsList(server.url, '-createdAt')
 
-      const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:9001') as Account
+      const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account
       expect(accountDeleted).not.to.be.undefined
 
       const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
       const videoChannelDeleted = resVideoChannels.body.data.find(a => {
-        return a.displayName === 'Main user1 channel' && a.host === 'localhost:9001'
+        return a.displayName === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port
       }) as VideoChannel
       expect(videoChannelDeleted).not.to.be.undefined
     }
@@ -205,12 +205,12 @@ describe('Test users with multiple servers', function () {
     for (const server of servers) {
       const resAccounts = await getAccountsList(server.url, '-createdAt')
 
-      const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:9001') as Account
+      const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account
       expect(accountDeleted).to.be.undefined
 
       const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
       const videoChannelDeleted = resVideoChannels.body.data.find(a => {
-        return a.name === 'Main user1 channel' && a.host === 'localhost:9001'
+        return a.name === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port
       }) as VideoChannel
       expect(videoChannelDeleted).to.be.undefined
     }
@@ -218,14 +218,14 @@ describe('Test users with multiple servers', function () {
 
   it('Should not have actor files', async () => {
     for (const server of servers) {
-      await checkActorFilesWereRemoved(userAccountUUID, server.serverNumber)
-      await checkActorFilesWereRemoved(userVideoChannelUUID, server.serverNumber)
+      await checkActorFilesWereRemoved(userAccountUUID, server.internalServerNumber)
+      await checkActorFilesWereRemoved(userVideoChannelUUID, server.internalServerNumber)
     }
   })
 
   it('Should not have video files', async () => {
     for (const server of servers) {
-      await checkVideoFilesWereRemoved(videoUUID, server.serverNumber)
+      await checkVideoFilesWereRemoved(videoUUID, server.internalServerNumber)
     }
   })
 
index 514acf2e7daf2de90b9262e1caab68c04b39f04d..3b37a26cfe2721a85aba227e1e34f4636da89d6b 100644 (file)
@@ -30,11 +30,12 @@ describe('Test users account verification', function () {
   before(async function () {
     this.timeout(30000)
 
-    await MockSmtpServer.Instance.collectEmails(emails)
+    const port = await MockSmtpServer.Instance.collectEmails(emails)
 
     const overrideConfig = {
       smtp: {
-        hostname: 'localhost'
+        hostname: 'localhost',
+        port
       }
     }
     server = await flushAndRunServer(1, overrideConfig)
index c8e32f3f55b0b368f782e1701edcd7ffef3c622f..c1a24b8387598d9276a3d74649eb6c73d9165a52 100644 (file)
@@ -316,7 +316,7 @@ describe('Test users', function () {
 
       const rootUser = users[ 1 ]
       expect(rootUser.username).to.equal('root')
-      expect(rootUser.email).to.equal('admin1@example.com')
+      expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
       expect(user.nsfwPolicy).to.equal('display')
 
       userId = user.id
@@ -334,7 +334,7 @@ describe('Test users', function () {
 
       const user = users[ 0 ]
       expect(user.username).to.equal('root')
-      expect(user.email).to.equal('admin1@example.com')
+      expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
       expect(user.roleLabel).to.equal('Administrator')
       expect(user.nsfwPolicy).to.equal('display')
     })
@@ -379,7 +379,7 @@ describe('Test users', function () {
       expect(users.length).to.equal(2)
 
       expect(users[ 0 ].username).to.equal('root')
-      expect(users[ 0 ].email).to.equal('admin1@example.com')
+      expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com')
       expect(users[ 0 ].nsfwPolicy).to.equal('display')
 
       expect(users[ 1 ].username).to.equal('user_1')
index 68c1e9a8de3e531345f68d3f69c95cfb9548c0fa..e9625e5f79a04589e14641a014e85c0b5cb3f3aa 100644 (file)
@@ -9,18 +9,17 @@ import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/
 import {
   addVideoChannel,
   checkTmpIsEmpty,
-  checkVideoFilesWereRemoved, cleanupTests,
+  checkVideoFilesWereRemoved,
+  cleanupTests,
   completeVideoCheck,
   createUser,
   dateIsValid,
   doubleFollow,
   flushAndRunMultipleServers,
-  flushTests,
   getLocalVideos,
   getVideo,
   getVideoChannelsList,
   getVideosList,
-  killallServers,
   rateVideo,
   removeVideo,
   ServerInfo,
@@ -110,7 +109,7 @@ describe('Test multiple servers', function () {
       // All servers should have this video
       let publishedAt: string = null
       for (const server of servers) {
-        const isLocal = server.url === 'http://localhost:9001'
+        const isLocal = server.port === servers[0].port
         const checkAttributes = {
           name: 'my super name for server 1',
           category: 5,
@@ -122,7 +121,7 @@ describe('Test multiple servers', function () {
           originallyPublishedAt: '2019-02-10T13:38:14.449Z',
           account: {
             name: 'root',
-            host: 'localhost:9001'
+            host: 'localhost:' + servers[0].port
           },
           isLocal,
           publishedAt,
@@ -187,7 +186,7 @@ describe('Test multiple servers', function () {
 
       // All servers should have this video
       for (const server of servers) {
-        const isLocal = server.url === 'http://localhost:9002'
+        const isLocal = server.url === 'http://localhost:' + servers[1].port
         const checkAttributes = {
           name: 'my super name for server 2',
           category: 4,
@@ -198,7 +197,7 @@ describe('Test multiple servers', function () {
           support: 'my super support text for server 2',
           account: {
             name: 'user1',
-            host: 'localhost:9002'
+            host: 'localhost:' + servers[1].port
           },
           isLocal,
           commentsEnabled: true,
@@ -216,7 +215,7 @@ describe('Test multiple servers', function () {
           files: [
             {
               resolution: 240,
-              size: 187000
+              size: 189000
             },
             {
               resolution: 360,
@@ -224,7 +223,7 @@ describe('Test multiple servers', function () {
             },
             {
               resolution: 480,
-              size: 383000
+              size: 384000
             },
             {
               resolution: 720,
@@ -278,7 +277,7 @@ describe('Test multiple servers', function () {
 
       // All servers should have this video
       for (const server of servers) {
-        const isLocal = server.url === 'http://localhost:9003'
+        const isLocal = server.url === 'http://localhost:' + servers[2].port
         const res = await getVideosList(server.url)
 
         const videos = res.body.data
@@ -306,7 +305,7 @@ describe('Test multiple servers', function () {
           support: 'my super support text for server 3',
           account: {
             name: 'root',
-            host: 'localhost:9003'
+            host: 'localhost:' + servers[2].port
           },
           isLocal,
           duration: 5,
@@ -340,7 +339,7 @@ describe('Test multiple servers', function () {
           support: 'my super support text for server 3-2',
           account: {
             name: 'root',
-            host: 'localhost:9003'
+            host: 'localhost:' + servers[2].port
           },
           commentsEnabled: true,
           downloadEnabled: true,
@@ -646,7 +645,7 @@ describe('Test multiple servers', function () {
         const videoUpdated = videos.find(video => video.name === 'my super video updated')
         expect(!!videoUpdated).to.be.true
 
-        const isLocal = server.url === 'http://localhost:9003'
+        const isLocal = server.url === 'http://localhost:' + servers[2].port
         const checkAttributes = {
           name: 'my super video updated',
           category: 10,
@@ -658,7 +657,7 @@ describe('Test multiple servers', function () {
           originallyPublishedAt: '2019-02-11T13:38:14.449Z',
           account: {
             name: 'root',
-            host: 'localhost:9003'
+            host: 'localhost:' + servers[2].port
           },
           isLocal,
           duration: 5,
@@ -813,7 +812,7 @@ describe('Test multiple servers', function () {
           expect(comment).to.not.be.undefined
           expect(comment.inReplyToCommentId).to.be.null
           expect(comment.account.name).to.equal('root')
-          expect(comment.account.host).to.equal('localhost:9001')
+          expect(comment.account.host).to.equal('localhost:' + servers[0].port)
           expect(comment.totalReplies).to.equal(3)
           expect(dateIsValid(comment.createdAt as string)).to.be.true
           expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -824,7 +823,7 @@ describe('Test multiple servers', function () {
           expect(comment).to.not.be.undefined
           expect(comment.inReplyToCommentId).to.be.null
           expect(comment.account.name).to.equal('root')
-          expect(comment.account.host).to.equal('localhost:9003')
+          expect(comment.account.host).to.equal('localhost:' + servers[2].port)
           expect(comment.totalReplies).to.equal(0)
           expect(dateIsValid(comment.createdAt as string)).to.be.true
           expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -842,25 +841,25 @@ describe('Test multiple servers', function () {
         const tree: VideoCommentThreadTree = res2.body
         expect(tree.comment.text).equal('my super first comment')
         expect(tree.comment.account.name).equal('root')
-        expect(tree.comment.account.host).equal('localhost:9001')
+        expect(tree.comment.account.host).equal('localhost:' + servers[0].port)
         expect(tree.children).to.have.lengthOf(2)
 
         const firstChild = tree.children[0]
         expect(firstChild.comment.text).to.equal('my super answer to thread 1')
         expect(firstChild.comment.account.name).equal('root')
-        expect(firstChild.comment.account.host).equal('localhost:9002')
+        expect(firstChild.comment.account.host).equal('localhost:' + servers[1].port)
         expect(firstChild.children).to.have.lengthOf(1)
 
         childOfFirstChild = firstChild.children[0]
         expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
         expect(childOfFirstChild.comment.account.name).equal('root')
-        expect(childOfFirstChild.comment.account.host).equal('localhost:9003')
+        expect(childOfFirstChild.comment.account.host).equal('localhost:' + servers[2].port)
         expect(childOfFirstChild.children).to.have.lengthOf(0)
 
         const secondChild = tree.children[1]
         expect(secondChild.comment.text).to.equal('my second answer to thread 1')
         expect(secondChild.comment.account.name).equal('root')
-        expect(secondChild.comment.account.host).equal('localhost:9003')
+        expect(secondChild.comment.account.host).equal('localhost:' + servers[2].port)
         expect(secondChild.children).to.have.lengthOf(0)
       }
     })
@@ -915,7 +914,7 @@ describe('Test multiple servers', function () {
           expect(comment).to.not.be.undefined
           expect(comment.inReplyToCommentId).to.be.null
           expect(comment.account.name).to.equal('root')
-          expect(comment.account.host).to.equal('localhost:9003')
+          expect(comment.account.host).to.equal('localhost:' + servers[2].port)
           expect(comment.totalReplies).to.equal(0)
           expect(dateIsValid(comment.createdAt as string)).to.be.true
           expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -971,7 +970,7 @@ describe('Test multiple servers', function () {
         const res = await getVideosList(server.url)
         const video = res.body.data.find(v => v.name === 'minimum parameters')
 
-        const isLocal = server.url === 'http://localhost:9002'
+        const isLocal = server.url === 'http://localhost:' + servers[1].port
         const checkAttributes = {
           name: 'minimum parameters',
           category: null,
@@ -982,7 +981,7 @@ describe('Test multiple servers', function () {
           support: null,
           account: {
             name: 'root',
-            host: 'localhost:9002'
+            host: 'localhost:' + servers[1].port
           },
           isLocal,
           duration: 5,
index e9ad947b2dc3bd34270c33937bcb788893908246..17172331f917b41639ef298c3bac60bb424554a2 100644 (file)
@@ -27,13 +27,13 @@ describe('Test services', function () {
   })
 
   it('Should have a valid oEmbed response', async function () {
-    const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
+    const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
 
     const res = await getOEmbed(server.url, oembedUrl)
     const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
-                         `src="http://localhost:9001/videos/embed/${server.video.uuid}" ` +
+                         `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
                          'frameborder="0" allowfullscreen></iframe>'
-    const expectedThumbnailUrl = 'http://localhost:9001/static/previews/' + server.video.uuid + '.jpg'
+    const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg'
 
     expect(res.body.html).to.equal(expectedHtml)
     expect(res.body.title).to.equal(server.video.name)
@@ -41,19 +41,19 @@ describe('Test services', function () {
     expect(res.body.width).to.equal(560)
     expect(res.body.height).to.equal(315)
     expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
-    expect(res.body.thumbnail_width).to.equal(560)
-    expect(res.body.thumbnail_height).to.equal(315)
+    expect(res.body.thumbnail_width).to.equal(850)
+    expect(res.body.thumbnail_height).to.equal(480)
   })
 
   it('Should have a valid oEmbed response with small max height query', async function () {
-    const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid
+    const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
     const format = 'json'
     const maxHeight = 50
     const maxWidth = 50
 
     const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
     const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
-                         `src="http://localhost:9001/videos/embed/${server.video.uuid}" ` +
+                         `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
                          'frameborder="0" allowfullscreen></iframe>'
 
     expect(res.body.html).to.equal(expectedHtml)
index 1f366b642f07a3e1d9858f0d3ecd08ea6d54ba5b..d8f394ac749b6cc5c2ca646b74fcf6ddd750ecbe 100644 (file)
@@ -37,7 +37,7 @@ describe('Test a single server', function () {
   let videoUUID = ''
   let videosListBase: any[] = null
 
-  const getCheckAttributes = {
+  const getCheckAttributes = () => ({
     name: 'my super name',
     category: 2,
     licence: 6,
@@ -47,7 +47,7 @@ describe('Test a single server', function () {
     support: 'my super support text',
     account: {
       name: 'root',
-      host: 'localhost:9001'
+      host: 'localhost:' + server.port
     },
     isLocal: true,
     duration: 5,
@@ -68,9 +68,9 @@ describe('Test a single server', function () {
         size: 218910
       }
     ]
-  }
+  })
 
-  const updateCheckAttributes = {
+  const updateCheckAttributes = () => ({
     name: 'my super video updated',
     category: 4,
     licence: 2,
@@ -80,7 +80,7 @@ describe('Test a single server', function () {
     support: 'my super support text updated',
     account: {
       name: 'root',
-      host: 'localhost:9001'
+      host: 'localhost:' + server.port
     },
     isLocal: true,
     tags: [ 'tagup1', 'tagup2' ],
@@ -101,7 +101,7 @@ describe('Test a single server', function () {
         size: 292677
       }
     ]
-  }
+  })
 
   before(async function () {
     this.timeout(30000)
@@ -182,7 +182,7 @@ describe('Test a single server', function () {
     expect(res.body.data.length).to.equal(1)
 
     const video = res.body.data[0]
-    await completeVideoCheck(server.url, video, getCheckAttributes)
+    await completeVideoCheck(server.url, video, getCheckAttributes())
   })
 
   it('Should get the video by UUID', async function () {
@@ -191,7 +191,7 @@ describe('Test a single server', function () {
     const res = await getVideo(server.url, videoUUID)
 
     const video = res.body
-    await completeVideoCheck(server.url, video, getCheckAttributes)
+    await completeVideoCheck(server.url, video, getCheckAttributes())
   })
 
   it('Should have the views updated', async function () {
@@ -376,7 +376,7 @@ describe('Test a single server', function () {
     const res = await getVideo(server.url, videoId)
     const video = res.body
 
-    await completeVideoCheck(server.url, video, updateCheckAttributes)
+    await completeVideoCheck(server.url, video, updateCheckAttributes())
   })
 
   it('Should update only the tags of a video', async function () {
@@ -388,7 +388,7 @@ describe('Test a single server', function () {
     const res = await getVideo(server.url, videoId)
     const video = res.body
 
-    await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes, attributes))
+    await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes(), attributes))
   })
 
   it('Should update only the description of a video', async function () {
@@ -400,7 +400,8 @@ describe('Test a single server', function () {
     const res = await getVideo(server.url, videoId)
     const video = res.body
 
-    await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes, attributes))
+    const expectedAttributes = Object.assign(updateCheckAttributes(), { tags: [ 'supertag', 'tag1', 'tag2' ] }, attributes)
+    await completeVideoCheck(server.url, video, expectedAttributes)
   })
 
   it('Should like a video', async function () {
index 7318497d59f352c47d141044b4ee35689c16656c..a2f3ee16177b414285db25b3c4172d0b4a7ebae2 100644 (file)
@@ -9,7 +9,6 @@ import {
   flushAndRunMultipleServers,
   getVideoAbusesList,
   getVideosList,
-  killallServers,
   reportVideoAbuse,
   ServerInfo,
   setAccessTokensToServers,
@@ -90,7 +89,7 @@ describe('Test video abuses', function () {
     const abuse: VideoAbuse = res1.body.data[0]
     expect(abuse.reason).to.equal('my super bad reason')
     expect(abuse.reporterAccount.name).to.equal('root')
-    expect(abuse.reporterAccount.host).to.equal('localhost:9001')
+    expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port)
     expect(abuse.video.id).to.equal(servers[0].video.id)
 
     const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
@@ -118,7 +117,7 @@ describe('Test video abuses', function () {
     const abuse1: VideoAbuse = res1.body.data[0]
     expect(abuse1.reason).to.equal('my super bad reason')
     expect(abuse1.reporterAccount.name).to.equal('root')
-    expect(abuse1.reporterAccount.host).to.equal('localhost:9001')
+    expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port)
     expect(abuse1.video.id).to.equal(servers[0].video.id)
     expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING)
     expect(abuse1.state.label).to.equal('Pending')
@@ -127,7 +126,7 @@ describe('Test video abuses', function () {
     const abuse2: VideoAbuse = res1.body.data[1]
     expect(abuse2.reason).to.equal('my super bad reason 2')
     expect(abuse2.reporterAccount.name).to.equal('root')
-    expect(abuse2.reporterAccount.host).to.equal('localhost:9001')
+    expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
     expect(abuse2.video.id).to.equal(servers[1].video.id)
     expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING)
     expect(abuse2.state.label).to.equal('Pending')
@@ -141,7 +140,7 @@ describe('Test video abuses', function () {
     abuseServer2 = res2.body.data[0]
     expect(abuseServer2.reason).to.equal('my super bad reason 2')
     expect(abuseServer2.reporterAccount.name).to.equal('root')
-    expect(abuseServer2.reporterAccount.host).to.equal('localhost:9001')
+    expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
     expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING)
     expect(abuseServer2.state.label).to.equal('Pending')
     expect(abuseServer2.moderationComment).to.be.null
index 1c0327d4089043edd680b26777c3673cc0e4e836..3a3add71b099cd744ca44e3d56fc309f7e71edbd 100644 (file)
@@ -4,7 +4,8 @@ import * as chai from 'chai'
 import 'mocha'
 import {
   acceptChangeOwnership,
-  changeVideoOwnership, cleanupTests,
+  changeVideoOwnership,
+  cleanupTests,
   createUser,
   doubleFollow,
   flushAndRunMultipleServers,
@@ -13,7 +14,6 @@ import {
   getVideo,
   getVideoChangeOwnershipList,
   getVideosList,
-  killallServers,
   refuseChangeOwnership,
   ServerInfo,
   setAccessTokensToServers,
@@ -203,8 +203,8 @@ describe('Test video change ownership - nominal', function () {
     }
   })
 
-  after(function () {
-    killallServers(servers)
+  after(async function () {
+    await cleanupTests(servers)
   })
 })
 
index 345e96f438b90cefab4c1bc6202e88e445a29091..41fe3be5cf8dbf1522244e0f9f9aa8256fdbe009 100644 (file)
@@ -41,7 +41,7 @@ describe('Test video channels', function () {
   let videoUUID: string
 
   before(async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     servers = await flushAndRunMultipleServers(2)
 
@@ -213,7 +213,7 @@ describe('Test video channels', function () {
     this.timeout(10000)
 
     for (const server of servers) {
-      const channelURI = 'second_video_channel@localhost:9001'
+      const channelURI = 'second_video_channel@localhost:' + servers[0].port
       const res1 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
       expect(res1.body.total).to.equal(1)
       expect(res1.body.data).to.be.an('array')
@@ -234,11 +234,11 @@ describe('Test video channels', function () {
     this.timeout(10000)
 
     for (const server of servers) {
-      const secondChannelURI = 'second_video_channel@localhost:9001'
+      const secondChannelURI = 'second_video_channel@localhost:' + servers[0].port
       const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondChannelURI, 0, 5)
       expect(res1.body.total).to.equal(0)
 
-      const channelURI = 'root_channel@localhost:9001'
+      const channelURI = 'root_channel@localhost:' + servers[0].port
       const res2 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
       expect(res2.body.total).to.equal(1)
 
index 22fd8c0585c6a8ea6685ec0f5b6ae5b7e38cf641..82182cc7cb90da36c82f1bc4d29f6fd20ed4a093 100644 (file)
@@ -66,8 +66,8 @@ describe('Test video comments', function () {
     expect(comment.videoId).to.equal(videoId)
     expect(comment.id).to.equal(comment.threadId)
     expect(comment.account.name).to.equal('root')
-    expect(comment.account.host).to.equal('localhost:9001')
-    expect(comment.account.url).to.equal('http://localhost:9001/accounts/root')
+    expect(comment.account.host).to.equal('localhost:' + server.port)
+    expect(comment.account.url).to.equal('http://localhost:' + server.port + '/accounts/root')
     expect(comment.totalReplies).to.equal(0)
     expect(dateIsValid(comment.createdAt as string)).to.be.true
     expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -86,7 +86,7 @@ describe('Test video comments', function () {
     expect(comment.videoId).to.equal(videoId)
     expect(comment.id).to.equal(comment.threadId)
     expect(comment.account.name).to.equal('root')
-    expect(comment.account.host).to.equal('localhost:9001')
+    expect(comment.account.host).to.equal('localhost:' + server.port)
 
     await testImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png')
 
index 22031c18bf0f61e84a457d2e0ee2c442298c431c..39178bb1a739b5001ef184708065067ad5c35d95 100644 (file)
@@ -5,13 +5,12 @@ import 'mocha'
 import {
   checkDirectoryIsEmpty,
   checkSegmentHash,
-  checkTmpIsEmpty, cleanupTests,
+  checkTmpIsEmpty,
+  cleanupTests,
   doubleFollow,
   flushAndRunMultipleServers,
-  flushTests,
   getPlaylist,
   getVideo,
-  killallServers,
   removeVideo,
   ServerInfo,
   setAccessTokensToServers,
@@ -22,12 +21,11 @@ import {
 import { VideoDetails } from '../../../../shared/models/videos'
 import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
 import { join } from 'path'
+import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
 
 const expect = chai.expect
 
-async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
-  const resolutions = [ 240, 360, 480, 720 ]
-
+async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, resolutions = [ 240, 360, 480, 720 ]) {
   for (const server of servers) {
     const res = await getVideo(server.url, videoUUID)
     const videoDetails: VideoDetails = res.body
@@ -42,16 +40,15 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
 
       const masterPlaylist = res2.text
 
-      expect(masterPlaylist).to.contain('#EXT-X-STREAM-INF:BANDWIDTH=55472,RESOLUTION=640x360,FRAME-RATE=25')
-
       for (const resolution of resolutions) {
+        expect(masterPlaylist).to.match(new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+'))
         expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
       }
     }
 
     {
       for (const resolution of resolutions) {
-        const res2 = await getPlaylist(`http://localhost:9001/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`)
+        const res2 = await getPlaylist(`http://localhost:${servers[0].port}/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`)
 
         const subPlaylist = res2.text
         expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`)
@@ -59,7 +56,7 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
     }
 
     {
-      const baseUrl = 'http://localhost:9001/static/streaming-playlists/hls'
+      const baseUrl = 'http://localhost:' + servers[0].port + '/static/streaming-playlists/hls'
 
       for (const resolution of resolutions) {
         await checkSegmentHash(baseUrl, baseUrl, videoUUID, resolution, hlsPlaylist)
@@ -71,11 +68,21 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
 describe('Test HLS videos', function () {
   let servers: ServerInfo[] = []
   let videoUUID = ''
+  let videoAudioUUID = ''
 
   before(async function () {
     this.timeout(120000)
 
-    servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: true, hls: { enabled: true } } })
+    const configOverride = {
+      transcoding: {
+        enabled: true,
+        allow_audio_files: true,
+        hls: {
+          enabled: true
+        }
+      }
+    }
+    servers = await flushAndRunMultipleServers(2, configOverride)
 
     // Get the access tokens
     await setAccessTokensToServers(servers)
@@ -87,17 +94,28 @@ describe('Test HLS videos', function () {
   it('Should upload a video and transcode it to HLS', async function () {
     this.timeout(120000)
 
-    {
-      const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' })
-      videoUUID = res.body.video.uuid
-    }
+    const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' })
+    videoUUID = res.body.video.uuid
 
     await waitJobs(servers)
 
     await checkHlsPlaylist(servers, videoUUID)
   })
 
+  it('Should upload an audio file and transcode it to HLS', async function () {
+    this.timeout(120000)
+
+    const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video audio', fixture: 'sample.ogg' })
+    videoAudioUUID = res.body.video.uuid
+
+    await waitJobs(servers)
+
+    await checkHlsPlaylist(servers, videoAudioUUID, [ DEFAULT_AUDIO_RESOLUTION ])
+  })
+
   it('Should update the video', async function () {
+    this.timeout(10000)
+
     await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' })
 
     await waitJobs(servers)
@@ -105,13 +123,17 @@ describe('Test HLS videos', function () {
     await checkHlsPlaylist(servers, videoUUID)
   })
 
-  it('Should delete the video', async function () {
+  it('Should delete videos', async function () {
+    this.timeout(10000)
+
     await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
+    await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID)
 
     await waitJobs(servers)
 
     for (const server of servers) {
       await getVideo(server.url, videoUUID, 404)
+      await getVideo(server.url, videoAudioUUID, 404)
     }
   })
 
index e4d817ff8b3784f03fac6b70752b78c757a7e7ef..83a2f3d4d81550a3e926e6af7896477abe41a62b 100644 (file)
@@ -358,7 +358,7 @@ describe('Test video playlists', function () {
 
     for (const server of servers) {
       const results = [
-        await getAccountPlaylistsList(server.url, 'root@localhost:9002', 0, 5, '-createdAt'),
+        await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
         await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
       ]
 
@@ -757,7 +757,7 @@ describe('Test video playlists', function () {
     this.timeout(30000)
 
     for (const server of servers) {
-      await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.serverNumber)
+      await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
     }
   })
 
index 3cd43e99bdf4dc6bd23ab21ba30fb9478df87068..90ade16525aa35ae059ad90f7325e255c26420a9 100644 (file)
@@ -4,24 +4,25 @@ import * as chai from 'chai'
 import 'mocha'
 import { omit } from 'lodash'
 import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
-import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
+import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
 import {
-  buildAbsoluteFixturePath, cleanupTests,
+  buildAbsoluteFixturePath,
+  cleanupTests,
   doubleFollow,
   flushAndRunMultipleServers,
   generateHighBitrateVideo,
   getMyVideos,
   getVideo,
   getVideosList,
-  killallServers,
+  makeGetRequest,
   root,
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo,
+  waitJobs,
   webtorrentAdd
 } from '../../../../shared/extra-utils'
-import { extname, join } from 'path'
-import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
+import { join } from 'path'
 import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
 
 const expect = chai.expect
@@ -121,7 +122,7 @@ describe('Test video transcoding', function () {
 
       expect(videoDetails.files).to.have.lengthOf(4)
 
-      const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
+      const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
       const probe = await audio.get(path)
 
       if (probe.audioStream) {
@@ -152,7 +153,7 @@ describe('Test video transcoding', function () {
       const videoDetails: VideoDetails = res2.body
 
       expect(videoDetails.files).to.have.lengthOf(4)
-      const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
+      const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
       const probe = await audio.get(path)
       expect(probe).to.not.have.property('audioStream')
     }
@@ -179,7 +180,7 @@ describe('Test video transcoding', function () {
       expect(videoDetails.files).to.have.lengthOf(4)
       const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture)
       const fixtureVideoProbe = await audio.get(fixturePath)
-      const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
+      const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
       const videoProbe = await audio.get(path)
       if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
         const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
@@ -216,13 +217,13 @@ describe('Test video transcoding', function () {
       expect(videoDetails.files[ 3 ].fps).to.be.below(31)
 
       for (const resolution of [ '240', '360', '480' ]) {
-        const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4')
+        const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
         const fps = await getVideoFileFPS(path)
 
         expect(fps).to.be.below(31)
       }
 
-      const path = join(root(), 'test2', 'videos', video.uuid + '-720.mp4')
+      const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4')
       const fps = await getVideoFileFPS(path)
 
       expect(fps).to.be.above(58).and.below(62)
@@ -310,7 +311,7 @@ describe('Test video transcoding', function () {
       const video = res.body.data.find(v => v.name === videoAttributes.name)
 
       for (const resolution of ['240', '360', '480', '720', '1080']) {
-        const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4')
+        const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
         const bitrate = await getVideoFileBitrate(path)
         const fps = await getVideoFileFPS(path)
         const resolution2 = await getVideoFileResolution(path)
@@ -324,6 +325,15 @@ describe('Test video transcoding', function () {
   it('Should accept and transcode additional extensions', async function () {
     this.timeout(300000)
 
+    let tempFixturePath: string
+
+    {
+      tempFixturePath = await generateHighBitrateVideo()
+
+      const bitrate = await getVideoFileBitrate(tempFixturePath)
+      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
+    }
+
     for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
       const videoAttributes = {
         name: fixture,
@@ -349,6 +359,63 @@ describe('Test video transcoding', function () {
     }
   })
 
+  it('Should correctly detect if quick transcode is possible', async function () {
+    this.timeout(10000)
+
+    expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
+    expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
+  })
+
+  it('Should merge an audio file with the preview file', async function () {
+    this.timeout(60000)
+
+    const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
+    await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
+
+    await waitJobs(servers)
+
+    for (const server of servers) {
+      const res = await getVideosList(server.url)
+
+      const video = res.body.data.find(v => v.name === 'audio_with_preview')
+      const res2 = await getVideo(server.url, video.id)
+      const videoDetails: VideoDetails = res2.body
+
+      expect(videoDetails.files).to.have.lengthOf(1)
+
+      await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
+      await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
+
+      const magnetUri = videoDetails.files[ 0 ].magnetUri
+      expect(magnetUri).to.contain('.mp4')
+    }
+  })
+
+  it('Should upload an audio file and choose a default background image', async function () {
+    this.timeout(60000)
+
+    const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
+    await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
+
+    await waitJobs(servers)
+
+    for (const server of servers) {
+      const res = await getVideosList(server.url)
+
+      const video = res.body.data.find(v => v.name === 'audio_without_preview')
+      const res2 = await getVideo(server.url, video.id)
+      const videoDetails = res2.body
+
+      expect(videoDetails.files).to.have.lengthOf(1)
+
+      await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
+      await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
+
+      const magnetUri = videoDetails.files[ 0 ].magnetUri
+      expect(magnetUri).to.contain('.mp4')
+    }
+  })
+
   after(async function () {
     await cleanupTests(servers)
   })
index c21d46d569f58d1701b4868492d36f2267c25704..fbddd40f4906f1e73c52084075c5dc834fe4a466 100644 (file)
@@ -10,7 +10,7 @@ import {
   flushAndRunServer,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests
+  uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests, closeAllSequelize
 } from '../../../../shared/extra-utils'
 import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews'
 import { VideosOverview } from '../../../../shared/models/overviews'
@@ -58,14 +58,14 @@ describe('Test video views cleaner', function () {
 
     {
       for (const server of servers) {
-        const total = await countVideoViewsOf(server.serverNumber, videoIdServer1)
+        const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1)
         expect(total).to.equal(2)
       }
     }
 
     {
       for (const server of servers) {
-        const total = await countVideoViewsOf(server.serverNumber, videoIdServer2)
+        const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer2)
         expect(total).to.equal(2)
       }
     }
@@ -74,8 +74,6 @@ describe('Test video views cleaner', function () {
   it('Should clean old video views', async function () {
     this.timeout(50000)
 
-    this.timeout(50000)
-
     killallServers([ servers[0] ])
 
     await reRunServer(servers[0], { views: { videos: { remote: { max_age: '5 seconds' } } } })
@@ -86,21 +84,23 @@ describe('Test video views cleaner', function () {
 
     {
       for (const server of servers) {
-        const total = await countVideoViewsOf(server.serverNumber, videoIdServer1)
+        const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1)
         expect(total).to.equal(2)
       }
     }
 
     {
-      const totalServer1 = await countVideoViewsOf(servers[0].serverNumber, videoIdServer2)
+      const totalServer1 = await countVideoViewsOf(servers[0].internalServerNumber, videoIdServer2)
       expect(totalServer1).to.equal(0)
 
-      const totalServer2 = await countVideoViewsOf(servers[1].serverNumber, videoIdServer2)
+      const totalServer2 = await countVideoViewsOf(servers[1].internalServerNumber, videoIdServer2)
       expect(totalServer2).to.equal(2)
     }
   })
 
   after(async function () {
+    await closeAllSequelize(servers)
+
     await cleanupTests(servers)
   })
 })
index 5e12c008912e779067d4f215dc7b6011e5e9d52a..3822fca42f05f5d80daf2377f2fdf58160df0f99 100644 (file)
@@ -8,14 +8,16 @@ import {
   doubleFollow,
   execCLI,
   flushAndRunMultipleServers,
-  flushTests, generateHighBitrateVideo,
+  generateHighBitrateVideo,
   getEnvCli,
   getVideo,
   getVideosList,
-  killallServers, root,
+  root,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo, viewVideo, wait
+  uploadVideo,
+  viewVideo,
+  wait
 } from '../../../shared/extra-utils'
 import { waitJobs } from '../../../shared/extra-utils/server/jobs'
 import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils'
@@ -102,7 +104,7 @@ describe('Test optimize old videos', function () {
 
         expect(file.size).to.be.below(5000000)
 
-        const path = join(root(), 'test1', 'videos', video.uuid + '-' + file.resolution.id + '.mp4')
+        const path = join(root(), 'test' + servers[0].internalServerNumber, 'videos', video.uuid + '-' + file.resolution.id + '.mp4')
         const bitrate = await getVideoFileBitrate(path)
         const fps = await getVideoFileFPS(path)
         const resolution = await getVideoFileResolution(path)
index c40ece838e9679508f7d1c8e03585ba2ff05dc6b..cb56922814c88d442797b28db802f314e1cf8dc1 100644 (file)
Binary files a/server/tests/fixtures/preview.jpg and b/server/tests/fixtures/preview.jpg differ
diff --git a/server/tests/fixtures/sample.ogg b/server/tests/fixtures/sample.ogg
new file mode 100644 (file)
index 0000000..0d7f43e
Binary files /dev/null and b/server/tests/fixtures/sample.ogg differ
index d2a068b783d96938a57035267e1801c9956d11eb..157d3ca9a9142e32cc51b6f85702c217eeba3089 100644 (file)
Binary files a/server/tests/fixtures/video_short1-preview.webm.jpg and b/server/tests/fixtures/video_short1-preview.webm.jpg differ
index 3cfae5c23bcfc4d0115d9a6fb5991366b65a81a1..34477cb78beba574294e6fffdd799cd15aa1b4aa 100644 (file)
@@ -1,11 +1,12 @@
 import { QueryTypes, Sequelize } from 'sequelize'
+import { ServerInfo } from '../server/servers'
 
 let sequelizes: { [ id: number ]: Sequelize } = {}
 
-function getSequelize (serverNumber: number) {
-  if (sequelizes[serverNumber]) return sequelizes[serverNumber]
+function getSequelize (internalServerNumber: number) {
+  if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber]
 
-  const dbname = 'peertube_test' + serverNumber
+  const dbname = 'peertube_test' + internalServerNumber
   const username = 'peertube'
   const password = 'peertube'
   const host = 'localhost'
@@ -18,37 +19,37 @@ function getSequelize (serverNumber: number) {
     logging: false
   })
 
-  sequelizes[serverNumber] = seq
+  sequelizes[internalServerNumber] = seq
 
   return seq
 }
 
-function setActorField (serverNumber: number, to: string, field: string, value: string) {
-  const seq = getSequelize(serverNumber)
+function setActorField (internalServerNumber: number, to: string, field: string, value: string) {
+  const seq = getSequelize(internalServerNumber)
 
   const options = { type: QueryTypes.UPDATE }
 
   return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
 }
 
-function setVideoField (serverNumber: number, uuid: string, field: string, value: string) {
-  const seq = getSequelize(serverNumber)
+function setVideoField (internalServerNumber: number, uuid: string, field: string, value: string) {
+  const seq = getSequelize(internalServerNumber)
 
   const options = { type: QueryTypes.UPDATE }
 
   return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
 }
 
-function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) {
-  const seq = getSequelize(serverNumber)
+function setPlaylistField (internalServerNumber: number, uuid: string, field: string, value: string) {
+  const seq = getSequelize(internalServerNumber)
 
   const options = { type: QueryTypes.UPDATE }
 
   return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
 }
 
-async function countVideoViewsOf (serverNumber: number, uuid: string) {
-  const seq = getSequelize(serverNumber)
+async function countVideoViewsOf (internalServerNumber: number, uuid: string) {
+  const seq = getSequelize(internalServerNumber)
 
   // tslint:disable
   const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
@@ -62,11 +63,11 @@ async function countVideoViewsOf (serverNumber: number, uuid: string) {
   return parseInt(total + '', 10)
 }
 
-async function closeAllSequelize (servers: any[]) {
-  for (let i = 1; i <= servers.length; i++) {
-    if (sequelizes[ i ]) {
-      await sequelizes[ i ].close()
-      delete sequelizes[ i ]
+async function closeAllSequelize (servers: ServerInfo[]) {
+  for (const server of servers) {
+    if (sequelizes[ server.internalServerNumber ]) {
+      await sequelizes[ server.internalServerNumber ].close()
+      delete sequelizes[ server.internalServerNumber ]
     }
   }
 }
index deb77e9c0e1067f98a9a73a2b94711022dd3a056..a5f5989e099f7bc0cb0ef2c81e2d09f2eed624af 100644 (file)
@@ -91,6 +91,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
     transcoding: {
       enabled: true,
       allowAdditionalExtensions: true,
+      allowAudioFiles: true,
       threads: 1,
       resolutions: {
         '240p': false,
index ed41bfa48161f6c5dad06a35b85f78d8fef14bb4..4c7d6862ae8d1bbc1694463acba812062a4e5043 100644 (file)
@@ -246,7 +246,7 @@ async function checkTmpIsEmpty (server: ServerInfo) {
 }
 
 async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
-  const testDirectory = 'test' + server.serverNumber
+  const testDirectory = 'test' + server.internalServerNumber
 
   const directoryPath = join(root(), testDirectory, directory)
 
@@ -284,7 +284,7 @@ function cleanupTests (servers: ServerInfo[]) {
 }
 
 async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
-  const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log')
+  const logfile = join(root(), 'test' + server.internalServerNumber, 'logs/peertube.log')
 
   while (true) {
     const buf = await readFile(logfile)
index 495ff80d9b61c450a6f02534a566a4f49ab0c012..f7de542bfe731b87c9ce94bc493cab7ea2e0a161 100644 (file)
@@ -380,7 +380,7 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
     }
   }
 
-  const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
+  const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
   function emailFinder (email: object) {
     return email[ 'text' ].indexOf(commentUrl) !== -1
   }
index 4d110a13124cebd1bd47ae286788a7d66cf3e013..fd62bef1999e93747e2a598cbb1b4cdc06294776 100644 (file)
@@ -252,10 +252,10 @@ function reorderVideosPlaylist (options: {
 
 async function checkPlaylistFilesWereRemoved (
   playlistUUID: string,
-  serverNumber: number,
+  internalServerNumber: number,
   directories = [ 'thumbnails' ]
 ) {
-  const testDirectory = 'test' + serverNumber
+  const testDirectory = 'test' + internalServerNumber
 
   for (const directory of directories) {
     const directoryPath = join(root(), testDirectory, directory)
index b5a07b792d55bd93eda3be9dfd9405cb4b819c66..b64de247074bd0d376fb2f7718584da6fef0be6e 100644 (file)
@@ -568,8 +568,8 @@ async function completeVideoCheck (
     expect(file).not.to.be.undefined
 
     let extension = extname(attributes.fixture)
-    // Transcoding enabled on server 2, extension will always be .mp4
-    if (attributes.account.host === 'localhost:9002') extension = '.mp4'
+    // Transcoding enabled: extension will always be .mp4
+    if (attributes.files.length > 1) extension = '.mp4'
 
     const magnetUri = file.magnetUri
     expect(file.magnetUri).to.have.lengthOf.above(2)
index ca52eff4ba761b48cd43510ab89424004b3ae99d..4cc379b2a89bb93373b279ff2d53238a872271c0 100644 (file)
@@ -54,6 +54,7 @@ export interface CustomConfig {
   transcoding: {
     enabled: boolean
     allowAdditionalExtensions: boolean
+    allowAudioFiles: boolean
     threads: number
     resolutions: {
       '240p': boolean
index 2eba6e6a37fd77144ed0e5ec0c041bb0b265cae5..4f20cf140f39ae1f699ba166765a03eb2d0357f2 100644 (file)
@@ -204,6 +204,9 @@ logs. You can set another password with:
 $ cd /var/www/peertube/peertube-latest && NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u root
 ```
 
+Alternatively you can set the environment variable `PT_INITIAL_ROOT_PASSWORD`,
+to your own administrator password, although it must be 6 characters or more.
+
 ### What now?
 
 Now your instance is up you can:
index f2cc0ee055205f4362da3fdf60dd61be43dff431..f2dc8a9ec248052e034837b5e241961ba0a3571c 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -515,6 +515,11 @@ ansi-regex@^3.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
   integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
 
+ansi-regex@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
+  integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+
 ansi-styles@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1446,6 +1451,11 @@ circular-json@^0.3.1:
   resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
   integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
 
+circular-json@^0.5.9:
+  version "0.5.9"
+  resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
+  integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==
+
 class-utils@^0.3.5:
   version "0.3.6"
   resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -2322,6 +2332,11 @@ elliptic@=3.0.3:
     hash.js "^1.0.0"
     inherits "^2.0.1"
 
+emoji-regex@^7.0.1:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
+  integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
+
 enabled@1.0.x:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93"
@@ -3304,6 +3319,11 @@ get-caller-file@^1.0.1:
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
   integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
 
+get-caller-file@^2.0.1:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
 get-func-name@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@@ -5322,6 +5342,15 @@ mkdirp@0.5.1, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi
   dependencies:
     minimist "0.0.8"
 
+mocha-parallel-tests@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mocha-parallel-tests/-/mocha-parallel-tests-2.1.0.tgz#94ab823b619b129fc347472f97c18595f0870c0e"
+  integrity sha512-NElZRp6T7kpis0mSkviPTwgIU13kkvazmmPPFLl/UqBeJoEjMj9tKz47qMV9kB0txURLoA1Rd/yDYqG1hlsKoA==
+  dependencies:
+    circular-json "^0.5.9"
+    debug "^4.1.1"
+    yargs "^13.2.2"
+
 mocha@^6.0.0:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.0.2.tgz#cdc1a6fdf66472c079b5605bac59d29807702d2c"
@@ -6170,7 +6199,7 @@ os-locale@^2.0.0:
     lcid "^1.0.0"
     mem "^1.1.0"
 
-os-locale@^3.0.0:
+os-locale@^3.0.0, os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
   integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
@@ -7171,6 +7200,11 @@ require-main-filename@^1.0.1:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
   integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
 
+require-main-filename@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+  integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
 require-uncached@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
@@ -8136,6 +8170,15 @@ string-width@^1.0.1:
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^4.0.0"
 
+string-width@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
+  integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
+  dependencies:
+    emoji-regex "^7.0.1"
+    is-fullwidth-code-point "^2.0.0"
+    strip-ansi "^5.1.0"
+
 string2compact@^1.1.1, string2compact@^1.2.5:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.0.tgz#22d946127b082d1203c51316af60117a337423c3"
@@ -8191,6 +8234,13 @@ strip-ansi@^4.0.0:
   dependencies:
     ansi-regex "^3.0.0"
 
+strip-ansi@^5.1.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
+  integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
+  dependencies:
+    ansi-regex "^4.1.0"
+
 strip-eof@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -9315,6 +9365,14 @@ yargs-parser@11.1.1, yargs-parser@^11.1.1:
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
+yargs-parser@^13.0.0:
+  version "13.0.0"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b"
+  integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^8.0.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
@@ -9374,6 +9432,23 @@ yargs@^11.0.0:
     y18n "^3.2.1"
     yargs-parser "^9.0.2"
 
+yargs@^13.2.2:
+  version "13.2.2"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993"
+  integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==
+  dependencies:
+    cliui "^4.0.0"
+    find-up "^3.0.0"
+    get-caller-file "^2.0.1"
+    os-locale "^3.1.0"
+    require-directory "^2.1.1"
+    require-main-filename "^2.0.0"
+    set-blocking "^2.0.0"
+    string-width "^3.0.0"
+    which-module "^2.0.0"
+    y18n "^4.0.0"
+    yargs-parser "^13.0.0"
+
 yeast@0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"