mirror of https://github.com/Chocobozzz/PeerTube
Allow to specify transcoding and import jobs concurrency
parent
81b46cbc34
commit
9129b7694d
|
@ -436,8 +436,25 @@
|
||||||
<div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
|
<div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
|
||||||
|
|
||||||
<ng-container formGroupName="import">
|
<ng-container formGroupName="import">
|
||||||
|
|
||||||
<ng-container formGroupName="videos">
|
<ng-container formGroupName="videos">
|
||||||
|
|
||||||
|
<div class="form-group mt-4">
|
||||||
|
<label i18n for="importConcurrency">Import jobs concurrency</label>
|
||||||
|
<span class="text-muted ml-1">
|
||||||
|
<span i18n>allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="importConcurrency" formControlName="concurrency" class="form-control">
|
||||||
|
<option *ngFor="let option of concurrencyOptions" [value]="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="formErrors.import.concurrency" class="form-error">{{ formErrors.import.concurrency }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group" formGroupName="http">
|
<div class="form-group" formGroupName="http">
|
||||||
<my-peertube-checkbox
|
<my-peertube-checkbox
|
||||||
inputName="importVideosHttpEnabled" formControlName="enabled"
|
inputName="importVideosHttpEnabled" formControlName="enabled"
|
||||||
|
@ -885,6 +902,22 @@
|
||||||
<div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div>
|
<div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mt-4" [ngClass]="{ 'disabled-checkbox-extra': !isTranscodingEnabled() }">
|
||||||
|
<label i18n for="transcodingConcurrency">Transcoding jobs concurrency</label>
|
||||||
|
<span class="text-muted ml-1">
|
||||||
|
<span i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart.</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="transcodingConcurrency" formControlName="concurrency" class="form-control">
|
||||||
|
<option *ngFor="let option of concurrencyOptions" [value]="option">
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group mt-4" [ngClass]="{ 'disabled-checkbox-extra': !isTranscodingEnabled() }">
|
<div class="form-group mt-4" [ngClass]="{ 'disabled-checkbox-extra': !isTranscodingEnabled() }">
|
||||||
<label i18n for="transcodingProfile">Transcoding profile</label>
|
<label i18n for="transcodingProfile">Transcoding profile</label>
|
||||||
<span class="text-muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
|
<span class="text-muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
ADMIN_EMAIL_VALIDATOR,
|
ADMIN_EMAIL_VALIDATOR,
|
||||||
CACHE_CAPTIONS_SIZE_VALIDATOR,
|
CACHE_CAPTIONS_SIZE_VALIDATOR,
|
||||||
CACHE_PREVIEWS_SIZE_VALIDATOR,
|
CACHE_PREVIEWS_SIZE_VALIDATOR,
|
||||||
|
CONCURRENCY_VALIDATOR,
|
||||||
INDEX_URL_VALIDATOR,
|
INDEX_URL_VALIDATOR,
|
||||||
INSTANCE_NAME_VALIDATOR,
|
INSTANCE_NAME_VALIDATOR,
|
||||||
INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
|
INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
|
||||||
|
@ -36,6 +37,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
|
||||||
|
|
||||||
resolutions: { id: string, label: string, description?: string }[] = []
|
resolutions: { id: string, label: string, description?: string }[] = []
|
||||||
liveResolutions: { id: string, label: string, description?: string }[] = []
|
liveResolutions: { id: string, label: string, description?: string }[] = []
|
||||||
|
concurrencyOptions: number[] = []
|
||||||
transcodingThreadOptions: { label: string, value: number }[] = []
|
transcodingThreadOptions: { label: string, value: number }[] = []
|
||||||
liveMaxDurationOptions: { label: string, value: number }[] = []
|
liveMaxDurationOptions: { label: string, value: number }[] = []
|
||||||
|
|
||||||
|
@ -103,6 +105,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
|
||||||
{ value: 4, label: '4' },
|
{ value: 4, label: '4' },
|
||||||
{ value: 8, label: '8' }
|
{ value: 8, label: '8' }
|
||||||
]
|
]
|
||||||
|
this.concurrencyOptions = [ 1, 2, 3, 4, 5, 6 ]
|
||||||
|
|
||||||
this.vodTranscodingProfileOptions = [ 'default' ]
|
this.vodTranscodingProfileOptions = [ 'default' ]
|
||||||
this.liveTranscodingProfileOptions = [ 'default' ]
|
this.liveTranscodingProfileOptions = [ 'default' ]
|
||||||
|
@ -230,6 +233,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: CONCURRENCY_VALIDATOR,
|
||||||
http: {
|
http: {
|
||||||
enabled: null
|
enabled: null
|
||||||
},
|
},
|
||||||
|
@ -262,6 +266,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
|
||||||
allowAdditionalExtensions: null,
|
allowAdditionalExtensions: null,
|
||||||
allowAudioFiles: null,
|
allowAudioFiles: null,
|
||||||
profile: null,
|
profile: null,
|
||||||
|
concurrency: CONCURRENCY_VALIDATOR,
|
||||||
resolutions: {},
|
resolutions: {},
|
||||||
hls: {
|
hls: {
|
||||||
enabled: null
|
enabled: null
|
||||||
|
|
|
@ -65,6 +65,14 @@ export const TRANSCODING_THREADS_VALIDATOR: BuildFormValidator = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CONCURRENCY_VALIDATOR: BuildFormValidator = {
|
||||||
|
VALIDATORS: [Validators.required, Validators.min(1)],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': $localize`Concurrency is required.`,
|
||||||
|
'min': $localize`Concurrency should be greater or equal to 1.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const INDEX_URL_VALIDATOR: BuildFormValidator = {
|
export const INDEX_URL_VALIDATOR: BuildFormValidator = {
|
||||||
VALIDATORS: [Validators.pattern(/^https:\/\//)],
|
VALIDATORS: [Validators.pattern(/^https:\/\//)],
|
||||||
MESSAGES: {
|
MESSAGES: {
|
||||||
|
|
|
@ -233,7 +233,10 @@ transcoding:
|
||||||
# If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
|
# 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
|
allow_audio_files: true
|
||||||
|
|
||||||
|
# Amount of threads used by ffmpeg for 1 transcoding job
|
||||||
threads: 1
|
threads: 1
|
||||||
|
# Amount of transcoding jobs to execute in parallel
|
||||||
|
concurrency: 1
|
||||||
|
|
||||||
# Choose the transcoding profile
|
# Choose the transcoding profile
|
||||||
# New profiles can be added by plugins
|
# New profiles can be added by plugins
|
||||||
|
@ -312,6 +315,9 @@ live:
|
||||||
import:
|
import:
|
||||||
# Add ability for your users to import remote videos (from YouTube, torrent...)
|
# Add ability for your users to import remote videos (from YouTube, torrent...)
|
||||||
videos:
|
videos:
|
||||||
|
# Amount of import jobs to execute in parallel
|
||||||
|
concurrency: 1
|
||||||
|
|
||||||
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
|
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
|
|
@ -244,7 +244,10 @@ transcoding:
|
||||||
# If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
|
# 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
|
allow_audio_files: true
|
||||||
|
|
||||||
|
# Amount of threads used by ffmpeg for 1 transcoding job
|
||||||
threads: 1
|
threads: 1
|
||||||
|
# Amount of transcoding jobs to execute in parallel
|
||||||
|
concurrency: 1
|
||||||
|
|
||||||
# Choose the transcoding profile
|
# Choose the transcoding profile
|
||||||
# New profiles can be added by plugins
|
# New profiles can be added by plugins
|
||||||
|
@ -323,6 +326,9 @@ live:
|
||||||
import:
|
import:
|
||||||
# Add ability for your users to import remote videos (from YouTube, torrent...)
|
# Add ability for your users to import remote videos (from YouTube, torrent...)
|
||||||
videos:
|
videos:
|
||||||
|
# Amount of import jobs to execute in parallel
|
||||||
|
concurrency: 1
|
||||||
|
|
||||||
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
|
http: # Classic HTTP or all sites supported by youtube-dl https://rg3.github.io/youtube-dl/supportedsites.html
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,7 @@ transcoding:
|
||||||
allow_additional_extensions: false
|
allow_additional_extensions: false
|
||||||
allow_audio_files: false
|
allow_audio_files: false
|
||||||
threads: 2
|
threads: 2
|
||||||
|
concurrency: 2
|
||||||
resolutions:
|
resolutions:
|
||||||
0p: false
|
0p: false
|
||||||
240p: true
|
240p: true
|
||||||
|
@ -106,6 +107,7 @@ live:
|
||||||
|
|
||||||
import:
|
import:
|
||||||
videos:
|
videos:
|
||||||
|
concurrency: 2
|
||||||
http:
|
http:
|
||||||
enabled: true
|
enabled: true
|
||||||
proxy:
|
proxy:
|
||||||
|
|
|
@ -53,7 +53,8 @@ async function run () {
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode: false,
|
isPortraitMode: false,
|
||||||
copyCodecs: false
|
copyCodecs: false,
|
||||||
|
isMaxQuality: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (options.resolution !== undefined) {
|
} else if (options.resolution !== undefined) {
|
||||||
|
|
|
@ -417,6 +417,7 @@ function customConfig (): CustomConfig {
|
||||||
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
|
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
|
||||||
allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
|
allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
|
||||||
threads: CONFIG.TRANSCODING.THREADS,
|
threads: CONFIG.TRANSCODING.THREADS,
|
||||||
|
concurrency: CONFIG.TRANSCODING.CONCURRENCY,
|
||||||
profile: CONFIG.TRANSCODING.PROFILE,
|
profile: CONFIG.TRANSCODING.PROFILE,
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
|
'0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
|
||||||
|
@ -458,6 +459,7 @@ function customConfig (): CustomConfig {
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: CONFIG.IMPORT.VIDEOS.CONCURRENCY,
|
||||||
http: {
|
http: {
|
||||||
enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
|
enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
|
||||||
},
|
},
|
||||||
|
|
|
@ -116,6 +116,16 @@ function checkConfig () {
|
||||||
if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
|
if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
|
||||||
return 'You need to enable at least WebTorrent transcoding or HLS transcoding.'
|
return 'You need to enable at least WebTorrent transcoding or HLS transcoding.'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CONFIG.TRANSCODING.CONCURRENCY <= 0) {
|
||||||
|
return 'Transcoding concurrency should be > 0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CONFIG.IMPORT.VIDEOS.HTTP.ENABLED || CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED) {
|
||||||
|
if (CONFIG.IMPORT.VIDEOS.CONCURRENCY <= 0) {
|
||||||
|
return 'Video import concurrency should be > 0'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast message
|
// Broadcast message
|
||||||
|
|
|
@ -22,10 +22,10 @@ function checkMissedConfig () {
|
||||||
'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.allow_additional_extensions', 'transcoding.hls.enabled',
|
'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 'transcoding.hls.enabled',
|
||||||
'transcoding.profile',
|
'transcoding.profile', 'transcoding.concurrency',
|
||||||
'transcoding.resolutions.0p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', 'transcoding.resolutions.480p',
|
'transcoding.resolutions.0p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', 'transcoding.resolutions.480p',
|
||||||
'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', 'transcoding.resolutions.2160p',
|
'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p', 'transcoding.resolutions.2160p',
|
||||||
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled',
|
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'auto_blacklist.videos.of_users.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',
|
||||||
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
|
'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
|
||||||
|
|
|
@ -188,6 +188,7 @@ const CONFIG = {
|
||||||
get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
|
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 ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
|
||||||
get THREADS () { return config.get<number>('transcoding.threads') },
|
get THREADS () { return config.get<number>('transcoding.threads') },
|
||||||
|
get CONCURRENCY () { return config.get<number>('transcoding.concurrency') },
|
||||||
get PROFILE () { return config.get<string>('transcoding.profile') },
|
get PROFILE () { return config.get<string>('transcoding.profile') },
|
||||||
RESOLUTIONS: {
|
RESOLUTIONS: {
|
||||||
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
||||||
|
@ -237,6 +238,8 @@ const CONFIG = {
|
||||||
},
|
},
|
||||||
IMPORT: {
|
IMPORT: {
|
||||||
VIDEOS: {
|
VIDEOS: {
|
||||||
|
get CONCURRENCY () { return config.get<number>('import.videos.concurrency') },
|
||||||
|
|
||||||
HTTP: {
|
HTTP: {
|
||||||
get ENABLED () { return config.get<boolean>('import.videos.http.enabled') },
|
get ENABLED () { return config.get<boolean>('import.videos.http.enabled') },
|
||||||
get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') },
|
get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') },
|
||||||
|
|
|
@ -146,14 +146,12 @@ const JOB_ATTEMPTS: { [id in JobType]: number } = {
|
||||||
'video-redundancy': 1,
|
'video-redundancy': 1,
|
||||||
'video-live-ending': 1
|
'video-live-ending': 1
|
||||||
}
|
}
|
||||||
const JOB_CONCURRENCY: { [id in JobType]: number } = {
|
const JOB_CONCURRENCY: { [id in JobType]?: number } = {
|
||||||
'activitypub-http-broadcast': 1,
|
'activitypub-http-broadcast': 1,
|
||||||
'activitypub-http-unicast': 5,
|
'activitypub-http-unicast': 5,
|
||||||
'activitypub-http-fetcher': 1,
|
'activitypub-http-fetcher': 1,
|
||||||
'activitypub-follow': 1,
|
'activitypub-follow': 1,
|
||||||
'video-file-import': 1,
|
'video-file-import': 1,
|
||||||
'video-transcoding': 1,
|
|
||||||
'video-import': 1,
|
|
||||||
'email': 5,
|
'email': 5,
|
||||||
'videos-views': 1,
|
'videos-views': 1,
|
||||||
'activitypub-refresher': 1,
|
'activitypub-refresher': 1,
|
||||||
|
|
|
@ -76,7 +76,7 @@ async function processVideoTranscoding (job: Bull.Job) {
|
||||||
// Job handlers
|
// Job handlers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, video: MVideoFullLight) {
|
async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, video: MVideoFullLight, user: MUser) {
|
||||||
const videoFileInput = payload.copyCodecs
|
const videoFileInput = payload.copyCodecs
|
||||||
? video.getWebTorrentFile(payload.resolution)
|
? video.getWebTorrentFile(payload.resolution)
|
||||||
: video.getMaxQualityFile()
|
: video.getMaxQualityFile()
|
||||||
|
@ -93,7 +93,7 @@ async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, vide
|
||||||
job
|
job
|
||||||
})
|
})
|
||||||
|
|
||||||
await retryTransactionWrapper(onHlsPlaylistGeneration, video, payload.resolution)
|
await retryTransactionWrapper(onHlsPlaylistGeneration, video, user, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleNewWebTorrentResolutionJob (
|
async function handleNewWebTorrentResolutionJob (
|
||||||
|
@ -110,9 +110,7 @@ async function handleNewWebTorrentResolutionJob (
|
||||||
async function handleWebTorrentMergeAudioJob (job: Bull.Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
async function handleWebTorrentMergeAudioJob (job: Bull.Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
||||||
await mergeAudioVideofile(video, payload.resolution, job)
|
await mergeAudioVideofile(video, payload.resolution, job)
|
||||||
|
|
||||||
await retryTransactionWrapper(onNewWebTorrentFileResolution, video, user, payload)
|
await retryTransactionWrapper(onVideoFileOptimizer, video, payload, 'video', user)
|
||||||
|
|
||||||
await createLowerResolutionsJobs(video, user, payload.resolution, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
||||||
|
@ -123,13 +121,11 @@ async function handleWebTorrentOptimizeJob (job: Bull.Job, payload: OptimizeTran
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function onHlsPlaylistGeneration (video: MVideoFullLight, resolution: number) {
|
async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, payload: HLSTranscodingPayload) {
|
||||||
if (video === undefined) return undefined
|
if (video === undefined) return undefined
|
||||||
|
|
||||||
const maxQualityFile = video.getMaxQualityFile()
|
if (payload.isMaxQuality && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
|
||||||
|
// Remove webtorrent files if not enabled
|
||||||
// We generated the max quality HLS playlist, we don't need the webtorrent files anymore if the admin disabled it
|
|
||||||
if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && video.hasWebTorrentFiles() && maxQualityFile.resolution === resolution) {
|
|
||||||
for (const file of video.VideoFiles) {
|
for (const file of video.VideoFiles) {
|
||||||
await video.removeFile(file)
|
await video.removeFile(file)
|
||||||
await video.removeTorrent(file)
|
await video.removeTorrent(file)
|
||||||
|
@ -137,6 +133,9 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, resolution: numb
|
||||||
}
|
}
|
||||||
|
|
||||||
video.VideoFiles = []
|
video.VideoFiles = []
|
||||||
|
|
||||||
|
// Create HLS new resolution jobs
|
||||||
|
await createLowerResolutionsJobs(video, user, payload.resolution, payload.isPortraitMode, 'hls')
|
||||||
}
|
}
|
||||||
|
|
||||||
return publishAndFederateIfNeeded(video)
|
return publishAndFederateIfNeeded(video)
|
||||||
|
@ -144,7 +143,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, resolution: numb
|
||||||
|
|
||||||
async function onVideoFileOptimizer (
|
async function onVideoFileOptimizer (
|
||||||
videoArg: MVideoWithFile,
|
videoArg: MVideoWithFile,
|
||||||
payload: OptimizeTranscodingPayload,
|
payload: OptimizeTranscodingPayload | MergeAudioTranscodingPayload,
|
||||||
transcodeType: TranscodeOptionsType,
|
transcodeType: TranscodeOptionsType,
|
||||||
user: MUserId
|
user: MUserId
|
||||||
) {
|
) {
|
||||||
|
@ -166,11 +165,12 @@ async function onVideoFileOptimizer (
|
||||||
isPortraitMode,
|
isPortraitMode,
|
||||||
resolution: videoDatabase.getMaxQualityFile().resolution,
|
resolution: videoDatabase.getMaxQualityFile().resolution,
|
||||||
// If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues
|
// If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues
|
||||||
copyCodecs: transcodeType !== 'quick-transcode'
|
copyCodecs: transcodeType !== 'quick-transcode',
|
||||||
|
isMaxQuality: true
|
||||||
})
|
})
|
||||||
await createHlsJobIfEnabled(user, originalFileHLSPayload)
|
await createHlsJobIfEnabled(user, originalFileHLSPayload)
|
||||||
|
|
||||||
const hasNewResolutions = createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode)
|
const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode, 'webtorrent')
|
||||||
|
|
||||||
if (!hasNewResolutions) {
|
if (!hasNewResolutions) {
|
||||||
// No transcoding to do, it's now published
|
// No transcoding to do, it's now published
|
||||||
|
@ -193,7 +193,7 @@ async function onNewWebTorrentFileResolution (
|
||||||
) {
|
) {
|
||||||
await publishAndFederateIfNeeded(video)
|
await publishAndFederateIfNeeded(video)
|
||||||
|
|
||||||
await createHlsJobIfEnabled(user, Object.assign({}, payload, { copyCodecs: true }))
|
await createHlsJobIfEnabled(user, Object.assign({}, payload, { copyCodecs: true, isMaxQuality: false }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -210,6 +210,7 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
||||||
resolution: number
|
resolution: number
|
||||||
isPortraitMode?: boolean
|
isPortraitMode?: boolean
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
|
isMaxQuality: boolean
|
||||||
}) {
|
}) {
|
||||||
if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return
|
if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return
|
||||||
|
|
||||||
|
@ -222,7 +223,8 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
||||||
videoUUID: payload.videoUUID,
|
videoUUID: payload.videoUUID,
|
||||||
resolution: payload.resolution,
|
resolution: payload.resolution,
|
||||||
isPortraitMode: payload.isPortraitMode,
|
isPortraitMode: payload.isPortraitMode,
|
||||||
copyCodecs: payload.copyCodecs
|
copyCodecs: payload.copyCodecs,
|
||||||
|
isMaxQuality: payload.isMaxQuality
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: hlsTranscodingPayload }, jobOptions)
|
return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: hlsTranscodingPayload }, jobOptions)
|
||||||
|
@ -232,7 +234,8 @@ async function createLowerResolutionsJobs (
|
||||||
video: MVideoFullLight,
|
video: MVideoFullLight,
|
||||||
user: MUserId,
|
user: MUserId,
|
||||||
videoFileResolution: number,
|
videoFileResolution: number,
|
||||||
isPortraitMode: boolean
|
isPortraitMode: boolean,
|
||||||
|
type: 'hls' | 'webtorrent'
|
||||||
) {
|
) {
|
||||||
// Create transcoding jobs if there are enabled resolutions
|
// Create transcoding jobs if there are enabled resolutions
|
||||||
const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod')
|
const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod')
|
||||||
|
@ -250,7 +253,7 @@ async function createLowerResolutionsJobs (
|
||||||
for (const resolution of resolutionsEnabled) {
|
for (const resolution of resolutionsEnabled) {
|
||||||
let dataInput: VideoTranscodingPayload
|
let dataInput: VideoTranscodingPayload
|
||||||
|
|
||||||
if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED) {
|
if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') {
|
||||||
// WebTorrent will create subsequent HLS job
|
// WebTorrent will create subsequent HLS job
|
||||||
dataInput = {
|
dataInput = {
|
||||||
type: 'new-resolution-to-webtorrent',
|
type: 'new-resolution-to-webtorrent',
|
||||||
|
@ -258,13 +261,16 @@ async function createLowerResolutionsJobs (
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode
|
isPortraitMode
|
||||||
}
|
}
|
||||||
} else if (CONFIG.TRANSCODING.HLS.ENABLED) {
|
}
|
||||||
|
|
||||||
|
if (CONFIG.TRANSCODING.HLS.ENABLED && type === 'hls') {
|
||||||
dataInput = {
|
dataInput = {
|
||||||
type: 'new-resolution-to-hls',
|
type: 'new-resolution-to-hls',
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode,
|
isPortraitMode,
|
||||||
copyCodecs: false
|
copyCodecs: false,
|
||||||
|
isMaxQuality: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as Bull from 'bull'
|
import * as Bull from 'bull'
|
||||||
import { jobStates } from '@server/helpers/custom-validators/jobs'
|
import { jobStates } from '@server/helpers/custom-validators/jobs'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { processVideoRedundancy } from '@server/lib/job-queue/handlers/video-redundancy'
|
import { processVideoRedundancy } from '@server/lib/job-queue/handlers/video-redundancy'
|
||||||
import {
|
import {
|
||||||
ActivitypubFollowPayload,
|
ActivitypubFollowPayload,
|
||||||
|
@ -105,11 +106,11 @@ class JobQueue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const handlerName of Object.keys(handlers)) {
|
for (const handlerName of (Object.keys(handlers) as JobType[])) {
|
||||||
const queue = new Bull(handlerName, queueOptions)
|
const queue = new Bull(handlerName, queueOptions)
|
||||||
const handler = handlers[handlerName]
|
const handler = handlers[handlerName]
|
||||||
|
|
||||||
queue.process(JOB_CONCURRENCY[handlerName], handler)
|
queue.process(this.getJobConcurrency(handlerName), handler)
|
||||||
.catch(err => logger.error('Error in job queue processor %s.', handlerName, { err }))
|
.catch(err => logger.error('Error in job queue processor %s.', handlerName, { err }))
|
||||||
|
|
||||||
queue.on('failed', (job, err) => {
|
queue.on('failed', (job, err) => {
|
||||||
|
@ -235,6 +236,13 @@ class JobQueue {
|
||||||
return jobTypes.filter(t => t === jobType)
|
return jobTypes.filter(t => t === jobType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getJobConcurrency (jobType: JobType) {
|
||||||
|
if (jobType === 'video-transcoding') return CONFIG.TRANSCODING.CONCURRENCY
|
||||||
|
if (jobType === 'video-import') return CONFIG.IMPORT.VIDEOS.CONCURRENCY
|
||||||
|
|
||||||
|
return JOB_CONCURRENCY[jobType]
|
||||||
|
}
|
||||||
|
|
||||||
static get Instance () {
|
static get Instance () {
|
||||||
return this.instance || (this.instance = new this())
|
return this.instance || (this.instance = new this())
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,7 +272,7 @@ async function generateHlsPlaylistCommon (options: {
|
||||||
const { type, video, inputPath, resolution, copyCodecs, isPortraitMode, isAAC, job } = options
|
const { type, video, inputPath, resolution, copyCodecs, isPortraitMode, isAAC, job } = options
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
|
|
||||||
const videoTranscodedBasePath = join(transcodeDirectory, type, video.uuid)
|
const videoTranscodedBasePath = join(transcodeDirectory, type)
|
||||||
await ensureDir(videoTranscodedBasePath)
|
await ensureDir(videoTranscodedBasePath)
|
||||||
|
|
||||||
const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution)
|
const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution)
|
||||||
|
@ -337,8 +337,6 @@ async function generateHlsPlaylistCommon (options: {
|
||||||
await move(playlistFileTranscodePath, playlistPath, { overwrite: true })
|
await move(playlistFileTranscodePath, playlistPath, { overwrite: true })
|
||||||
// Move video file
|
// Move video file
|
||||||
await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
|
await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
|
||||||
// Cleanup directory
|
|
||||||
await remove(videoTranscodedBasePath)
|
|
||||||
|
|
||||||
const stats = await stat(videoFilePath)
|
const stats = await stat(videoFilePath)
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ const customConfigUpdateValidator = [
|
||||||
body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
|
body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
|
||||||
body('transcoding.allowAdditionalExtensions').isBoolean().withMessage('Should have a valid additional extensions boolean'),
|
body('transcoding.allowAdditionalExtensions').isBoolean().withMessage('Should have a valid additional extensions boolean'),
|
||||||
body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
|
body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
|
||||||
|
body('transcoding.concurrency').isInt({ min: 1 }).withMessage('Should have a valid transcoding concurrency number'),
|
||||||
body('transcoding.resolutions.0p').isBoolean().withMessage('Should have a valid transcoding 0p resolution enabled boolean'),
|
body('transcoding.resolutions.0p').isBoolean().withMessage('Should have a valid transcoding 0p resolution enabled boolean'),
|
||||||
body('transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'),
|
body('transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'),
|
||||||
body('transcoding.resolutions.360p').isBoolean().withMessage('Should have a valid transcoding 360p resolution enabled boolean'),
|
body('transcoding.resolutions.360p').isBoolean().withMessage('Should have a valid transcoding 360p resolution enabled boolean'),
|
||||||
|
@ -51,6 +52,7 @@ const customConfigUpdateValidator = [
|
||||||
body('transcoding.webtorrent.enabled').isBoolean().withMessage('Should have a valid webtorrent transcoding enabled boolean'),
|
body('transcoding.webtorrent.enabled').isBoolean().withMessage('Should have a valid webtorrent transcoding enabled boolean'),
|
||||||
body('transcoding.hls.enabled').isBoolean().withMessage('Should have a valid hls transcoding enabled boolean'),
|
body('transcoding.hls.enabled').isBoolean().withMessage('Should have a valid hls transcoding enabled boolean'),
|
||||||
|
|
||||||
|
body('import.videos.concurrency').isInt({ min: 0 }).withMessage('Should have a valid import concurrency number'),
|
||||||
body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'),
|
body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'),
|
||||||
body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'),
|
body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'),
|
||||||
|
|
||||||
|
|
|
@ -345,7 +345,6 @@ export type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: VideoFileModel,
|
model: VideoFileModel,
|
||||||
separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
|
|
||||||
required: false,
|
required: false,
|
||||||
include: subInclude
|
include: subInclude
|
||||||
}
|
}
|
||||||
|
@ -372,7 +371,6 @@ export type AvailableForListIDsOptions = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
model: VideoStreamingPlaylistModel.unscoped(),
|
model: VideoStreamingPlaylistModel.unscoped(),
|
||||||
separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
|
|
||||||
required: false,
|
required: false,
|
||||||
include: subInclude
|
include: subInclude
|
||||||
}
|
}
|
||||||
|
@ -1689,7 +1687,7 @@ export class VideoModel extends Model {
|
||||||
|
|
||||||
channelModel.Account = accountModel
|
channelModel.Account = accountModel
|
||||||
|
|
||||||
const videoModel = new VideoModel(pick(row, videoKeys))
|
const videoModel = new VideoModel(pick(row, videoKeys), buildOpts)
|
||||||
videoModel.VideoChannel = channelModel
|
videoModel.VideoChannel = channelModel
|
||||||
|
|
||||||
videoModel.UserVideoHistories = []
|
videoModel.UserVideoHistories = []
|
||||||
|
|
|
@ -86,6 +86,7 @@ describe('Test config API validators', function () {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
allowAudioFiles: true,
|
allowAudioFiles: true,
|
||||||
|
concurrency: 1,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
profile: 'vod_profile',
|
profile: 'vod_profile',
|
||||||
resolutions: {
|
resolutions: {
|
||||||
|
@ -130,6 +131,7 @@ describe('Test config API validators', function () {
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: 1,
|
||||||
http: {
|
http: {
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,6 +70,7 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
|
||||||
expect(data.transcoding.allowAdditionalExtensions).to.be.false
|
expect(data.transcoding.allowAdditionalExtensions).to.be.false
|
||||||
expect(data.transcoding.allowAudioFiles).to.be.false
|
expect(data.transcoding.allowAudioFiles).to.be.false
|
||||||
expect(data.transcoding.threads).to.equal(2)
|
expect(data.transcoding.threads).to.equal(2)
|
||||||
|
expect(data.transcoding.concurrency).to.equal(2)
|
||||||
expect(data.transcoding.profile).to.equal('default')
|
expect(data.transcoding.profile).to.equal('default')
|
||||||
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
|
||||||
|
@ -97,6 +98,7 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
|
||||||
expect(data.live.transcoding.resolutions['1440p']).to.be.false
|
expect(data.live.transcoding.resolutions['1440p']).to.be.false
|
||||||
expect(data.live.transcoding.resolutions['2160p']).to.be.false
|
expect(data.live.transcoding.resolutions['2160p']).to.be.false
|
||||||
|
|
||||||
|
expect(data.import.videos.concurrency).to.equal(2)
|
||||||
expect(data.import.videos.http.enabled).to.be.true
|
expect(data.import.videos.http.enabled).to.be.true
|
||||||
expect(data.import.videos.torrent.enabled).to.be.true
|
expect(data.import.videos.torrent.enabled).to.be.true
|
||||||
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false
|
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false
|
||||||
|
@ -159,6 +161,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
|
|
||||||
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.concurrency).to.equal(3)
|
||||||
expect(data.transcoding.allowAdditionalExtensions).to.be.true
|
expect(data.transcoding.allowAdditionalExtensions).to.be.true
|
||||||
expect(data.transcoding.allowAudioFiles).to.be.true
|
expect(data.transcoding.allowAudioFiles).to.be.true
|
||||||
expect(data.transcoding.profile).to.equal('vod_profile')
|
expect(data.transcoding.profile).to.equal('vod_profile')
|
||||||
|
@ -186,6 +189,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.live.transcoding.resolutions['1080p']).to.be.true
|
expect(data.live.transcoding.resolutions['1080p']).to.be.true
|
||||||
expect(data.live.transcoding.resolutions['2160p']).to.be.true
|
expect(data.live.transcoding.resolutions['2160p']).to.be.true
|
||||||
|
|
||||||
|
expect(data.import.videos.concurrency).to.equal(4)
|
||||||
expect(data.import.videos.http.enabled).to.be.false
|
expect(data.import.videos.http.enabled).to.be.false
|
||||||
expect(data.import.videos.torrent.enabled).to.be.false
|
expect(data.import.videos.torrent.enabled).to.be.false
|
||||||
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true
|
expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true
|
||||||
|
@ -323,6 +327,7 @@ describe('Test config', function () {
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
allowAudioFiles: true,
|
allowAudioFiles: true,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
|
concurrency: 3,
|
||||||
profile: 'vod_profile',
|
profile: 'vod_profile',
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'0p': false,
|
'0p': false,
|
||||||
|
@ -364,6 +369,7 @@ describe('Test config', function () {
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: 4,
|
||||||
http: {
|
http: {
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
|
|
@ -112,6 +112,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
allowAudioFiles: true,
|
allowAudioFiles: true,
|
||||||
threads: 1,
|
threads: 1,
|
||||||
|
concurrency: 3,
|
||||||
profile: 'default',
|
profile: 'default',
|
||||||
resolutions: {
|
resolutions: {
|
||||||
'0p': false,
|
'0p': false,
|
||||||
|
@ -153,6 +154,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: 3,
|
||||||
http: {
|
http: {
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
|
|
@ -87,6 +87,7 @@ export interface CustomConfig {
|
||||||
allowAudioFiles: boolean
|
allowAudioFiles: boolean
|
||||||
|
|
||||||
threads: number
|
threads: number
|
||||||
|
concurrency: number
|
||||||
|
|
||||||
profile: string
|
profile: string
|
||||||
|
|
||||||
|
@ -120,6 +121,8 @@ export interface CustomConfig {
|
||||||
|
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
concurrency: number
|
||||||
|
|
||||||
http: {
|
http: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ export interface HLSTranscodingPayload extends BaseTranscodingPayload {
|
||||||
isPortraitMode?: boolean
|
isPortraitMode?: boolean
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
|
isMaxQuality: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
|
export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
|
||||||
|
|
Loading…
Reference in New Issue