- 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
></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">
enabled: null,
threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
allowAdditionalExtensions: null,
+ allowAudioFiles: null,
resolutions: {}
},
autoBlacklist: {
</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>
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 {
<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>
@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)
}
}
export class ButtonComponent {
@Input() label = ''
- @Input() className: string = undefined
+ @Input() className = 'grey-button'
@Input() icon: GlobalIconName = undefined
@Input() title: string = undefined
-<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>
-<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>
<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"
/>
</div>
- <div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxFileSize | bytes }})</div>
-
<div class="filename" *ngIf="displayFilename === true && filename">{{ filename }}</div>
</div>
.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 {
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',
@Input() extensions: string[] = []
@Input() maxFileSize: number
@Input() displayFilename = false
+ @Input() icon: GlobalIconName
@Output() fileChanged = new EventEmitter<Blob>()
+++ /dev/null
-<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>
+++ /dev/null
-@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;
- }
- }
-}
--- /dev/null
+<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>
--- /dev/null
+@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;
+ }
+ }
+ }
+}
-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
return this.serverService.getConfig().video.image.size.max
}
+ ngOnInit () {
+ this.allowedExtensionsMessage = this.videoImageExtensions.join(', ')
+ }
+
onFileChanged (file: File) {
this.file = file
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'
ConfirmComponent,
GlobalIconComponent,
- ImageUploadComponent
+ PreviewUploadComponent
],
exports: [
ConfirmComponent,
GlobalIconComponent,
- ImageUploadComponent,
+ PreviewUploadComponent,
NumberFormatterPipe,
ObjectLengthPipe,
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'
resolutionId: number | string = -1
video: VideoDetails
+ activeModal: NgbActiveModal
constructor (
private notifier: Notifier,
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
}
download () {
window.location.assign(this.getLink())
+ this.activeModal.close()
}
getLink () {
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 () {
<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">
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,
</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>
@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 {
userVideoQuotaUsed = 0
userVideoQuotaUsedDaily = 0
+ isUploadingAudioFile = false
isUploadingVideo = false
isUpdatingVideo = false
+
videoUploaded = false
videoUploadObservable: Subscription = null
videoUploadPercents = 0
id: 0,
uuid: ''
}
+
waitTranscodingEnabled = true
+ previewfileUpload: File
error: string
}
}
+ 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()
}
}
}
- 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
}
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)
name,
privacy,
nsfw,
- channelId
+ channelId,
+ previewfile: this.previewfileUpload
})
this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
}
)
}
+
+ 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')
+ }
}
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)
+ }
}
}
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)
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...
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
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
transcoding:
enabled: true
allow_additional_extensions: true
+ allow_audio_files: true
transcoding:
enabled: true
allow_additional_extensions: false
+ allow_audio_files: false
threads: 2
resolutions:
240p: true
"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",
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')
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 })
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"
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' ],
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,
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()
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))
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 })
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()
},
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 {
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'
}
async function getVideoFileSize (path: string) {
- const videoStream = await getVideoFileStream(path)
+ const videoStream = await getVideoStreamFromFile(path)
return {
width: videoStream.width,
}
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]
}
}
-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)
}
return rej(err)
})
.on('end', () => {
- return onTranscodingSuccess(options)
+ return fixHLSPlaylistIfNeeded(options)
.then(() => res())
.catch(err => rej(err))
})
})
}
+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 {
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 (
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
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)
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)
* 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
return res({ absolutePath: data.format.filename })
}
- if (typeof option === 'string') {
- return ffmpeg.ffprobe(option, parseFfprobe)
- }
-
- return option.ffprobe(parseFfprobe)
+ return ffmpeg.ffprobe(option, parseFfprobe)
})
}
* 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')
.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()
.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
}
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')),
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') },
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'
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
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'
}
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: {
}
}
}
-MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT)
// ---------------------------------------------------------------------------
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' ]
},
height: 122
}
const PREVIEWS_SIZE = {
- width: 560,
- height: 315
+ width: 850,
+ height: 480
}
const AVATARS_SIZE = {
width: 120,
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 = {
}
updateWebserverUrls()
+updateWebserverConfig()
registerConfigChangedHandler(() => {
updateWebserverUrls()
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
}
'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
}
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 () {
// 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)
}
`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
}
`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
}
`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
}
`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
}
`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
}
`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
}
`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
}
`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
}
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
}
`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
}
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
}
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
}
`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
}
`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
}
`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
}
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
}
fromDisplayName: fromEmail,
replyTo: fromEmail,
to: [ CONFIG.ADMIN.EMAIL ],
- subject: '[PeerTube] Contact form submitted',
+ subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Contact form submitted',
text
}
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)
}
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'
await updateVideoFile(video, payload.filePath)
- await publishVideoIfNeeded(video)
+ await publishNewResolutionIfNeeded(video)
return video
}
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
}
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)
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)
})
}
-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)
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)
for (const resolution of resolutionsEnabled) {
const dataInput = {
+ type: 'new-resolution' as 'new-resolution',
videoUUID: videoDatabase.uuid,
resolution
}
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 })
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'
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 })
}
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'
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
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 }))
}
}
+/**
+ * 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
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) {
const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
const transcodeOptions = {
+ type: 'hls' as 'hls',
inputPath: videoInputPath,
outputPath,
resolution,
})
}
+// ---------------------------------------------------------------------------
+
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
}
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())
}
}
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',
}))
}
+ isAudio () {
+ return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
+ }
+
hasSameUniqueKeysThan (other: VideoFileModel) {
return this.fps === other.fps &&
this.resolution === other.resolution &&
import * as chai from 'chai'
import 'mocha'
import {
+ cleanupTests,
doubleFollow,
flushAndRunMultipleServers,
flushTests,
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')
})
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)
})
})
import 'mocha'
import {
+ cleanupTests,
closeAllSequelize,
createUser,
doubleFollow,
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 () {
})
after(async function () {
- killallServers(servers)
+ this.timeout(10000)
+
+ await cleanupTests(servers)
await closeAllSequelize(servers)
})
import 'mocha'
import {
+ cleanupTests, closeAllSequelize,
createVideoPlaylist,
doubleFollow,
flushAndRunMultipleServers,
generateUserAccessToken,
getVideo,
getVideoPlaylist,
- killallServers, rateVideo,
+ killallServers,
reRunServer,
ServerInfo,
setAccessTokensToServers,
}
{
- 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 () {
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)
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)
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)
})
})
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)
})
})
- after(function () {
- killallServers(servers)
+ after(async function () {
+ this.timeout(10000)
+
+ await cleanupTests(servers)
+
+ await closeAllSequelize(servers)
})
})
import 'mocha'
import {
+ cleanupTests,
closeAllSequelize,
flushAndRunMultipleServers,
- flushTests,
killallServers,
ServerInfo,
setActorField
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 () {
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
- }
+ })
// ---------------------------------------------------------------
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)
})
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)
})
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)
})
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)
})
transcoding: {
enabled: true,
allowAdditionalExtensions: true,
+ allowAudioFiles: true,
threads: 1,
resolutions: {
'240p': false,
+++ /dev/null
-import './check-params'
-import './notifications'
-import './search'
+++ /dev/null
-import './server'
-import './users'
+++ /dev/null
-import './videos'
+++ /dev/null
-import './redundancy'
-import './activitypub'
// 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'
-export * from './user-notifications'
+import './user-notifications'
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)
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)
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)
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)
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)
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')
})
})
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)
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)
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)
})
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)
})
})
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) {
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) {
}
}
- 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)
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)
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
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
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)
}
})
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 () {
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,
const expect = chai.expect
-describe('Test a ActivityPub video channels search', function () {
+describe('Test ActivityPub video channels search', function () {
let servers: ServerInfo[]
let userServer2Token: string
let videoServer2UUID: string
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)
{
// 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)
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) {
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) {
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')
// 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)
// 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')
// 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)
})
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 a ActivityPub videos search', function () {
+describe('Test ActivityPub videos search', function () {
let servers: ServerInfo[]
let videoServer1UUID: string
let videoServer2UUID: string
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')
{
// 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')
})
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')
})
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')
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)
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)
})
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 a videos search', function () {
+describe('Test videos search', function () {
let server: ServerInfo = null
let startDate: string
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 ' +
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
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)
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
before(async function () {
this.timeout(30000)
+
server = await flushAndRunServer(1)
await setAccessTokensToServers([ server ])
})
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
})
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 () {
transcoding: {
enabled: true,
allowAdditionalExtensions: true,
+ allowAudioFiles: true,
threads: 1,
resolutions: {
'240p': false,
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 () {
const res = await getCustomConfig(server.url, server.accessToken)
const data = res.body
- checkInitialConfig(data)
+ checkInitialConfig(server, data)
})
after(async 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)
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')
})
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'
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)
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')
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)
})
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')
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')
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')
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')
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')
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'
})
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)
})
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)
})
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)
})
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)
import * as chai from 'chai'
import 'mocha'
import {
- acceptFollower, cleanupTests,
+ acceptFollower,
+ cleanupTests,
flushAndRunMultipleServers,
- killallServers,
ServerInfo,
setAccessTokensToServers,
updateCustomSubConfig
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'
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')
}
{
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')
}
}
})
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)
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)
import {
flushAndRunMultipleServers,
getVideosList,
- killallServers,
ServerInfo,
setAccessTokensToServers,
uploadVideo
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
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)
}
{
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)
}
{
})
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 () {
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 () {
})
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 () {
})
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 () {
support: 'my super support text',
account: {
name: 'root',
- host: 'localhost:9003'
+ host: 'localhost:' + servers[2].port
},
isLocal,
commentsEnabled: true,
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
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)
})
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 () {
})
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' })
})
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 ])
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
})
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 () {
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)
}
{
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 () {
})
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 () {
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 () {
})
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 () {
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)
}
{
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 () {
})
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 () {
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 () {
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)
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)
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 () {
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)
})
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)
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)
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')
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
}
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
}
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)
}
})
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)
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
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')
})
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')
import {
addVideoChannel,
checkTmpIsEmpty,
- checkVideoFilesWereRemoved, cleanupTests,
+ checkVideoFilesWereRemoved,
+ cleanupTests,
completeVideoCheck,
createUser,
dateIsValid,
doubleFollow,
flushAndRunMultipleServers,
- flushTests,
getLocalVideos,
getVideo,
getVideoChannelsList,
getVideosList,
- killallServers,
rateVideo,
removeVideo,
ServerInfo,
// 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,
originallyPublishedAt: '2019-02-10T13:38:14.449Z',
account: {
name: 'root',
- host: 'localhost:9001'
+ host: 'localhost:' + servers[0].port
},
isLocal,
publishedAt,
// 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,
support: 'my super support text for server 2',
account: {
name: 'user1',
- host: 'localhost:9002'
+ host: 'localhost:' + servers[1].port
},
isLocal,
commentsEnabled: true,
files: [
{
resolution: 240,
- size: 187000
+ size: 189000
},
{
resolution: 360,
},
{
resolution: 480,
- size: 383000
+ size: 384000
},
{
resolution: 720,
// 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
support: 'my super support text for server 3',
account: {
name: 'root',
- host: 'localhost:9003'
+ host: 'localhost:' + servers[2].port
},
isLocal,
duration: 5,
support: 'my super support text for server 3-2',
account: {
name: 'root',
- host: 'localhost:9003'
+ host: 'localhost:' + servers[2].port
},
commentsEnabled: true,
downloadEnabled: true,
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,
originallyPublishedAt: '2019-02-11T13:38:14.449Z',
account: {
name: 'root',
- host: 'localhost:9003'
+ host: 'localhost:' + servers[2].port
},
isLocal,
duration: 5,
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
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
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)
}
})
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
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,
support: null,
account: {
name: 'root',
- host: 'localhost:9002'
+ host: 'localhost:' + servers[1].port
},
isLocal,
duration: 5,
})
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)
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)
let videoUUID = ''
let videosListBase: any[] = null
- const getCheckAttributes = {
+ const getCheckAttributes = () => ({
name: 'my super name',
category: 2,
licence: 6,
support: 'my super support text',
account: {
name: 'root',
- host: 'localhost:9001'
+ host: 'localhost:' + server.port
},
isLocal: true,
duration: 5,
size: 218910
}
]
- }
+ })
- const updateCheckAttributes = {
+ const updateCheckAttributes = () => ({
name: 'my super video updated',
category: 4,
licence: 2,
support: 'my super support text updated',
account: {
name: 'root',
- host: 'localhost:9001'
+ host: 'localhost:' + server.port
},
isLocal: true,
tags: [ 'tagup1', 'tagup2' ],
size: 292677
}
]
- }
+ })
before(async function () {
this.timeout(30000)
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 () {
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 () {
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 () {
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 () {
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 () {
flushAndRunMultipleServers,
getVideoAbusesList,
getVideosList,
- killallServers,
reportVideoAbuse,
ServerInfo,
setAccessTokensToServers,
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)
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')
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')
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
import 'mocha'
import {
acceptChangeOwnership,
- changeVideoOwnership, cleanupTests,
+ changeVideoOwnership,
+ cleanupTests,
createUser,
doubleFollow,
flushAndRunMultipleServers,
getVideo,
getVideoChangeOwnershipList,
getVideosList,
- killallServers,
refuseChangeOwnership,
ServerInfo,
setAccessTokensToServers,
}
})
- after(function () {
- killallServers(servers)
+ after(async function () {
+ await cleanupTests(servers)
})
})
let videoUUID: string
before(async function () {
- this.timeout(30000)
+ this.timeout(60000)
servers = await flushAndRunMultipleServers(2)
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')
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)
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
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')
import {
checkDirectoryIsEmpty,
checkSegmentHash,
- checkTmpIsEmpty, cleanupTests,
+ checkTmpIsEmpty,
+ cleanupTests,
doubleFollow,
flushAndRunMultipleServers,
- flushTests,
getPlaylist,
getVideo,
- killallServers,
removeVideo,
ServerInfo,
setAccessTokensToServers,
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
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`)
}
{
- 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)
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)
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)
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)
}
})
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')
]
this.timeout(30000)
for (const server of servers) {
- await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.serverNumber)
+ await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
}
})
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
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) {
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')
}
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' ]
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)
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)
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,
}
})
+ 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)
})
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'
{
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)
}
}
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' } } } })
{
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)
})
})
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'
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)
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'
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}'`
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 ]
}
}
}
transcoding: {
enabled: true,
allowAdditionalExtensions: true,
+ allowAudioFiles: true,
threads: 1,
resolutions: {
'240p': false,
}
async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
- const testDirectory = 'test' + server.serverNumber
+ const testDirectory = 'test' + server.internalServerNumber
const directoryPath = join(root(), testDirectory, directory)
}
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)
}
}
- 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
}
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)
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)
transcoding: {
enabled: boolean
allowAdditionalExtensions: boolean
+ allowAudioFiles: boolean
threads: number
resolutions: {
'240p': boolean
$ 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:
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"
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"
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"
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"
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"
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==
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"
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"
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"
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"
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"