]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Allow to specify transcoding and import jobs concurrency
authorChocobozzz <me@florianbigard.com>
Mon, 8 Feb 2021 09:51:10 +0000 (10:51 +0100)
committerChocobozzz <chocobozzz@cpy.re>
Mon, 8 Feb 2021 14:38:45 +0000 (15:38 +0100)
22 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/shared/form-validators/custom-config-validators.ts
config/default.yaml
config/production.yaml.example
config/test.yaml
scripts/create-transcoding-job.ts
server/controllers/api/config.ts
server/initializers/checker-after-init.ts
server/initializers/checker-before-init.ts
server/initializers/config.ts
server/initializers/constants.ts
server/lib/job-queue/handlers/video-transcoding.ts
server/lib/job-queue/job-queue.ts
server/lib/video-transcoding.ts
server/middlewares/validators/config.ts
server/models/video/video.ts
server/tests/api/check-params/config.ts
server/tests/api/server/config.ts
shared/extra-utils/server/config.ts
shared/models/server/custom-config.model.ts
shared/models/server/job.model.ts

index 48678a194dfbb73096376d17b433fb83def103c2..5f0a5ff6c1659bf0aeef8b0f12b2949fbc0ea135 100644 (file)
           <div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
 
             <ng-container formGroupName="import">
+
               <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">
                   <my-peertube-checkbox
                     inputName="importVideosHttpEnabled" formControlName="enabled"
                 <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</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() }">
                 <label i18n for="transcodingProfile">Transcoding profile</label>
                 <span class="text-muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
index ae6a9e844f63e97a502f1c869212d1224c7dc1da..48fb86968b6eeaecf8a5345ca8ca2998cbfe7ef4 100644 (file)
@@ -9,6 +9,7 @@ import {
   ADMIN_EMAIL_VALIDATOR,
   CACHE_CAPTIONS_SIZE_VALIDATOR,
   CACHE_PREVIEWS_SIZE_VALIDATOR,
+  CONCURRENCY_VALIDATOR,
   INDEX_URL_VALIDATOR,
   INSTANCE_NAME_VALIDATOR,
   INSTANCE_SHORT_DESCRIPTION_VALIDATOR,
@@ -36,6 +37,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
 
   resolutions: { id: string, label: string, description?: string }[] = []
   liveResolutions: { id: string, label: string, description?: string }[] = []
+  concurrencyOptions: number[] = []
   transcodingThreadOptions: { 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: 8, label: '8' }
     ]
+    this.concurrencyOptions = [ 1, 2, 3, 4, 5, 6 ]
 
     this.vodTranscodingProfileOptions = [ 'default' ]
     this.liveTranscodingProfileOptions = [ 'default' ]
@@ -230,6 +233,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
       },
       import: {
         videos: {
+          concurrency: CONCURRENCY_VALIDATOR,
           http: {
             enabled: null
           },
@@ -262,6 +266,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
         allowAdditionalExtensions: null,
         allowAudioFiles: null,
         profile: null,
+        concurrency: CONCURRENCY_VALIDATOR,
         resolutions: {},
         hls: {
           enabled: null
index 41b3cbba9a6e9717c0dfdbb826739b24f19db4ac..23f2156c2586896465c0106c25942e93c15b31f7 100644 (file)
@@ -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 = {
   VALIDATORS: [Validators.pattern(/^https:\/\//)],
   MESSAGES: {
index 95df2e06c4c7614e5042cf76841513f77257996a..3bbb3e5c44f018887393d6738aba35a9383e549e 100644 (file)
@@ -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
   allow_audio_files: true
 
+  # Amount of threads used by ffmpeg for 1 transcoding job
   threads: 1
+  # Amount of transcoding jobs to execute in parallel
+  concurrency: 1
 
   # Choose the transcoding profile
   # New profiles can be added by plugins
@@ -312,6 +315,9 @@ live:
 import:
   # Add ability for your users to import remote videos (from YouTube, torrent...)
   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
       enabled: false
 
index 13a6469187e6dce535fb00459d397d792aab8a75..d75e302760795ceb900abdd33b49f968231c2555 100644 (file)
@@ -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
   allow_audio_files: true
 
+  # Amount of threads used by ffmpeg for 1 transcoding job
   threads: 1
+  # Amount of transcoding jobs to execute in parallel
+  concurrency: 1
 
   # Choose the transcoding profile
   # New profiles can be added by plugins
@@ -323,6 +326,9 @@ live:
 import:
   # Add ability for your users to import remote videos (from YouTube, torrent...)
   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
       enabled: false
 
index 6fedb1dc33e26248faed39d6f0bcb7f4b934b566..ae8011ba5387f91858dde2db269d68648df8bedc 100644 (file)
@@ -71,6 +71,7 @@ transcoding:
   allow_additional_extensions: false
   allow_audio_files: false
   threads: 2
+  concurrency: 2
   resolutions:
     0p: false
     240p: true
@@ -106,6 +107,7 @@ live:
 
 import:
   videos:
+    concurrency: 2
     http:
       enabled: true
       proxy:
index eb620aecac03a87b0bfa9b65bdbc15f7f8b40955..5f56d6d3684039e7be7e65c6691ac3670918e423 100755 (executable)
@@ -53,7 +53,8 @@ async function run () {
         videoUUID: video.uuid,
         resolution,
         isPortraitMode: false,
-        copyCodecs: false
+        copyCodecs: false,
+        isMaxQuality: false
       })
     }
   } else if (options.resolution !== undefined) {
index 7fda06a87329f565fcb06f8e5a5b3ad286af0f22..5c242da04a55708bed6e816319615d8449cb21b9 100644 (file)
@@ -417,6 +417,7 @@ function customConfig (): CustomConfig {
       allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
       allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
       threads: CONFIG.TRANSCODING.THREADS,
+      concurrency: CONFIG.TRANSCODING.CONCURRENCY,
       profile: CONFIG.TRANSCODING.PROFILE,
       resolutions: {
         '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
@@ -458,6 +459,7 @@ function customConfig (): CustomConfig {
     },
     import: {
       videos: {
+        concurrency: CONFIG.IMPORT.VIDEOS.CONCURRENCY,
         http: {
           enabled: CONFIG.IMPORT.VIDEOS.HTTP.ENABLED
         },
index 979c97a8b2743024bfe06e36e1c01b0fb2b34b07..2b00e2047e9770c57f8c896b59df94f0d6fe035b 100644 (file)
@@ -116,6 +116,16 @@ function checkConfig () {
     if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false && CONFIG.TRANSCODING.HLS.ENABLED === false) {
       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
index cac24f0b664f22bde5e1ed8552d84e72151771d4..a186afbdd9efcdad8bd1e264e131a927f7f33802 100644 (file)
@@ -22,10 +22,10 @@ function checkMissedConfig () {
     'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
     'redundancy.videos.strategies', 'redundancy.videos.check_interval',
     '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.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',
     'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
     'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
index 3b44d82ede46156473d2e79d008717746fcf88e3..930fd784e8fff4eccc84408b9e2d740356e179b3 100644 (file)
@@ -188,6 +188,7 @@ const CONFIG = {
     get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
     get ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
     get THREADS () { return config.get<number>('transcoding.threads') },
+    get CONCURRENCY () { return config.get<number>('transcoding.concurrency') },
     get PROFILE () { return config.get<string>('transcoding.profile') },
     RESOLUTIONS: {
       get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
@@ -237,6 +238,8 @@ const CONFIG = {
   },
   IMPORT: {
     VIDEOS: {
+      get CONCURRENCY () { return config.get<number>('import.videos.concurrency') },
+
       HTTP: {
         get ENABLED () { return config.get<boolean>('import.videos.http.enabled') },
         get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') },
index 9d9b3966c272cbb21f7b2cde517dd3800d200a78..7beaca238b78277bb9424f62d835e605ebd2a750 100644 (file)
@@ -146,14 +146,12 @@ const JOB_ATTEMPTS: { [id in JobType]: number } = {
   'video-redundancy': 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-unicast': 5,
   'activitypub-http-fetcher': 1,
   'activitypub-follow': 1,
   'video-file-import': 1,
-  'video-transcoding': 1,
-  'video-import': 1,
   'email': 5,
   'videos-views': 1,
   'activitypub-refresher': 1,
index 4718a7d5c456fb8945eab9205b9415bc99647429..e248b645e4f736bcd6bc954d19faed4d209352da 100644 (file)
@@ -76,7 +76,7 @@ async function processVideoTranscoding (job: Bull.Job) {
 // 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
     ? video.getWebTorrentFile(payload.resolution)
     : video.getMaxQualityFile()
@@ -93,7 +93,7 @@ async function handleHLSJob (job: Bull.Job, payload: HLSTranscodingPayload, vide
     job
   })
 
-  await retryTransactionWrapper(onHlsPlaylistGeneration, video, payload.resolution)
+  await retryTransactionWrapper(onHlsPlaylistGeneration, video, user, payload)
 }
 
 async function handleNewWebTorrentResolutionJob (
@@ -110,9 +110,7 @@ async function handleNewWebTorrentResolutionJob (
 async function handleWebTorrentMergeAudioJob (job: Bull.Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) {
   await mergeAudioVideofile(video, payload.resolution, job)
 
-  await retryTransactionWrapper(onNewWebTorrentFileResolution, video, user, payload)
-
-  await createLowerResolutionsJobs(video, user, payload.resolution, false)
+  await retryTransactionWrapper(onVideoFileOptimizer, video, payload, 'video', user)
 }
 
 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
 
-  const maxQualityFile = video.getMaxQualityFile()
-
-  // 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) {
+  if (payload.isMaxQuality && CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
+    // Remove webtorrent files if not enabled
     for (const file of video.VideoFiles) {
       await video.removeFile(file)
       await video.removeTorrent(file)
@@ -137,6 +133,9 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, resolution: numb
     }
 
     video.VideoFiles = []
+
+    // Create HLS new resolution jobs
+    await createLowerResolutionsJobs(video, user, payload.resolution, payload.isPortraitMode, 'hls')
   }
 
   return publishAndFederateIfNeeded(video)
@@ -144,7 +143,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, resolution: numb
 
 async function onVideoFileOptimizer (
   videoArg: MVideoWithFile,
-  payload: OptimizeTranscodingPayload,
+  payload: OptimizeTranscodingPayload | MergeAudioTranscodingPayload,
   transcodeType: TranscodeOptionsType,
   user: MUserId
 ) {
@@ -166,11 +165,12 @@ async function onVideoFileOptimizer (
       isPortraitMode,
       resolution: videoDatabase.getMaxQualityFile().resolution,
       // 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)
 
-    const hasNewResolutions = createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode)
+    const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode, 'webtorrent')
 
     if (!hasNewResolutions) {
       // No transcoding to do, it's now published
@@ -193,7 +193,7 @@ async function onNewWebTorrentFileResolution (
 ) {
   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
   isPortraitMode?: boolean
   copyCodecs: boolean
+  isMaxQuality: boolean
 }) {
   if (!payload || CONFIG.TRANSCODING.HLS.ENABLED !== true) return
 
@@ -222,7 +223,8 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
     videoUUID: payload.videoUUID,
     resolution: payload.resolution,
     isPortraitMode: payload.isPortraitMode,
-    copyCodecs: payload.copyCodecs
+    copyCodecs: payload.copyCodecs,
+    isMaxQuality: payload.isMaxQuality
   }
 
   return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: hlsTranscodingPayload }, jobOptions)
@@ -232,7 +234,8 @@ async function createLowerResolutionsJobs (
   video: MVideoFullLight,
   user: MUserId,
   videoFileResolution: number,
-  isPortraitMode: boolean
+  isPortraitMode: boolean,
+  type: 'hls' | 'webtorrent'
 ) {
   // Create transcoding jobs if there are enabled resolutions
   const resolutionsEnabled = computeResolutionsToTranscode(videoFileResolution, 'vod')
@@ -250,7 +253,7 @@ async function createLowerResolutionsJobs (
   for (const resolution of resolutionsEnabled) {
     let dataInput: VideoTranscodingPayload
 
-    if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED) {
+    if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') {
       // WebTorrent will create subsequent HLS job
       dataInput = {
         type: 'new-resolution-to-webtorrent',
@@ -258,13 +261,16 @@ async function createLowerResolutionsJobs (
         resolution,
         isPortraitMode
       }
-    } else if (CONFIG.TRANSCODING.HLS.ENABLED) {
+    }
+
+    if (CONFIG.TRANSCODING.HLS.ENABLED && type === 'hls') {
       dataInput = {
         type: 'new-resolution-to-hls',
         videoUUID: video.uuid,
         resolution,
         isPortraitMode,
-        copyCodecs: false
+        copyCodecs: false,
+        isMaxQuality: false
       }
     }
 
index 38b1d6f1ff6ed9d0a5c5056195660f1e18a75314..72fed6072bbcf380ca4de2eb1dda2c4798d5b56c 100644 (file)
@@ -1,5 +1,6 @@
 import * as Bull from 'bull'
 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 {
   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 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 }))
 
       queue.on('failed', (job, err) => {
@@ -235,6 +236,13 @@ class JobQueue {
     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 () {
     return this.instance || (this.instance = new this())
   }
index 88a6e0673abb94415d589805d61b058a65b16817..a58c9dd20984d041b6953337108528b3d830bf52 100644 (file)
@@ -272,7 +272,7 @@ async function generateHlsPlaylistCommon (options: {
   const { type, video, inputPath, resolution, copyCodecs, isPortraitMode, isAAC, job } = options
   const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
 
-  const videoTranscodedBasePath = join(transcodeDirectory, type, video.uuid)
+  const videoTranscodedBasePath = join(transcodeDirectory, type)
   await ensureDir(videoTranscodedBasePath)
 
   const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution)
@@ -337,8 +337,6 @@ async function generateHlsPlaylistCommon (options: {
   await move(playlistFileTranscodePath, playlistPath, { overwrite: true })
   // Move video file
   await move(join(videoTranscodedBasePath, videoFilename), videoFilePath, { overwrite: true })
-  // Cleanup directory
-  await remove(videoTranscodedBasePath)
 
   const stats = await stat(videoFilePath)
 
index 52c28799febf701cdb80a557dd0a57e7f9af39ab..71098ff9941e8ab24668d9689853bffc2251dac7 100644 (file)
@@ -39,6 +39,7 @@ const customConfigUpdateValidator = [
   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.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.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'),
@@ -51,6 +52,7 @@ const customConfigUpdateValidator = [
   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('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.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'),
 
index 3f6fd8dc055acdd96e9bd285e52ab9d0a7ce7c79..14e80a3ba5cffe8bdd32ae4310ace0ca16b83295 100644 (file)
@@ -345,7 +345,6 @@ export type AvailableForListIDsOptions = {
       include: [
         {
           model: VideoFileModel,
-          separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
           required: false,
           include: subInclude
         }
@@ -372,7 +371,6 @@ export type AvailableForListIDsOptions = {
       include: [
         {
           model: VideoStreamingPlaylistModel.unscoped(),
-          separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
           required: false,
           include: subInclude
         }
@@ -1689,7 +1687,7 @@ export class VideoModel extends Model {
 
         channelModel.Account = accountModel
 
-        const videoModel = new VideoModel(pick(row, videoKeys))
+        const videoModel = new VideoModel(pick(row, videoKeys), buildOpts)
         videoModel.VideoChannel = channelModel
 
         videoModel.UserVideoHistories = []
index d6c20f7af0e1fc9393a4c21076e9a69da2c8137f..c7eb3189b98ad1cf64e91a710eb6a2fd254a46d9 100644 (file)
@@ -86,6 +86,7 @@ describe('Test config API validators', function () {
       enabled: true,
       allowAdditionalExtensions: true,
       allowAudioFiles: true,
+      concurrency: 1,
       threads: 1,
       profile: 'vod_profile',
       resolutions: {
@@ -130,6 +131,7 @@ describe('Test config API validators', function () {
     },
     import: {
       videos: {
+        concurrency: 1,
         http: {
           enabled: false
         },
index 26df8373e3f27e9d8e29933b301c2988b0194b82..b2371614f4a6eaf0347e216206e430373129d843 100644 (file)
@@ -70,6 +70,7 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
   expect(data.transcoding.allowAdditionalExtensions).to.be.false
   expect(data.transcoding.allowAudioFiles).to.be.false
   expect(data.transcoding.threads).to.equal(2)
+  expect(data.transcoding.concurrency).to.equal(2)
   expect(data.transcoding.profile).to.equal('default')
   expect(data.transcoding.resolutions['240p']).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['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.torrent.enabled).to.be.true
   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.threads).to.equal(1)
+  expect(data.transcoding.concurrency).to.equal(3)
   expect(data.transcoding.allowAdditionalExtensions).to.be.true
   expect(data.transcoding.allowAudioFiles).to.be.true
   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['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.torrent.enabled).to.be.false
   expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true
@@ -323,6 +327,7 @@ describe('Test config', function () {
         allowAdditionalExtensions: true,
         allowAudioFiles: true,
         threads: 1,
+        concurrency: 3,
         profile: 'vod_profile',
         resolutions: {
           '0p': false,
@@ -364,6 +369,7 @@ describe('Test config', function () {
       },
       import: {
         videos: {
+          concurrency: 4,
           http: {
             enabled: false
           },
index db5a473ca616519cc59365cb84ee9f848b8538d8..efe569243b01d47324f943041f3ca039a333afbc 100644 (file)
@@ -112,6 +112,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
       allowAdditionalExtensions: true,
       allowAudioFiles: true,
       threads: 1,
+      concurrency: 3,
       profile: 'default',
       resolutions: {
         '0p': false,
@@ -153,6 +154,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: DeepParti
     },
     import: {
       videos: {
+        concurrency: 3,
         http: {
           enabled: false
         },
index d23b8abef052640fd26275d6d62be0c6716051f0..93b2d34176a892237cfcb53cf24cfc65158ff1af 100644 (file)
@@ -87,6 +87,7 @@ export interface CustomConfig {
     allowAudioFiles: boolean
 
     threads: number
+    concurrency: number
 
     profile: string
 
@@ -120,6 +121,8 @@ export interface CustomConfig {
 
   import: {
     videos: {
+      concurrency: number
+
       http: {
         enabled: boolean
       }
index ddd678b910883a1ea34cdc7d70031ef2e5e21f01..44f92abf158b24865b71ededfcd12df60f01248c 100644 (file)
@@ -106,6 +106,7 @@ export interface HLSTranscodingPayload extends BaseTranscodingPayload {
   isPortraitMode?: boolean
   resolution: VideoResolution
   copyCodecs: boolean
+  isMaxQuality: boolean
 }
 
 export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {