Support additional video extensions
This commit is contained in:
parent
8923187455
commit
14e2014acc
|
@ -219,6 +219,14 @@
|
||||||
|
|
||||||
<ng-template [ngIf]="isTranscodingEnabled()">
|
<ng-template [ngIf]="isTranscodingEnabled()">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
inputName="transcodingAllowAdditionalExtensions" formControlName="transcodingAllowAdditionalExtensions"
|
||||||
|
i18n-labelText labelText="Allow additional extensions"
|
||||||
|
i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label i18n for="transcodingThreads">Transcoding threads</label>
|
<label i18n for="transcodingThreads">Transcoding threads</label>
|
||||||
<div class="peertube-select-container">
|
<div class="peertube-select-container">
|
||||||
|
|
|
@ -82,6 +82,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
||||||
userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
|
userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
|
||||||
transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
||||||
|
transcodingAllowAdditionalExtensions: null,
|
||||||
transcodingEnabled: null,
|
transcodingEnabled: null,
|
||||||
customizationJavascript: null,
|
customizationJavascript: null,
|
||||||
customizationCSS: null
|
customizationCSS: null
|
||||||
|
@ -163,6 +164,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: this.form.value['transcodingEnabled'],
|
enabled: this.form.value['transcodingEnabled'],
|
||||||
|
allowAdditionalExtensions: this.form.value['transcodingAllowAdditionalExtensions'],
|
||||||
threads: this.form.value['transcodingThreads'],
|
threads: this.form.value['transcodingThreads'],
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': this.form.value[this.getResolutionKey('240p')],
|
'240p': this.form.value[this.getResolutionKey('240p')],
|
||||||
|
@ -221,6 +223,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily,
|
userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily,
|
||||||
transcodingThreads: this.customConfig.transcoding.threads,
|
transcodingThreads: this.customConfig.transcoding.threads,
|
||||||
transcodingEnabled: this.customConfig.transcoding.enabled,
|
transcodingEnabled: this.customConfig.transcoding.enabled,
|
||||||
|
transcodingAllowAdditionalExtensions: this.customConfig.transcoding.allowAdditionalExtensions,
|
||||||
customizationJavascript: this.customConfig.instance.customizations.javascript,
|
customizationJavascript: this.customConfig.instance.customizations.javascript,
|
||||||
customizationCSS: this.customConfig.instance.customizations.css,
|
customizationCSS: this.customConfig.instance.customizations.css,
|
||||||
importVideosHttpEnabled: this.customConfig.import.videos.http.enabled,
|
importVideosHttpEnabled: this.customConfig.import.videos.http.enabled,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="root">
|
<div class="root">
|
||||||
<label class="form-group-checkbox">
|
<label class="form-group-checkbox">
|
||||||
<input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="isDisabled" />
|
<input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" />
|
||||||
<span role="checkbox" [attr.aria-checked]="checked"></span>
|
<span role="checkbox" [attr.aria-checked]="checked"></span>
|
||||||
<span *ngIf="labelText">{{ labelText }}</span>
|
<span *ngIf="labelText">{{ labelText }}</span>
|
||||||
<span *ngIf="labelHtml" [innerHTML]="labelHtml"></span>
|
<span *ngIf="labelHtml" [innerHTML]="labelHtml"></span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help>
|
<my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,8 +19,7 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor {
|
||||||
@Input() labelText: string
|
@Input() labelText: string
|
||||||
@Input() labelHtml: string
|
@Input() labelHtml: string
|
||||||
@Input() helpHtml: string
|
@Input() helpHtml: string
|
||||||
|
@Input() disabled = false
|
||||||
isDisabled = false
|
|
||||||
|
|
||||||
propagateChange = (_: any) => { /* empty */ }
|
propagateChange = (_: any) => { /* empty */ }
|
||||||
|
|
||||||
|
@ -41,6 +40,6 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisabledState (isDisabled: boolean) {
|
setDisabledState (isDisabled: boolean) {
|
||||||
this.isDisabled = isDisabled
|
this.disabled = isDisabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@
|
||||||
></my-peertube-checkbox>
|
></my-peertube-checkbox>
|
||||||
|
|
||||||
<my-peertube-checkbox
|
<my-peertube-checkbox
|
||||||
|
*ngIf="waitTranscodingEnabled"
|
||||||
inputName="waitTranscoding" formControlName="waitTranscoding"
|
inputName="waitTranscoding" formControlName="waitTranscoding"
|
||||||
i18n-labelText labelText="Wait transcoding before publishing the video"
|
i18n-labelText labelText="Wait transcoding before publishing the video"
|
||||||
i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
|
i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
|
||||||
|
|
|
@ -27,6 +27,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
@Input() userVideoChannels: { id: number, label: string, support: string }[] = []
|
@Input() userVideoChannels: { id: number, label: string, support: string }[] = []
|
||||||
@Input() schedulePublicationPossible = true
|
@Input() schedulePublicationPossible = true
|
||||||
@Input() videoCaptions: VideoCaptionEdit[] = []
|
@Input() videoCaptions: VideoCaptionEdit[] = []
|
||||||
|
@Input() waitTranscodingEnabled = true
|
||||||
|
|
||||||
@ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent
|
@ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<span i18n>Select the file to upload</span>
|
<span i18n>Select the file to upload</span>
|
||||||
<input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" />
|
<input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" />
|
||||||
</div>
|
</div>
|
||||||
<span class="button-file-extension">(.mp4, .webm, .ogv)</span>
|
<span class="button-file-extension">({{ videoExtensions }})</span>
|
||||||
|
|
||||||
<div class="form-group form-group-channel">
|
<div class="form-group form-group-channel">
|
||||||
<label i18n for="first-step-channel">Channel</label>
|
<label i18n for="first-step-channel">Channel</label>
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
<my-video-edit
|
<my-video-edit
|
||||||
[form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions"
|
[form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions"
|
||||||
[validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
|
[validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
|
||||||
|
[waitTranscodingEnabled]="waitTranscodingEnabled"
|
||||||
></my-video-edit>
|
></my-video-edit>
|
||||||
|
|
||||||
<div class="submit-container">
|
<div class="submit-container">
|
||||||
|
|
|
@ -44,6 +44,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
id: 0,
|
id: 0,
|
||||||
uuid: ''
|
uuid: ''
|
||||||
}
|
}
|
||||||
|
waitTranscodingEnabled = true
|
||||||
|
|
||||||
error: string
|
error: string
|
||||||
|
|
||||||
|
@ -117,6 +118,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
const videofile = this.videofileInput.nativeElement.files[0]
|
const videofile = this.videofileInput.nativeElement.files[0]
|
||||||
if (!videofile) return
|
if (!videofile) return
|
||||||
|
|
||||||
|
// Check global user quota
|
||||||
const bytePipes = new BytesPipe()
|
const bytePipes = new BytesPipe()
|
||||||
const videoQuota = this.authService.getUser().videoQuota
|
const videoQuota = this.authService.getUser().videoQuota
|
||||||
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
|
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
|
||||||
|
@ -132,6 +134,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check daily user quota
|
||||||
const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
|
const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
|
||||||
if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
|
if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
|
||||||
const msg = this.i18n(
|
const msg = this.i18n(
|
||||||
|
@ -146,6 +149,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build name field
|
||||||
const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
|
const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
|
||||||
let name: string
|
let name: string
|
||||||
|
|
||||||
|
@ -153,6 +157,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
if (nameWithoutExtension.length < 3) name = videofile.name
|
if (nameWithoutExtension.length < 3) name = videofile.name
|
||||||
else name = nameWithoutExtension
|
else name = nameWithoutExtension
|
||||||
|
|
||||||
|
// Force user to wait transcoding for unsupported video types in web browsers
|
||||||
|
if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) {
|
||||||
|
this.waitTranscodingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
const privacy = this.firstStepPrivacyId.toString()
|
const privacy = this.firstStepPrivacyId.toString()
|
||||||
const nsfw = false
|
const nsfw = false
|
||||||
const waitTranscoding = true
|
const waitTranscoding = true
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<my-video-edit
|
<my-video-edit
|
||||||
[form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible"
|
[form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible"
|
||||||
[validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
|
[validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
|
||||||
[videoCaptions]="videoCaptions"
|
[videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled"
|
||||||
></my-video-edit>
|
></my-video-edit>
|
||||||
|
|
||||||
<div class="submit-container">
|
<div class="submit-container">
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||||
import { VideoCaptionService } from '@app/shared/video-caption'
|
import { VideoCaptionService } from '@app/shared/video-caption'
|
||||||
import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
|
import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
|
||||||
|
import { VideoDetails } from '@app/shared/video/video-details.model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-videos-update',
|
selector: 'my-videos-update',
|
||||||
|
@ -26,6 +27,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
userVideoChannels: { id: number, label: string, support: string }[] = []
|
userVideoChannels: { id: number, label: string, support: string }[] = []
|
||||||
schedulePublicationPossible = false
|
schedulePublicationPossible = false
|
||||||
videoCaptions: VideoCaptionEdit[] = []
|
videoCaptions: VideoCaptionEdit[] = []
|
||||||
|
waitTranscodingEnabled = true
|
||||||
|
|
||||||
private updateDone = false
|
private updateDone = false
|
||||||
|
|
||||||
|
@ -65,6 +67,11 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
|
|
||||||
this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
|
this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
|
||||||
|
|
||||||
|
const videoFiles = (video as VideoDetails).files
|
||||||
|
if (videoFiles.length > 1) { // Already transcoded
|
||||||
|
this.waitTranscodingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
|
// FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
|
||||||
setTimeout(() => this.hydrateFormFromVideo())
|
setTimeout(() => this.hydrateFormFromVideo())
|
||||||
},
|
},
|
||||||
|
|
|
@ -124,6 +124,8 @@ user:
|
||||||
# Please, do not disable transcoding since many uploaded videos will not work
|
# Please, do not disable transcoding since many uploaded videos will not work
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# Allow your users to upload .mkv, .mov, .avi, .flv videos
|
||||||
|
allow_additional_extensions: true
|
||||||
threads: 1
|
threads: 1
|
||||||
resolutions: # Only created if the original video has a higher resolution, uses more storage!
|
resolutions: # Only created if the original video has a higher resolution, uses more storage!
|
||||||
240p: false
|
240p: false
|
||||||
|
|
|
@ -137,6 +137,8 @@ user:
|
||||||
# Please, do not disable transcoding since many uploaded videos will not work
|
# Please, do not disable transcoding since many uploaded videos will not work
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
# Allow your users to upload .mkv, .mov, .avi, .flv videos
|
||||||
|
allow_additional_extensions: true
|
||||||
threads: 1
|
threads: 1
|
||||||
resolutions: # Only created if the original video has a higher resolution, uses more storage!
|
resolutions: # Only created if the original video has a higher resolution, uses more storage!
|
||||||
240p: false
|
240p: false
|
||||||
|
|
|
@ -29,3 +29,4 @@ signup:
|
||||||
|
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
allow_additional_extensions: true
|
||||||
|
|
|
@ -51,6 +51,7 @@ signup:
|
||||||
|
|
||||||
transcoding:
|
transcoding:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
allow_additional_extensions: false
|
||||||
threads: 2
|
threads: 2
|
||||||
resolutions:
|
resolutions:
|
||||||
240p: true
|
240p: true
|
||||||
|
|
|
@ -172,7 +172,8 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
|
||||||
'instance.defaultClientRoute',
|
'instance.defaultClientRoute',
|
||||||
'instance.shortDescription',
|
'instance.shortDescription',
|
||||||
'cache.videoCaptions',
|
'cache.videoCaptions',
|
||||||
'signup.requiresEmailVerification'
|
'signup.requiresEmailVerification',
|
||||||
|
'transcoding.allowAdditionalExtensions'
|
||||||
)
|
)
|
||||||
toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
|
toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
|
||||||
toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily
|
toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily
|
||||||
|
@ -180,6 +181,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
|
||||||
toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
|
toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
|
||||||
toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy
|
toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy
|
||||||
toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification
|
toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification
|
||||||
|
toUpdateJSON.transcoding['allow_additional_extensions'] = toUpdate.transcoding.allowAdditionalExtensions
|
||||||
|
|
||||||
await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
|
await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
|
||||||
|
|
||||||
|
@ -247,6 +249,7 @@ function customConfig (): CustomConfig {
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: CONFIG.TRANSCODING.ENABLED,
|
enabled: CONFIG.TRANSCODING.ENABLED,
|
||||||
|
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
|
||||||
threads: CONFIG.TRANSCODING.THREADS,
|
threads: CONFIG.TRANSCODING.THREADS,
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
|
'240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
||||||
import 'multer'
|
import 'multer'
|
||||||
import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
|
import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../../initializers'
|
import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
|
||||||
import { sendUpdateActor } from '../../../lib/activitypub/send'
|
import { sendUpdateActor } from '../../../lib/activitypub/send'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
|
@ -42,7 +42,7 @@ import { AccountModel } from '../../../models/account/account'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('users-me')
|
const auditLogger = auditLoggerFactory('users-me')
|
||||||
|
|
||||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
||||||
|
|
||||||
const meRouter = express.Router()
|
const meRouter = express.Router()
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { createVideoChannel } from '../../lib/video-channel'
|
||||||
import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
||||||
import { setAsyncActorKeys } from '../../lib/activitypub'
|
import { setAsyncActorKeys } from '../../lib/activitypub'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
|
import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
import { updateAvatarValidator } from '../../middlewares/validators/avatar'
|
import { updateAvatarValidator } from '../../middlewares/validators/avatar'
|
||||||
|
@ -32,7 +32,7 @@ import { resetSequelizeInstance } from '../../helpers/database-utils'
|
||||||
import { UserModel } from '../../models/account/user'
|
import { UserModel } from '../../models/account/user'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('channels')
|
const auditLogger = auditLoggerFactory('channels')
|
||||||
const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
|
||||||
|
|
||||||
const videoChannelRouter = express.Router()
|
const videoChannelRouter = express.Router()
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
||||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
|
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
|
||||||
import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
|
import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils'
|
import { createReqFiles } from '../../../helpers/express-utils'
|
||||||
import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers'
|
import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import { VideoCaptionModel } from '../../../models/video/video-caption'
|
import { VideoCaptionModel } from '../../../models/video/video-caption'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
@ -12,7 +12,7 @@ import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
|
||||||
|
|
||||||
const reqVideoCaptionAdd = createReqFiles(
|
const reqVideoCaptionAdd = createReqFiles(
|
||||||
[ 'captionfile' ],
|
[ 'captionfile' ],
|
||||||
VIDEO_CAPTIONS_MIMETYPE_EXT,
|
MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT,
|
||||||
{
|
{
|
||||||
captionfile: CONFIG.STORAGE.CAPTIONS_DIR
|
captionfile: CONFIG.STORAGE.CAPTIONS_DIR
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,7 @@ import * as magnetUtil from 'magnet-uri'
|
||||||
import 'multer'
|
import 'multer'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
|
||||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
|
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
|
||||||
import {
|
import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
|
||||||
CONFIG,
|
|
||||||
IMAGE_MIMETYPE_EXT,
|
|
||||||
PREVIEWS_SIZE,
|
|
||||||
sequelizeTypescript,
|
|
||||||
THUMBNAILS_SIZE,
|
|
||||||
TORRENT_MIMETYPE_EXT
|
|
||||||
} from '../../../initializers'
|
|
||||||
import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
|
import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils'
|
import { createReqFiles } from '../../../helpers/express-utils'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
|
@ -35,7 +28,7 @@ const videoImportsRouter = express.Router()
|
||||||
|
|
||||||
const reqVideoFileImport = createReqFiles(
|
const reqVideoFileImport = createReqFiles(
|
||||||
[ 'thumbnailfile', 'previewfile', 'torrentfile' ],
|
[ 'thumbnailfile', 'previewfile', 'torrentfile' ],
|
||||||
Object.assign({}, TORRENT_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
|
Object.assign({}, MIMETYPES.TORRENT.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
|
||||||
{
|
{
|
||||||
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
||||||
previewfile: CONFIG.STORAGE.TMP_DIR,
|
previewfile: CONFIG.STORAGE.TMP_DIR,
|
||||||
|
|
|
@ -7,15 +7,13 @@ import { logger } from '../../../helpers/logger'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
|
import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
|
||||||
import {
|
import {
|
||||||
CONFIG,
|
CONFIG, MIMETYPES,
|
||||||
IMAGE_MIMETYPE_EXT,
|
|
||||||
PREVIEWS_SIZE,
|
PREVIEWS_SIZE,
|
||||||
sequelizeTypescript,
|
sequelizeTypescript,
|
||||||
THUMBNAILS_SIZE,
|
THUMBNAILS_SIZE,
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
VIDEO_LANGUAGES,
|
VIDEO_LANGUAGES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_MIMETYPE_EXT,
|
|
||||||
VIDEO_PRIVACIES
|
VIDEO_PRIVACIES
|
||||||
} from '../../../initializers'
|
} from '../../../initializers'
|
||||||
import {
|
import {
|
||||||
|
@ -57,7 +55,7 @@ import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-u
|
||||||
import { videoCaptionsRouter } from './captions'
|
import { videoCaptionsRouter } from './captions'
|
||||||
import { videoImportsRouter } from './import'
|
import { videoImportsRouter } from './import'
|
||||||
import { resetSequelizeInstance } from '../../../helpers/database-utils'
|
import { resetSequelizeInstance } from '../../../helpers/database-utils'
|
||||||
import { rename } from 'fs-extra'
|
import { move } from 'fs-extra'
|
||||||
import { watchingRouter } from './watching'
|
import { watchingRouter } from './watching'
|
||||||
|
|
||||||
const auditLogger = auditLoggerFactory('videos')
|
const auditLogger = auditLoggerFactory('videos')
|
||||||
|
@ -65,7 +63,7 @@ const videosRouter = express.Router()
|
||||||
|
|
||||||
const reqVideoFileAdd = createReqFiles(
|
const reqVideoFileAdd = createReqFiles(
|
||||||
[ 'videofile', 'thumbnailfile', 'previewfile' ],
|
[ 'videofile', 'thumbnailfile', 'previewfile' ],
|
||||||
Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
|
Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
|
||||||
{
|
{
|
||||||
videofile: CONFIG.STORAGE.TMP_DIR,
|
videofile: CONFIG.STORAGE.TMP_DIR,
|
||||||
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
||||||
|
@ -74,7 +72,7 @@ const reqVideoFileAdd = createReqFiles(
|
||||||
)
|
)
|
||||||
const reqVideoFileUpdate = createReqFiles(
|
const reqVideoFileUpdate = createReqFiles(
|
||||||
[ 'thumbnailfile', 'previewfile' ],
|
[ 'thumbnailfile', 'previewfile' ],
|
||||||
IMAGE_MIMETYPE_EXT,
|
MIMETYPES.IMAGE.MIMETYPE_EXT,
|
||||||
{
|
{
|
||||||
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
thumbnailfile: CONFIG.STORAGE.TMP_DIR,
|
||||||
previewfile: CONFIG.STORAGE.TMP_DIR
|
previewfile: CONFIG.STORAGE.TMP_DIR
|
||||||
|
@ -208,7 +206,7 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
// Move physical file
|
// Move physical file
|
||||||
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
|
const videoDir = CONFIG.STORAGE.VIDEOS_DIR
|
||||||
const destination = join(videoDir, video.getVideoFilename(videoFile))
|
const destination = join(videoDir, video.getVideoFilename(videoFile))
|
||||||
await rename(videoPhysicalFile.path, destination)
|
await move(videoPhysicalFile.path, destination)
|
||||||
// This is important in case if there is another attempt in the retry process
|
// This is important in case if there is another attempt in the retry process
|
||||||
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
|
videoPhysicalFile.filename = video.getVideoFilename(videoFile)
|
||||||
videoPhysicalFile.path = destination
|
videoPhysicalFile.path = destination
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CONSTRAINTS_FIELDS, VIDEO_CAPTIONS_MIMETYPE_EXT, VIDEO_LANGUAGES } from '../../initializers'
|
import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers'
|
||||||
import { exists, isFileValid } from './misc'
|
import { exists, isFileValid } from './misc'
|
||||||
import { Response } from 'express'
|
import { Response } from 'express'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
|
@ -8,7 +8,7 @@ function isVideoCaptionLanguageValid (value: any) {
|
||||||
return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined
|
return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoCaptionTypes = Object.keys(VIDEO_CAPTIONS_MIMETYPE_EXT)
|
const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT)
|
||||||
.concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream ><
|
.concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream ><
|
||||||
.map(m => `(${m})`)
|
.map(m => `(${m})`)
|
||||||
const videoCaptionTypesRegex = videoCaptionTypes.join('|')
|
const videoCaptionTypesRegex = videoCaptionTypes.join('|')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'express-validator'
|
import 'express-validator'
|
||||||
import 'multer'
|
import 'multer'
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import { CONSTRAINTS_FIELDS, TORRENT_MIMETYPE_EXT, VIDEO_IMPORT_STATES } from '../../initializers'
|
import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers'
|
||||||
import { exists, isFileValid } from './misc'
|
import { exists, isFileValid } from './misc'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { VideoImportModel } from '../../models/video/video-import'
|
import { VideoImportModel } from '../../models/video/video-import'
|
||||||
|
@ -24,7 +24,7 @@ function isVideoImportStateValid (value: any) {
|
||||||
return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined
|
return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoTorrentImportTypes = Object.keys(TORRENT_MIMETYPE_EXT).map(m => `(${m})`)
|
const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`)
|
||||||
const videoTorrentImportRegex = videoTorrentImportTypes.join('|')
|
const videoTorrentImportRegex = videoTorrentImportTypes.join('|')
|
||||||
function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
|
function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
|
||||||
return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true)
|
return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true)
|
||||||
|
|
|
@ -5,10 +5,9 @@ import 'multer'
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
|
import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
|
||||||
import {
|
import {
|
||||||
CONSTRAINTS_FIELDS,
|
CONSTRAINTS_FIELDS, MIMETYPES,
|
||||||
VIDEO_CATEGORIES,
|
VIDEO_CATEGORIES,
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_MIMETYPE_EXT,
|
|
||||||
VIDEO_PRIVACIES,
|
VIDEO_PRIVACIES,
|
||||||
VIDEO_RATE_TYPES,
|
VIDEO_RATE_TYPES,
|
||||||
VIDEO_STATES
|
VIDEO_STATES
|
||||||
|
@ -83,10 +82,15 @@ function isVideoRatingTypeValid (value: string) {
|
||||||
return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
|
return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
|
function isVideoFileExtnameValid (value: string) {
|
||||||
const videoFileTypesRegex = videoFileTypes.join('|')
|
return exists(value) && MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
|
function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
|
||||||
|
const videoFileTypesRegex = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
|
||||||
|
.map(m => `(${m})`)
|
||||||
|
.join('|')
|
||||||
|
|
||||||
return isFileValid(files, videoFileTypesRegex, 'videofile', null)
|
return isFileValid(files, videoFileTypesRegex, 'videofile', null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +225,7 @@ export {
|
||||||
isVideoStateValid,
|
isVideoStateValid,
|
||||||
isVideoViewsValid,
|
isVideoViewsValid,
|
||||||
isVideoRatingTypeValid,
|
isVideoRatingTypeValid,
|
||||||
|
isVideoFileExtnameValid,
|
||||||
isVideoDurationValid,
|
isVideoDurationValid,
|
||||||
isVideoTagValid,
|
isVideoTagValid,
|
||||||
isVideoPrivacyValid,
|
isVideoPrivacyValid,
|
||||||
|
|
|
@ -19,7 +19,7 @@ function checkMissedConfig () {
|
||||||
'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
|
'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
|
||||||
'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
||||||
'redundancy.videos.strategies', 'redundancy.videos.check_interval',
|
'redundancy.videos.strategies', 'redundancy.videos.check_interval',
|
||||||
'transcoding.enabled', 'transcoding.threads',
|
'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
|
||||||
'import.videos.http.enabled', 'import.videos.torrent.enabled',
|
'import.videos.http.enabled', 'import.videos.torrent.enabled',
|
||||||
'trending.videos.interval_days',
|
'trending.videos.interval_days',
|
||||||
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
|
||||||
|
|
|
@ -16,7 +16,7 @@ let config: IConfig = require('config')
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const LAST_MIGRATION_VERSION = 290
|
const LAST_MIGRATION_VERSION = 295
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -246,6 +246,7 @@ const CONFIG = {
|
||||||
},
|
},
|
||||||
TRANSCODING: {
|
TRANSCODING: {
|
||||||
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
||||||
|
get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
|
||||||
get THREADS () { return config.get<number>('transcoding.threads') },
|
get THREADS () { return config.get<number>('transcoding.threads') },
|
||||||
RESOLUTIONS: {
|
RESOLUTIONS: {
|
||||||
get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
|
get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
|
||||||
|
@ -298,7 +299,7 @@ const CONFIG = {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const CONSTRAINTS_FIELDS = {
|
let CONSTRAINTS_FIELDS = {
|
||||||
USERS: {
|
USERS: {
|
||||||
NAME: { min: 1, max: 50 }, // Length
|
NAME: { min: 1, max: 50 }, // Length
|
||||||
DESCRIPTION: { min: 3, max: 1000 }, // Length
|
DESCRIPTION: { min: 3, max: 1000 }, // Length
|
||||||
|
@ -357,7 +358,7 @@ const CONSTRAINTS_FIELDS = {
|
||||||
max: 2 * 1024 * 1024 // 2MB
|
max: 2 * 1024 * 1024 // 2MB
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
EXTNAME: [ '.mp4', '.ogv', '.webm' ],
|
EXTNAME: buildVideosExtname(),
|
||||||
INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
|
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
|
DURATION: { min: 0 }, // Number
|
||||||
TAGS: { min: 0, max: 5 }, // Number of total tags
|
TAGS: { min: 0, max: 5 }, // Number of total tags
|
||||||
|
@ -480,27 +481,31 @@ const VIDEO_ABUSE_STATES = {
|
||||||
[VideoAbuseState.ACCEPTED]: 'Accepted'
|
[VideoAbuseState.ACCEPTED]: 'Accepted'
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIDEO_MIMETYPE_EXT = {
|
const MIMETYPES = {
|
||||||
'video/webm': '.webm',
|
VIDEO: {
|
||||||
'video/ogg': '.ogv',
|
MIMETYPE_EXT: buildVideoMimetypeExt(),
|
||||||
'video/mp4': '.mp4'
|
EXT_MIMETYPE: null as { [ id: string ]: string }
|
||||||
}
|
},
|
||||||
const VIDEO_EXT_MIMETYPE = invert(VIDEO_MIMETYPE_EXT)
|
IMAGE: {
|
||||||
|
MIMETYPE_EXT: {
|
||||||
const IMAGE_MIMETYPE_EXT = {
|
'image/png': '.png',
|
||||||
'image/png': '.png',
|
'image/jpg': '.jpg',
|
||||||
'image/jpg': '.jpg',
|
'image/jpeg': '.jpg'
|
||||||
'image/jpeg': '.jpg'
|
}
|
||||||
}
|
},
|
||||||
|
VIDEO_CAPTIONS: {
|
||||||
const VIDEO_CAPTIONS_MIMETYPE_EXT = {
|
MIMETYPE_EXT: {
|
||||||
'text/vtt': '.vtt',
|
'text/vtt': '.vtt',
|
||||||
'application/x-subrip': '.srt'
|
'application/x-subrip': '.srt'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const TORRENT_MIMETYPE_EXT = {
|
TORRENT: {
|
||||||
'application/x-bittorrent': '.torrent'
|
MIMETYPE_EXT: {
|
||||||
|
'application/x-bittorrent': '.torrent'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -526,7 +531,7 @@ const ACTIVITY_PUB = {
|
||||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||||
FETCH_PAGE_LIMIT: 100,
|
FETCH_PAGE_LIMIT: 100,
|
||||||
URL_MIME_TYPES: {
|
URL_MIME_TYPES: {
|
||||||
VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
|
VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT),
|
||||||
TORRENT: [ 'application/x-bittorrent' ],
|
TORRENT: [ 'application/x-bittorrent' ],
|
||||||
MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
|
MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
|
||||||
},
|
},
|
||||||
|
@ -685,13 +690,12 @@ if (isTestInstance() === true) {
|
||||||
ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
|
ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
|
||||||
}
|
}
|
||||||
|
|
||||||
updateWebserverConfig()
|
updateWebserverUrls()
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
API_VERSION,
|
API_VERSION,
|
||||||
VIDEO_CAPTIONS_MIMETYPE_EXT,
|
|
||||||
AVATARS_SIZE,
|
AVATARS_SIZE,
|
||||||
ACCEPT_HEADERS,
|
ACCEPT_HEADERS,
|
||||||
BCRYPT_SALT_SIZE,
|
BCRYPT_SALT_SIZE,
|
||||||
|
@ -719,7 +723,6 @@ export {
|
||||||
FEEDS,
|
FEEDS,
|
||||||
JOB_TTL,
|
JOB_TTL,
|
||||||
NSFW_POLICY_TYPES,
|
NSFW_POLICY_TYPES,
|
||||||
TORRENT_MIMETYPE_EXT,
|
|
||||||
STATIC_MAX_AGE,
|
STATIC_MAX_AGE,
|
||||||
STATIC_PATHS,
|
STATIC_PATHS,
|
||||||
VIDEO_IMPORT_TIMEOUT,
|
VIDEO_IMPORT_TIMEOUT,
|
||||||
|
@ -732,7 +735,6 @@ export {
|
||||||
VIDEO_LICENCES,
|
VIDEO_LICENCES,
|
||||||
VIDEO_STATES,
|
VIDEO_STATES,
|
||||||
VIDEO_RATE_TYPES,
|
VIDEO_RATE_TYPES,
|
||||||
VIDEO_MIMETYPE_EXT,
|
|
||||||
VIDEO_TRANSCODING_FPS,
|
VIDEO_TRANSCODING_FPS,
|
||||||
FFMPEG_NICE,
|
FFMPEG_NICE,
|
||||||
VIDEO_ABUSE_STATES,
|
VIDEO_ABUSE_STATES,
|
||||||
|
@ -740,13 +742,12 @@ export {
|
||||||
USER_PASSWORD_RESET_LIFETIME,
|
USER_PASSWORD_RESET_LIFETIME,
|
||||||
MEMOIZE_TTL,
|
MEMOIZE_TTL,
|
||||||
USER_EMAIL_VERIFY_LIFETIME,
|
USER_EMAIL_VERIFY_LIFETIME,
|
||||||
IMAGE_MIMETYPE_EXT,
|
|
||||||
OVERVIEWS,
|
OVERVIEWS,
|
||||||
SCHEDULER_INTERVALS_MS,
|
SCHEDULER_INTERVALS_MS,
|
||||||
REPEAT_JOBS,
|
REPEAT_JOBS,
|
||||||
STATIC_DOWNLOAD_PATHS,
|
STATIC_DOWNLOAD_PATHS,
|
||||||
RATES_LIMIT,
|
RATES_LIMIT,
|
||||||
VIDEO_EXT_MIMETYPE,
|
MIMETYPES,
|
||||||
CRAWL_REQUEST_CONCURRENCY,
|
CRAWL_REQUEST_CONCURRENCY,
|
||||||
JOB_COMPLETED_LIFETIME,
|
JOB_COMPLETED_LIFETIME,
|
||||||
HTTP_SIGNATURE,
|
HTTP_SIGNATURE,
|
||||||
|
@ -768,11 +769,43 @@ function getLocalConfigFilePath () {
|
||||||
return join(dirname(configSources[ 0 ].name), filename + '.json')
|
return join(dirname(configSources[ 0 ].name), filename + '.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateWebserverConfig () {
|
function buildVideoMimetypeExt () {
|
||||||
|
const data = {
|
||||||
|
'video/webm': '.webm',
|
||||||
|
'video/ogg': '.ogv',
|
||||||
|
'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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWebserverUrls () {
|
||||||
CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
|
CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
|
||||||
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
|
CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateWebserverConfig () {
|
||||||
|
CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
|
||||||
|
|
||||||
|
MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
|
||||||
|
MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildVideosExtname () {
|
||||||
|
return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS
|
||||||
|
? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
|
||||||
|
: [ '.mp4', '.ogv', '.webm' ]
|
||||||
|
}
|
||||||
|
|
||||||
function buildVideosRedundancy (objs: any[]): VideosRedundancy[] {
|
function buildVideosRedundancy (objs: any[]): VideosRedundancy[] {
|
||||||
if (!objs) return []
|
if (!objs) return []
|
||||||
|
|
||||||
|
@ -854,4 +887,5 @@ export function reloadConfig () {
|
||||||
config = require('config')
|
config = require('config')
|
||||||
|
|
||||||
updateWebserverConfig()
|
updateWebserverConfig()
|
||||||
|
updateWebserverUrls()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction,
|
||||||
|
queryInterface: Sequelize.QueryInterface,
|
||||||
|
sequelize: Sequelize.Sequelize,
|
||||||
|
db: any
|
||||||
|
}): Promise<void> {
|
||||||
|
{
|
||||||
|
await utils.queryInterface.renameColumn('videoFile', 'extname', 'extname_old')
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
defaultValue: null,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
|
||||||
|
await utils.queryInterface.addColumn('videoFile', 'extname', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const query = 'UPDATE "videoFile" SET "extname" = "extname_old"::text'
|
||||||
|
await utils.sequelize.query(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
defaultValue: null,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
await utils.queryInterface.changeColumn('videoFile', 'extname', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
await utils.queryInterface.removeColumn('videoFile', 'extname_old')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
up,
|
||||||
|
down
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import { join } from 'path'
|
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import * as url from 'url'
|
import * as url from 'url'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import * as uuidv4 from 'uuid/v4'
|
||||||
|
@ -13,7 +12,7 @@ import { logger } from '../../helpers/logger'
|
||||||
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
|
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
|
||||||
import { doRequest, downloadImage } from '../../helpers/requests'
|
import { doRequest, downloadImage } from '../../helpers/requests'
|
||||||
import { getUrlFromWebfinger } from '../../helpers/webfinger'
|
import { getUrlFromWebfinger } from '../../helpers/webfinger'
|
||||||
import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
|
import { AVATARS_SIZE, CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
import { ActorModel } from '../../models/activitypub/actor'
|
import { ActorModel } from '../../models/activitypub/actor'
|
||||||
import { AvatarModel } from '../../models/avatar/avatar'
|
import { AvatarModel } from '../../models/avatar/avatar'
|
||||||
|
@ -172,10 +171,10 @@ async function fetchActorTotalItems (url: string) {
|
||||||
|
|
||||||
async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
|
async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
|
||||||
if (
|
if (
|
||||||
actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
|
actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
|
||||||
isActivityPubUrlValid(actorJSON.icon.url)
|
isActivityPubUrlValid(actorJSON.icon.url)
|
||||||
) {
|
) {
|
||||||
const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType]
|
const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType]
|
||||||
|
|
||||||
const avatarName = uuidv4() + extension
|
const avatarName = uuidv4() + extension
|
||||||
await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE)
|
await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import * as sequelize from 'sequelize'
|
import * as sequelize from 'sequelize'
|
||||||
import * as magnetUtil from 'magnet-uri'
|
import * as magnetUtil from 'magnet-uri'
|
||||||
import { join } from 'path'
|
|
||||||
import * as request from 'request'
|
import * as request from 'request'
|
||||||
import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index'
|
import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index'
|
||||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
||||||
|
@ -11,7 +10,7 @@ import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos
|
||||||
import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
|
import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { doRequest, downloadImage } from '../../helpers/requests'
|
import { doRequest, downloadImage } from '../../helpers/requests'
|
||||||
import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_MIMETYPE_EXT } from '../../initializers'
|
import { ACTIVITY_PUB, CONFIG, MIMETYPES, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers'
|
||||||
import { ActorModel } from '../../models/activitypub/actor'
|
import { ActorModel } from '../../models/activitypub/actor'
|
||||||
import { TagModel } from '../../models/video/tag'
|
import { TagModel } from '../../models/video/tag'
|
||||||
import { VideoModel } from '../../models/video/video'
|
import { VideoModel } from '../../models/video/video'
|
||||||
|
@ -362,7 +361,7 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject {
|
function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject {
|
||||||
const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
|
const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
|
||||||
|
|
||||||
const urlMediaType = url.mediaType || url.mimeType
|
const urlMediaType = url.mediaType || url.mimeType
|
||||||
return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/')
|
return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/')
|
||||||
|
@ -490,7 +489,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
|
||||||
|
|
||||||
const mediaType = fileUrl.mediaType || fileUrl.mimeType
|
const mediaType = fileUrl.mediaType || fileUrl.mimeType
|
||||||
const attribute = {
|
const attribute = {
|
||||||
extname: VIDEO_MIMETYPE_EXT[ mediaType ],
|
extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ],
|
||||||
infoHash: parsed.infoHash,
|
infoHash: parsed.infoHash,
|
||||||
resolution: fileUrl.height,
|
resolution: fileUrl.height,
|
||||||
size: fileUrl.size,
|
size: fileUrl.size,
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
import { ActorModel } from '../activitypub/actor'
|
import { ActorModel } from '../activitypub/actor'
|
||||||
import { getVideoSort, throwIfNotValid } from '../utils'
|
import { getVideoSort, throwIfNotValid } from '../utils'
|
||||||
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { CONFIG, CONSTRAINTS_FIELDS, STATIC_PATHS, VIDEO_EXT_MIMETYPE } from '../../initializers'
|
import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers'
|
||||||
import { VideoFileModel } from '../video/video-file'
|
import { VideoFileModel } from '../video/video-file'
|
||||||
import { getServerActor } from '../../helpers/utils'
|
import { getServerActor } from '../../helpers/utils'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
|
@ -415,8 +415,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
|
||||||
expires: this.expiresOn.toISOString(),
|
expires: this.expiresOn.toISOString(),
|
||||||
url: {
|
url: {
|
||||||
type: 'Link',
|
type: 'Link',
|
||||||
mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any,
|
mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
|
||||||
mediaType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any,
|
mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
|
||||||
href: this.fileUrl,
|
href: this.fileUrl,
|
||||||
height: this.VideoFile.resolution,
|
height: this.VideoFile.resolution,
|
||||||
size: this.VideoFile.size,
|
size: this.VideoFile.size,
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import {
|
import {
|
||||||
|
isVideoFileExtnameValid,
|
||||||
isVideoFileInfoHashValid,
|
isVideoFileInfoHashValid,
|
||||||
isVideoFileResolutionValid,
|
isVideoFileResolutionValid,
|
||||||
isVideoFileSizeValid,
|
isVideoFileSizeValid,
|
||||||
|
@ -58,7 +59,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
|
||||||
size: number
|
size: number
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
@Column(DataType.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)))
|
@Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname'))
|
||||||
|
@Column
|
||||||
extname: string
|
extname: string
|
||||||
|
|
||||||
@AllowNull(false)
|
@AllowNull(false)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoFileModel } from './video-file'
|
import { VideoFileModel } from './video-file'
|
||||||
import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects'
|
||||||
import { CONFIG, THUMBNAILS_SIZE, VIDEO_EXT_MIMETYPE } from '../../initializers'
|
import { CONFIG, MIMETYPES, THUMBNAILS_SIZE } from '../../initializers'
|
||||||
import { VideoCaptionModel } from './video-caption'
|
import { VideoCaptionModel } from './video-caption'
|
||||||
import {
|
import {
|
||||||
getVideoCommentsActivityPubUrl,
|
getVideoCommentsActivityPubUrl,
|
||||||
|
@ -207,8 +207,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
|
||||||
for (const file of video.VideoFiles) {
|
for (const file of video.VideoFiles) {
|
||||||
url.push({
|
url.push({
|
||||||
type: 'Link',
|
type: 'Link',
|
||||||
mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
|
mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any,
|
||||||
mediaType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
|
mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any,
|
||||||
href: video.getVideoFileUrl(file, baseUrlHttp),
|
href: video.getVideoFileUrl(file, baseUrlHttp),
|
||||||
height: file.resolution,
|
height: file.resolution,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
|
|
|
@ -54,6 +54,7 @@ describe('Test config API validators', function () {
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
allowAdditionalExtensions: true,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': false,
|
'240p': false,
|
||||||
|
|
|
@ -320,10 +320,15 @@ describe('Test videos API validator', function () {
|
||||||
|
|
||||||
it('Should fail without an incorrect input file', async function () {
|
it('Should fail without an incorrect input file', async function () {
|
||||||
const fields = baseCorrectParams
|
const fields = baseCorrectParams
|
||||||
const attaches = {
|
let attaches = {
|
||||||
'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm')
|
'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm')
|
||||||
}
|
}
|
||||||
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
|
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
|
||||||
|
|
||||||
|
attaches = {
|
||||||
|
'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mkv')
|
||||||
|
}
|
||||||
|
await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with an incorrect thumbnail file', async function () {
|
it('Should fail with an incorrect thumbnail file', async function () {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
updateCustomConfig
|
updateCustomConfig
|
||||||
} from '../../../../shared/utils'
|
} from '../../../../shared/utils'
|
||||||
|
import { ServerConfig } from '../../../../shared/models'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ function checkInitialConfig (data: CustomConfig) {
|
||||||
expect(data.user.videoQuota).to.equal(5242880)
|
expect(data.user.videoQuota).to.equal(5242880)
|
||||||
expect(data.user.videoQuotaDaily).to.equal(-1)
|
expect(data.user.videoQuotaDaily).to.equal(-1)
|
||||||
expect(data.transcoding.enabled).to.be.false
|
expect(data.transcoding.enabled).to.be.false
|
||||||
|
expect(data.transcoding.allowAdditionalExtensions).to.be.false
|
||||||
expect(data.transcoding.threads).to.equal(2)
|
expect(data.transcoding.threads).to.equal(2)
|
||||||
expect(data.transcoding.resolutions['240p']).to.be.true
|
expect(data.transcoding.resolutions['240p']).to.be.true
|
||||||
expect(data.transcoding.resolutions['360p']).to.be.true
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
|
@ -74,6 +76,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.user.videoQuotaDaily).to.equal(318742)
|
expect(data.user.videoQuotaDaily).to.equal(318742)
|
||||||
expect(data.transcoding.enabled).to.be.true
|
expect(data.transcoding.enabled).to.be.true
|
||||||
expect(data.transcoding.threads).to.equal(1)
|
expect(data.transcoding.threads).to.equal(1)
|
||||||
|
expect(data.transcoding.allowAdditionalExtensions).to.be.true
|
||||||
expect(data.transcoding.resolutions['240p']).to.be.false
|
expect(data.transcoding.resolutions['240p']).to.be.false
|
||||||
expect(data.transcoding.resolutions['360p']).to.be.true
|
expect(data.transcoding.resolutions['360p']).to.be.true
|
||||||
expect(data.transcoding.resolutions['480p']).to.be.true
|
expect(data.transcoding.resolutions['480p']).to.be.true
|
||||||
|
@ -96,7 +99,7 @@ describe('Test config', function () {
|
||||||
|
|
||||||
it('Should have a correct config on a server with registration enabled', async function () {
|
it('Should have a correct config on a server with registration enabled', async function () {
|
||||||
const res = await getConfig(server.url)
|
const res = await getConfig(server.url)
|
||||||
const data = res.body
|
const data: ServerConfig = res.body
|
||||||
|
|
||||||
expect(data.signup.allowed).to.be.true
|
expect(data.signup.allowed).to.be.true
|
||||||
})
|
})
|
||||||
|
@ -111,11 +114,21 @@ describe('Test config', function () {
|
||||||
])
|
])
|
||||||
|
|
||||||
const res = await getConfig(server.url)
|
const res = await getConfig(server.url)
|
||||||
const data = res.body
|
const data: ServerConfig = res.body
|
||||||
|
|
||||||
expect(data.signup.allowed).to.be.false
|
expect(data.signup.allowed).to.be.false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should have the correct video allowed extensions', async function () {
|
||||||
|
const res = await getConfig(server.url)
|
||||||
|
const data: ServerConfig = res.body
|
||||||
|
|
||||||
|
expect(data.video.file.extensions).to.have.lengthOf(3)
|
||||||
|
expect(data.video.file.extensions).to.contain('.mp4')
|
||||||
|
expect(data.video.file.extensions).to.contain('.webm')
|
||||||
|
expect(data.video.file.extensions).to.contain('.ogv')
|
||||||
|
})
|
||||||
|
|
||||||
it('Should get the customized configuration', async function () {
|
it('Should get the customized configuration', async function () {
|
||||||
const res = await getCustomConfig(server.url, server.accessToken)
|
const res = await getCustomConfig(server.url, server.accessToken)
|
||||||
const data = res.body as CustomConfig
|
const data = res.body as CustomConfig
|
||||||
|
@ -165,6 +178,7 @@ describe('Test config', function () {
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
allowAdditionalExtensions: true,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': false,
|
'240p': false,
|
||||||
|
@ -193,6 +207,18 @@ describe('Test config', function () {
|
||||||
checkUpdatedConfig(data)
|
checkUpdatedConfig(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should have the correct updated video allowed extensions', async function () {
|
||||||
|
const res = await getConfig(server.url)
|
||||||
|
const data: ServerConfig = res.body
|
||||||
|
|
||||||
|
expect(data.video.file.extensions).to.have.length.above(3)
|
||||||
|
expect(data.video.file.extensions).to.contain('.mp4')
|
||||||
|
expect(data.video.file.extensions).to.contain('.webm')
|
||||||
|
expect(data.video.file.extensions).to.contain('.ogv')
|
||||||
|
expect(data.video.file.extensions).to.contain('.flv')
|
||||||
|
expect(data.video.file.extensions).to.contain('.mkv')
|
||||||
|
})
|
||||||
|
|
||||||
it('Should have the configuration updated after a restart', async function () {
|
it('Should have the configuration updated after a restart', async function () {
|
||||||
this.timeout(10000)
|
this.timeout(10000)
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,8 @@ import {
|
||||||
uploadVideo,
|
uploadVideo,
|
||||||
webtorrentAdd
|
webtorrentAdd
|
||||||
} from '../../../../shared/utils'
|
} from '../../../../shared/utils'
|
||||||
import { join } from 'path'
|
import { extname, join } from 'path'
|
||||||
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
import { waitJobs } from '../../../../shared/utils/server/jobs'
|
||||||
import { pathExists } from 'fs-extra'
|
|
||||||
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
@ -322,6 +321,34 @@ describe('Test video transcoding', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should accept and transcode additional extensions', async function () {
|
||||||
|
this.timeout(300000)
|
||||||
|
|
||||||
|
for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
|
||||||
|
const videoAttributes = {
|
||||||
|
name: fixture,
|
||||||
|
fixture
|
||||||
|
}
|
||||||
|
|
||||||
|
await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes)
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
const res = await getVideosList(server.url)
|
||||||
|
|
||||||
|
const video = res.body.data.find(v => v.name === videoAttributes.name)
|
||||||
|
const res2 = await getVideo(server.url, video.id)
|
||||||
|
const videoDetails = res2.body
|
||||||
|
|
||||||
|
expect(videoDetails.files).to.have.lengthOf(4)
|
||||||
|
|
||||||
|
const magnetUri = videoDetails.files[ 0 ].magnetUri
|
||||||
|
expect(magnetUri).to.contain('.mp4')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
killallServers(servers)
|
killallServers(servers)
|
||||||
})
|
})
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -48,6 +48,7 @@ export interface CustomConfig {
|
||||||
|
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
allowAdditionalExtensions: boolean
|
||||||
threads: number
|
threads: number
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': boolean
|
'240p': boolean
|
||||||
|
|
|
@ -86,6 +86,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
allowAdditionalExtensions: true,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'240p': false,
|
'240p': false,
|
||||||
|
|
Loading…
Reference in New Issue