Add option to not transcode original resolution
This commit is contained in:
parent
7e0f50d6e0
commit
84cae54e7a
|
@ -175,6 +175,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
profile: null,
|
profile: null,
|
||||||
concurrency: CONCURRENCY_VALIDATOR,
|
concurrency: CONCURRENCY_VALIDATOR,
|
||||||
resolutions: {},
|
resolutions: {},
|
||||||
|
alwaysTranscodeOriginalResolution: null,
|
||||||
hls: {
|
hls: {
|
||||||
enabled: null
|
enabled: null
|
||||||
},
|
},
|
||||||
|
@ -197,7 +198,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
enabled: null,
|
enabled: null,
|
||||||
threads: TRANSCODING_THREADS_VALIDATOR,
|
threads: TRANSCODING_THREADS_VALIDATOR,
|
||||||
profile: null,
|
profile: null,
|
||||||
resolutions: {}
|
resolutions: {},
|
||||||
|
alwaysTranscodeOriginalResolution: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
videoStudio: {
|
videoStudio: {
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
<ng-container ngProjectAs="description" i18n>
|
<ng-container ngProjectAs="description" i18n>
|
||||||
Small latency disables P2P and high latency can increase P2P ratio
|
Small latency disables P2P and high latency can increase P2P ratio
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
</my-peertube-checkbox>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,8 +114,8 @@
|
||||||
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
|
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
|
||||||
|
|
||||||
<div class="ms-2 mt-2 d-flex flex-column">
|
<div class="ms-2 mt-2 d-flex flex-column">
|
||||||
<ng-container formGroupName="resolutions">
|
|
||||||
|
|
||||||
|
<ng-container formGroupName="resolutions">
|
||||||
<div class="form-group" *ngFor="let resolution of liveResolutions">
|
<div class="form-group" *ngFor="let resolution of liveResolutions">
|
||||||
<my-peertube-checkbox
|
<my-peertube-checkbox
|
||||||
[inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
|
[inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
|
||||||
|
@ -127,8 +126,18 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</my-peertube-checkbox>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
inputName="transcodingAlwaysTranscodeOriginalResolution" formControlName="alwaysTranscodeOriginalResolution"
|
||||||
|
i18n-labelText labelText="Also transcode original resolution"
|
||||||
|
>
|
||||||
|
<ng-container i18n ngProjectAs="description">
|
||||||
|
Even if it's above your maximum enabled resolution
|
||||||
|
</ng-container>
|
||||||
|
</my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,13 @@
|
||||||
<label i18n>Resolutions to generate per enabled format</label>
|
<label i18n>Resolutions to generate per enabled format</label>
|
||||||
|
|
||||||
<div class="ms-2 d-flex flex-column">
|
<div class="ms-2 d-flex flex-column">
|
||||||
<span class="mb-3 small muted" i18n>
|
<my-peertube-checkbox
|
||||||
|
inputName="transcodingAlwaysTranscodeOriginalResolution" formControlName="alwaysTranscodeOriginalResolution"
|
||||||
|
i18n-labelText labelText="Always transcode original resolution"
|
||||||
|
>
|
||||||
|
</my-peertube-checkbox>
|
||||||
|
|
||||||
|
<span class="mt-3 mb-2 small muted" i18n>
|
||||||
The original file resolution will be the default target if no option is selected.
|
The original file resolution will be the default target if no option is selected.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
|
@ -403,6 +403,9 @@ transcoding:
|
||||||
1440p: false
|
1440p: false
|
||||||
2160p: false
|
2160p: false
|
||||||
|
|
||||||
|
# Transcode and keep original resolution, even if it's above your maximum enabled resolution
|
||||||
|
always_transcode_original_resolution: true
|
||||||
|
|
||||||
# Generate videos in a WebTorrent format (what we do since the first PeerTube release)
|
# Generate videos in a WebTorrent format (what we do since the first PeerTube release)
|
||||||
# If you also enabled the hls format, it will multiply videos storage by 2
|
# If you also enabled the hls format, it will multiply videos storage by 2
|
||||||
# If disabled, breaks federation with PeerTube instances < 2.1
|
# If disabled, breaks federation with PeerTube instances < 2.1
|
||||||
|
@ -496,6 +499,9 @@ live:
|
||||||
1440p: false
|
1440p: false
|
||||||
2160p: false
|
2160p: false
|
||||||
|
|
||||||
|
# Also transcode original resolution, even if it's above your maximum enabled resolution
|
||||||
|
always_transcode_original_resolution: true
|
||||||
|
|
||||||
video_studio:
|
video_studio:
|
||||||
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
||||||
# If enabled, users can create transcoding tasks as they wish
|
# If enabled, users can create transcoding tasks as they wish
|
||||||
|
|
|
@ -413,6 +413,9 @@ transcoding:
|
||||||
1440p: false
|
1440p: false
|
||||||
2160p: false
|
2160p: false
|
||||||
|
|
||||||
|
# Transcode and keep original resolution, even if it's above your maximum enabled resolution
|
||||||
|
always_transcode_original_resolution: true
|
||||||
|
|
||||||
# Generate videos in a WebTorrent format (what we do since the first PeerTube release)
|
# Generate videos in a WebTorrent format (what we do since the first PeerTube release)
|
||||||
# If you also enabled the hls format, it will multiply videos storage by 2
|
# If you also enabled the hls format, it will multiply videos storage by 2
|
||||||
# If disabled, breaks federation with PeerTube instances < 2.1
|
# If disabled, breaks federation with PeerTube instances < 2.1
|
||||||
|
@ -506,6 +509,9 @@ live:
|
||||||
1440p: false
|
1440p: false
|
||||||
2160p: false
|
2160p: false
|
||||||
|
|
||||||
|
# Also transcode original resolution, even if it's above your maximum enabled resolution
|
||||||
|
always_transcode_original_resolution: true
|
||||||
|
|
||||||
video_studio:
|
video_studio:
|
||||||
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
||||||
# If enabled, users can create transcoding tasks as they wish
|
# If enabled, users can create transcoding tasks as they wish
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
import { isUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc'
|
import { isUUIDValid, toCompleteUUID } from '@server/helpers/custom-validators/misc'
|
||||||
import { computeLowerResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
import { computeResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
||||||
import { CONFIG } from '@server/initializers/config'
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { addTranscodingJob } from '@server/lib/video'
|
import { addTranscodingJob } from '@server/lib/video'
|
||||||
import { VideoState, VideoTranscodingPayload } from '@shared/models'
|
import { VideoState, VideoTranscodingPayload } from '@shared/models'
|
||||||
|
@ -53,7 +53,7 @@ async function run () {
|
||||||
if (options.generateHls || CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
|
if (options.generateHls || CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
|
||||||
const resolutionsEnabled = options.resolution
|
const resolutionsEnabled = options.resolution
|
||||||
? [ parseInt(options.resolution) ]
|
? [ parseInt(options.resolution) ]
|
||||||
: computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ])
|
: computeResolutionsToTranscode({ inputResolution: maxResolution, type: 'vod', includeInputResolution: true })
|
||||||
|
|
||||||
for (const resolution of resolutionsEnabled) {
|
for (const resolution of resolutionsEnabled) {
|
||||||
dataInput.push({
|
dataInput.push({
|
||||||
|
@ -61,8 +61,6 @@ async function run () {
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
|
|
||||||
// FIXME: check the file has audio and is not in portrait mode
|
|
||||||
isPortraitMode: false,
|
|
||||||
hasAudio: true,
|
hasAudio: true,
|
||||||
|
|
||||||
copyCodecs: false,
|
copyCodecs: false,
|
||||||
|
|
|
@ -31,8 +31,7 @@ async function run (path: string, cmd: any) {
|
||||||
availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
|
availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
|
||||||
profile: 'default',
|
profile: 'default',
|
||||||
|
|
||||||
resolution: +cmd.resolution,
|
resolution: +cmd.resolution
|
||||||
isPortraitMode: false
|
|
||||||
} as TranscodeVODOptions
|
} as TranscodeVODOptions
|
||||||
|
|
||||||
let command = ffmpeg(options.inputPath)
|
let command = ffmpeg(options.inputPath)
|
||||||
|
|
|
@ -227,6 +227,7 @@ function customConfig (): CustomConfig {
|
||||||
'1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
|
'1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
|
||||||
'2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
|
'2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
|
||||||
},
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION,
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
|
enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
|
||||||
},
|
},
|
||||||
|
@ -256,7 +257,8 @@ function customConfig (): CustomConfig {
|
||||||
'1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
|
'1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
|
||||||
'1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
|
'1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
|
||||||
'2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
|
'2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
|
||||||
}
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
videoStudio: {
|
videoStudio: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { computeLowerResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
import { computeResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
||||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
||||||
import { addTranscodingJob } from '@server/lib/video'
|
import { addTranscodingJob } from '@server/lib/video'
|
||||||
import { HttpStatusCode, UserRight, VideoState, VideoTranscodingCreate } from '@shared/models'
|
import { HttpStatusCode, UserRight, VideoState, VideoTranscodingCreate } from '@shared/models'
|
||||||
|
@ -30,9 +30,9 @@ async function createTranscoding (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
const body: VideoTranscodingCreate = req.body
|
const body: VideoTranscodingCreate = req.body
|
||||||
|
|
||||||
const { resolution: maxResolution, isPortraitMode, audioStream } = await video.probeMaxQualityFile()
|
const { resolution: maxResolution, audioStream } = await video.probeMaxQualityFile()
|
||||||
const resolutions = await Hooks.wrapObject(
|
const resolutions = await Hooks.wrapObject(
|
||||||
computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ]),
|
computeResolutionsToTranscode({ inputResolution: maxResolution, type: 'vod', includeInputResolution: true }),
|
||||||
'filter:transcoding.manual.lower-resolutions-to-transcode.result',
|
'filter:transcoding.manual.lower-resolutions-to-transcode.result',
|
||||||
body
|
body
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,6 @@ async function createTranscoding (req: express.Request, res: express.Response) {
|
||||||
type: 'new-resolution-to-hls',
|
type: 'new-resolution-to-hls',
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode,
|
|
||||||
hasAudio: !!audioStream,
|
hasAudio: !!audioStream,
|
||||||
copyCodecs: false,
|
copyCodecs: false,
|
||||||
isNewVideo: false,
|
isNewVideo: false,
|
||||||
|
@ -64,8 +63,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
|
||||||
isNewVideo: false,
|
isNewVideo: false,
|
||||||
resolution,
|
resolution,
|
||||||
hasAudio: !!audioStream,
|
hasAudio: !!audioStream,
|
||||||
createHLSIfNeeded: false,
|
createHLSIfNeeded: false
|
||||||
isPortraitMode
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { AvailableEncoders, VideoResolution } from '@shared/models'
|
||||||
import { logger, loggerTagsFactory } from '../logger'
|
import { logger, loggerTagsFactory } from '../logger'
|
||||||
import { getFFmpeg, runCommand } from './ffmpeg-commons'
|
import { getFFmpeg, runCommand } from './ffmpeg-commons'
|
||||||
import { presetCopy, presetOnlyAudio, presetVOD } from './ffmpeg-presets'
|
import { presetCopy, presetOnlyAudio, presetVOD } from './ffmpeg-presets'
|
||||||
import { computeFPS, getVideoStreamFPS } from './ffprobe-utils'
|
import { computeFPS, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS } from './ffprobe-utils'
|
||||||
import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('ffmpeg')
|
const lTags = loggerTagsFactory('ffmpeg')
|
||||||
|
@ -27,8 +27,6 @@ interface BaseTranscodeVODOptions {
|
||||||
|
|
||||||
resolution: number
|
resolution: number
|
||||||
|
|
||||||
isPortraitMode?: boolean
|
|
||||||
|
|
||||||
job?: Job
|
job?: Job
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,13 +113,17 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function buildVODCommand (command: FfmpegCommand, options: TranscodeVODOptions) {
|
async function buildVODCommand (command: FfmpegCommand, options: TranscodeVODOptions) {
|
||||||
let fps = await getVideoStreamFPS(options.inputPath)
|
const probe = await ffprobePromise(options.inputPath)
|
||||||
|
|
||||||
|
let fps = await getVideoStreamFPS(options.inputPath, probe)
|
||||||
fps = computeFPS(fps, options.resolution)
|
fps = computeFPS(fps, options.resolution)
|
||||||
|
|
||||||
let scaleFilterValue: string
|
let scaleFilterValue: string
|
||||||
|
|
||||||
if (options.resolution !== undefined) {
|
if (options.resolution !== undefined) {
|
||||||
scaleFilterValue = options.isPortraitMode === true
|
const videoStreamInfo = await getVideoStreamDimensionsInfo(options.inputPath, probe)
|
||||||
|
|
||||||
|
scaleFilterValue = videoStreamInfo?.isPortraitMode === true
|
||||||
? `w=${options.resolution}:h=-2`
|
? `w=${options.resolution}:h=-2`
|
||||||
: `w=-2:h=${options.resolution}`
|
: `w=-2:h=${options.resolution}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,15 +90,21 @@ async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
|
||||||
// Resolutions
|
// Resolutions
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') {
|
function computeResolutionsToTranscode (options: {
|
||||||
|
inputResolution: number
|
||||||
|
type: 'vod' | 'live'
|
||||||
|
includeInputResolution: boolean
|
||||||
|
}) {
|
||||||
|
const { inputResolution, type, includeInputResolution } = options
|
||||||
|
|
||||||
const configResolutions = type === 'vod'
|
const configResolutions = type === 'vod'
|
||||||
? CONFIG.TRANSCODING.RESOLUTIONS
|
? CONFIG.TRANSCODING.RESOLUTIONS
|
||||||
: CONFIG.LIVE.TRANSCODING.RESOLUTIONS
|
: CONFIG.LIVE.TRANSCODING.RESOLUTIONS
|
||||||
|
|
||||||
const resolutionsEnabled: number[] = []
|
const resolutionsEnabled = new Set<number>()
|
||||||
|
|
||||||
// Put in the order we want to proceed jobs
|
// Put in the order we want to proceed jobs
|
||||||
const resolutions: VideoResolution[] = [
|
const availableResolutions: VideoResolution[] = [
|
||||||
VideoResolution.H_NOVIDEO,
|
VideoResolution.H_NOVIDEO,
|
||||||
VideoResolution.H_480P,
|
VideoResolution.H_480P,
|
||||||
VideoResolution.H_360P,
|
VideoResolution.H_360P,
|
||||||
|
@ -110,13 +116,17 @@ function computeLowerResolutionsToTranscode (videoFileResolution: number, type:
|
||||||
VideoResolution.H_4K
|
VideoResolution.H_4K
|
||||||
]
|
]
|
||||||
|
|
||||||
for (const resolution of resolutions) {
|
for (const resolution of availableResolutions) {
|
||||||
if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
|
if (configResolutions[resolution + 'p'] === true && inputResolution > resolution) {
|
||||||
resolutionsEnabled.push(resolution)
|
resolutionsEnabled.add(resolution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolutionsEnabled
|
if (includeInputResolution) {
|
||||||
|
resolutionsEnabled.add(inputResolution)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(resolutionsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -224,7 +234,7 @@ export {
|
||||||
computeFPS,
|
computeFPS,
|
||||||
getClosestFramerateStandard,
|
getClosestFramerateStandard,
|
||||||
|
|
||||||
computeLowerResolutionsToTranscode,
|
computeResolutionsToTranscode,
|
||||||
|
|
||||||
canDoQuickTranscode,
|
canDoQuickTranscode,
|
||||||
canDoQuickVideoTranscode,
|
canDoQuickVideoTranscode,
|
||||||
|
|
|
@ -30,7 +30,7 @@ function checkMissedConfig () {
|
||||||
'transcoding.profile', 'transcoding.concurrency',
|
'transcoding.profile', 'transcoding.concurrency',
|
||||||
'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p',
|
'transcoding.resolutions.0p', 'transcoding.resolutions.144p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p',
|
||||||
'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p',
|
'transcoding.resolutions.480p', 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.1440p',
|
||||||
'transcoding.resolutions.2160p', 'video_studio.enabled',
|
'transcoding.resolutions.2160p', 'transcoding.always_transcode_original_resolution', 'video_studio.enabled',
|
||||||
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'import.videos.timeout',
|
'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'import.videos.timeout',
|
||||||
'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days',
|
'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days',
|
||||||
'client.videos.miniature.display_author_avatar',
|
'client.videos.miniature.display_author_avatar',
|
||||||
|
@ -59,7 +59,7 @@ function checkMissedConfig () {
|
||||||
'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
|
'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
|
||||||
'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
|
'live.transcoding.resolutions.144p', 'live.transcoding.resolutions.240p', 'live.transcoding.resolutions.360p',
|
||||||
'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
|
'live.transcoding.resolutions.480p', 'live.transcoding.resolutions.720p', 'live.transcoding.resolutions.1080p',
|
||||||
'live.transcoding.resolutions.1440p', 'live.transcoding.resolutions.2160p'
|
'live.transcoding.resolutions.1440p', 'live.transcoding.resolutions.2160p', 'live.transcoding.always_transcode_original_resolution'
|
||||||
]
|
]
|
||||||
|
|
||||||
const requiredAlternatives = [
|
const requiredAlternatives = [
|
||||||
|
|
|
@ -309,6 +309,7 @@ const CONFIG = {
|
||||||
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 CONCURRENCY () { return config.get<number>('transcoding.concurrency') },
|
||||||
get PROFILE () { return config.get<string>('transcoding.profile') },
|
get PROFILE () { return config.get<string>('transcoding.profile') },
|
||||||
|
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('transcoding.always_transcode_original_resolution') },
|
||||||
RESOLUTIONS: {
|
RESOLUTIONS: {
|
||||||
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
||||||
get '144p' () { return config.get<boolean>('transcoding.resolutions.144p') },
|
get '144p' () { return config.get<boolean>('transcoding.resolutions.144p') },
|
||||||
|
@ -361,6 +362,8 @@ const CONFIG = {
|
||||||
get THREADS () { return config.get<number>('live.transcoding.threads') },
|
get THREADS () { return config.get<number>('live.transcoding.threads') },
|
||||||
get PROFILE () { return config.get<string>('live.transcoding.profile') },
|
get PROFILE () { return config.get<string>('live.transcoding.profile') },
|
||||||
|
|
||||||
|
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('live.transcoding.always_transcode_original_resolution') },
|
||||||
|
|
||||||
RESOLUTIONS: {
|
RESOLUTIONS: {
|
||||||
get '144p' () { return config.get<boolean>('live.transcoding.resolutions.144p') },
|
get '144p' () { return config.get<boolean>('live.transcoding.resolutions.144p') },
|
||||||
get '240p' () { return config.get<boolean>('live.transcoding.resolutions.240p') },
|
get '240p' () { return config.get<boolean>('live.transcoding.resolutions.240p') },
|
||||||
|
|
|
@ -213,13 +213,12 @@ async function assignReplayFilesToVideo (options: {
|
||||||
const probe = await ffprobePromise(concatenatedTsFilePath)
|
const probe = await ffprobePromise(concatenatedTsFilePath)
|
||||||
const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe)
|
const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe)
|
||||||
|
|
||||||
const { resolution, isPortraitMode } = await getVideoStreamDimensionsInfo(concatenatedTsFilePath, probe)
|
const { resolution } = await getVideoStreamDimensionsInfo(concatenatedTsFilePath, probe)
|
||||||
|
|
||||||
const { resolutionPlaylistPath: outputPath } = await generateHlsPlaylistResolutionFromTS({
|
const { resolutionPlaylistPath: outputPath } = await generateHlsPlaylistResolutionFromTS({
|
||||||
video,
|
video,
|
||||||
concatenatedTsFilePath,
|
concatenatedTsFilePath,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode,
|
|
||||||
isAAC: audioStream?.codec_name === 'aac'
|
isAAC: audioStream?.codec_name === 'aac'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Job } from 'bull'
|
import { Job } from 'bull'
|
||||||
import { TranscodeVODOptionsType } from '@server/helpers/ffmpeg'
|
import { TranscodeVODOptionsType } from '@server/helpers/ffmpeg'
|
||||||
|
import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video'
|
import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video'
|
||||||
import { VideoPathManager } from '@server/lib/video-path-manager'
|
import { VideoPathManager } from '@server/lib/video-path-manager'
|
||||||
import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state'
|
import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state'
|
||||||
|
@ -16,7 +17,7 @@ import {
|
||||||
VideoTranscodingPayload
|
VideoTranscodingPayload
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
import { computeLowerResolutionsToTranscode } from '../../../helpers/ffmpeg'
|
import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger'
|
||||||
import { CONFIG } from '../../../initializers/config'
|
import { CONFIG } from '../../../initializers/config'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
|
@ -26,7 +27,6 @@ import {
|
||||||
optimizeOriginalVideofile,
|
optimizeOriginalVideofile,
|
||||||
transcodeNewWebTorrentResolution
|
transcodeNewWebTorrentResolution
|
||||||
} from '../../transcoding/transcoding'
|
} from '../../transcoding/transcoding'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks'
|
|
||||||
|
|
||||||
type HandlerFunction = (job: Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<void>
|
type HandlerFunction = (job: Job, payload: VideoTranscodingPayload, video: MVideoFullLight, user: MUser) => Promise<void>
|
||||||
|
|
||||||
|
@ -99,7 +99,6 @@ async function handleHLSJob (job: Job, payload: HLSTranscodingPayload, video: MV
|
||||||
videoInputPath,
|
videoInputPath,
|
||||||
resolution: payload.resolution,
|
resolution: payload.resolution,
|
||||||
copyCodecs: payload.copyCodecs,
|
copyCodecs: payload.copyCodecs,
|
||||||
isPortraitMode: payload.isPortraitMode || false,
|
|
||||||
job
|
job
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -117,7 +116,7 @@ async function handleNewWebTorrentResolutionJob (
|
||||||
) {
|
) {
|
||||||
logger.info('Handling WebTorrent transcoding job for %s.', video.uuid, lTags(video.uuid))
|
logger.info('Handling WebTorrent transcoding job for %s.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
await transcodeNewWebTorrentResolution(video, payload.resolution, payload.isPortraitMode || false, job)
|
await transcodeNewWebTorrentResolution({ video, resolution: payload.resolution, job })
|
||||||
|
|
||||||
logger.info('WebTorrent transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
logger.info('WebTorrent transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
|
@ -127,7 +126,7 @@ async function handleNewWebTorrentResolutionJob (
|
||||||
async function handleWebTorrentMergeAudioJob (job: Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
async function handleWebTorrentMergeAudioJob (job: Job, payload: MergeAudioTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
||||||
logger.info('Handling merge audio transcoding job for %s.', video.uuid, lTags(video.uuid))
|
logger.info('Handling merge audio transcoding job for %s.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
await mergeAudioVideofile(video, payload.resolution, job)
|
await mergeAudioVideofile({ video, resolution: payload.resolution, job })
|
||||||
|
|
||||||
logger.info('Merge audio transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
logger.info('Merge audio transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
|
@ -137,7 +136,7 @@ async function handleWebTorrentMergeAudioJob (job: Job, payload: MergeAudioTrans
|
||||||
async function handleWebTorrentOptimizeJob (job: Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
async function handleWebTorrentOptimizeJob (job: Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
||||||
logger.info('Handling optimize transcoding job for %s.', video.uuid, lTags(video.uuid))
|
logger.info('Handling optimize transcoding job for %s.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
const { transcodeType } = await optimizeOriginalVideofile(video, video.getMaxQualityFile(), job)
|
const { transcodeType } = await optimizeOriginalVideofile({ video, inputVideoFile: video.getMaxQualityFile(), job })
|
||||||
|
|
||||||
logger.info('Optimize transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
logger.info('Optimize transcoding job for %s ended.', video.uuid, lTags(video.uuid))
|
||||||
|
|
||||||
|
@ -161,7 +160,6 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
|
||||||
video,
|
video,
|
||||||
user,
|
user,
|
||||||
videoFileResolution: payload.resolution,
|
videoFileResolution: payload.resolution,
|
||||||
isPortraitMode: payload.isPortraitMode,
|
|
||||||
hasAudio: payload.hasAudio,
|
hasAudio: payload.hasAudio,
|
||||||
isNewVideo: payload.isNewVideo ?? true,
|
isNewVideo: payload.isNewVideo ?? true,
|
||||||
type: 'hls'
|
type: 'hls'
|
||||||
|
@ -178,7 +176,7 @@ async function onVideoFirstWebTorrentTranscoding (
|
||||||
transcodeType: TranscodeVODOptionsType,
|
transcodeType: TranscodeVODOptionsType,
|
||||||
user: MUserId
|
user: MUserId
|
||||||
) {
|
) {
|
||||||
const { resolution, isPortraitMode, audioStream } = await videoArg.probeMaxQualityFile()
|
const { resolution, audioStream } = await videoArg.probeMaxQualityFile()
|
||||||
|
|
||||||
// Maybe the video changed in database, refresh it
|
// Maybe the video changed in database, refresh it
|
||||||
const videoDatabase = await VideoModel.loadFull(videoArg.uuid)
|
const videoDatabase = await VideoModel.loadFull(videoArg.uuid)
|
||||||
|
@ -189,7 +187,6 @@ async function onVideoFirstWebTorrentTranscoding (
|
||||||
const originalFileHLSPayload = {
|
const originalFileHLSPayload = {
|
||||||
...payload,
|
...payload,
|
||||||
|
|
||||||
isPortraitMode,
|
|
||||||
hasAudio: !!audioStream,
|
hasAudio: !!audioStream,
|
||||||
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
|
||||||
|
@ -202,7 +199,6 @@ async function onVideoFirstWebTorrentTranscoding (
|
||||||
user,
|
user,
|
||||||
videoFileResolution: resolution,
|
videoFileResolution: resolution,
|
||||||
hasAudio: !!audioStream,
|
hasAudio: !!audioStream,
|
||||||
isPortraitMode,
|
|
||||||
type: 'webtorrent',
|
type: 'webtorrent',
|
||||||
isNewVideo: payload.isNewVideo ?? true
|
isNewVideo: payload.isNewVideo ?? true
|
||||||
})
|
})
|
||||||
|
@ -235,7 +231,6 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
||||||
videoUUID: string
|
videoUUID: string
|
||||||
resolution: number
|
resolution: number
|
||||||
hasAudio: boolean
|
hasAudio: boolean
|
||||||
isPortraitMode?: boolean
|
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
isMaxQuality: boolean
|
isMaxQuality: boolean
|
||||||
isNewVideo?: boolean
|
isNewVideo?: boolean
|
||||||
|
@ -250,7 +245,7 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
||||||
type: 'new-resolution-to-hls',
|
type: 'new-resolution-to-hls',
|
||||||
autoDeleteWebTorrentIfNeeded: true,
|
autoDeleteWebTorrentIfNeeded: true,
|
||||||
|
|
||||||
...pick(payload, [ 'videoUUID', 'resolution', 'isPortraitMode', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ])
|
...pick(payload, [ 'videoUUID', 'resolution', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ])
|
||||||
}
|
}
|
||||||
|
|
||||||
await addTranscodingJob(hlsTranscodingPayload, jobOptions)
|
await addTranscodingJob(hlsTranscodingPayload, jobOptions)
|
||||||
|
@ -262,16 +257,15 @@ async function createLowerResolutionsJobs (options: {
|
||||||
video: MVideoFullLight
|
video: MVideoFullLight
|
||||||
user: MUserId
|
user: MUserId
|
||||||
videoFileResolution: number
|
videoFileResolution: number
|
||||||
isPortraitMode: boolean
|
|
||||||
hasAudio: boolean
|
hasAudio: boolean
|
||||||
isNewVideo: boolean
|
isNewVideo: boolean
|
||||||
type: 'hls' | 'webtorrent'
|
type: 'hls' | 'webtorrent'
|
||||||
}) {
|
}) {
|
||||||
const { video, user, videoFileResolution, isPortraitMode, isNewVideo, hasAudio, type } = options
|
const { video, user, videoFileResolution, isNewVideo, hasAudio, type } = options
|
||||||
|
|
||||||
// Create transcoding jobs if there are enabled resolutions
|
// Create transcoding jobs if there are enabled resolutions
|
||||||
const resolutionsEnabled = await Hooks.wrapObject(
|
const resolutionsEnabled = await Hooks.wrapObject(
|
||||||
computeLowerResolutionsToTranscode(videoFileResolution, 'vod'),
|
computeResolutionsToTranscode({ inputResolution: videoFileResolution, type: 'vod', includeInputResolution: false }),
|
||||||
'filter:transcoding.auto.lower-resolutions-to-transcode.result',
|
'filter:transcoding.auto.lower-resolutions-to-transcode.result',
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
@ -289,7 +283,6 @@ async function createLowerResolutionsJobs (options: {
|
||||||
type: 'new-resolution-to-webtorrent',
|
type: 'new-resolution-to-webtorrent',
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode,
|
|
||||||
hasAudio,
|
hasAudio,
|
||||||
createHLSIfNeeded: true,
|
createHLSIfNeeded: true,
|
||||||
isNewVideo
|
isNewVideo
|
||||||
|
@ -303,7 +296,6 @@ async function createLowerResolutionsJobs (options: {
|
||||||
type: 'new-resolution-to-hls',
|
type: 'new-resolution-to-hls',
|
||||||
videoUUID: video.uuid,
|
videoUUID: video.uuid,
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode,
|
|
||||||
hasAudio,
|
hasAudio,
|
||||||
copyCodecs: false,
|
copyCodecs: false,
|
||||||
isMaxQuality: false,
|
isMaxQuality: false,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createServer, Server } from 'net'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
|
import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
|
||||||
import {
|
import {
|
||||||
computeLowerResolutionsToTranscode,
|
computeResolutionsToTranscode,
|
||||||
ffprobePromise,
|
ffprobePromise,
|
||||||
getLiveSegmentTime,
|
getLiveSegmentTime,
|
||||||
getVideoStreamBitrate,
|
getVideoStreamBitrate,
|
||||||
|
@ -26,10 +26,10 @@ import { federateVideoIfNeeded } from '../activitypub/videos'
|
||||||
import { JobQueue } from '../job-queue'
|
import { JobQueue } from '../job-queue'
|
||||||
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths'
|
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths'
|
||||||
import { PeerTubeSocket } from '../peertube-socket'
|
import { PeerTubeSocket } from '../peertube-socket'
|
||||||
|
import { Hooks } from '../plugins/hooks'
|
||||||
import { LiveQuotaStore } from './live-quota-store'
|
import { LiveQuotaStore } from './live-quota-store'
|
||||||
import { cleanupPermanentLive } from './live-utils'
|
import { cleanupPermanentLive } from './live-utils'
|
||||||
import { MuxingSession } from './shared'
|
import { MuxingSession } from './shared'
|
||||||
import { Hooks } from '../plugins/hooks'
|
|
||||||
|
|
||||||
const NodeRtmpSession = require('node-media-server/src/node_rtmp_session')
|
const NodeRtmpSession = require('node-media-server/src/node_rtmp_session')
|
||||||
const context = require('node-media-server/src/node_core_ctx')
|
const context = require('node-media-server/src/node_core_ctx')
|
||||||
|
@ -456,11 +456,17 @@ class LiveManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildAllResolutionsToTranscode (originResolution: number) {
|
private buildAllResolutionsToTranscode (originResolution: number) {
|
||||||
|
const includeInputResolution = CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
|
||||||
|
|
||||||
const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED
|
const resolutionsEnabled = CONFIG.LIVE.TRANSCODING.ENABLED
|
||||||
? computeLowerResolutionsToTranscode(originResolution, 'live')
|
? computeResolutionsToTranscode({ inputResolution: originResolution, type: 'live', includeInputResolution })
|
||||||
: []
|
: []
|
||||||
|
|
||||||
return resolutionsEnabled.concat([ originResolution ])
|
if (resolutionsEnabled.length === 0) {
|
||||||
|
return [ originResolution ]
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createLivePlaylist (video: MVideo, allResolutions: number[]): Promise<MStreamingPlaylistVideo> {
|
private async createLivePlaylist (video: MVideo, allResolutions: number[]): Promise<MStreamingPlaylistVideo> {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
|
||||||
import {
|
import {
|
||||||
buildFileMetadata,
|
buildFileMetadata,
|
||||||
canDoQuickTranscode,
|
canDoQuickTranscode,
|
||||||
|
computeResolutionsToTranscode,
|
||||||
getVideoStreamDuration,
|
getVideoStreamDuration,
|
||||||
getVideoStreamFPS,
|
getVideoStreamFPS,
|
||||||
transcodeVOD,
|
transcodeVOD,
|
||||||
|
@ -32,7 +33,13 @@ import { VideoTranscodingProfilesManager } from './default-transcoding-profiles'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Optimize the original video file and replace it. The resolution is not changed.
|
// Optimize the original video file and replace it. The resolution is not changed.
|
||||||
function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVideoFile, job?: Job) {
|
function optimizeOriginalVideofile (options: {
|
||||||
|
video: MVideoFullLight
|
||||||
|
inputVideoFile: MVideoFile
|
||||||
|
job: Job
|
||||||
|
}) {
|
||||||
|
const { video, inputVideoFile, job } = options
|
||||||
|
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
|
||||||
|
@ -43,7 +50,7 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
||||||
? 'quick-transcode'
|
? 'quick-transcode'
|
||||||
: 'video'
|
: 'video'
|
||||||
|
|
||||||
const resolution = toEven(inputVideoFile.resolution)
|
const resolution = buildOriginalFileResolution(inputVideoFile.resolution)
|
||||||
|
|
||||||
const transcodeOptions: TranscodeVODOptions = {
|
const transcodeOptions: TranscodeVODOptions = {
|
||||||
type: transcodeType,
|
type: transcodeType,
|
||||||
|
@ -63,6 +70,7 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
||||||
await transcodeVOD(transcodeOptions)
|
await transcodeVOD(transcodeOptions)
|
||||||
|
|
||||||
// Important to do this before getVideoFilename() to take in account the new filename
|
// Important to do this before getVideoFilename() to take in account the new filename
|
||||||
|
inputVideoFile.resolution = resolution
|
||||||
inputVideoFile.extname = newExtname
|
inputVideoFile.extname = newExtname
|
||||||
inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
|
inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
|
||||||
inputVideoFile.storage = VideoStorage.FILE_SYSTEM
|
inputVideoFile.storage = VideoStorage.FILE_SYSTEM
|
||||||
|
@ -76,17 +84,22 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transcode the original video file to a lower resolution
|
// Transcode the original video file to a lower resolution compatible with WebTorrent
|
||||||
// We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
|
function transcodeNewWebTorrentResolution (options: {
|
||||||
function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: VideoResolution, isPortrait: boolean, job: Job) {
|
video: MVideoFullLight
|
||||||
|
resolution: VideoResolution
|
||||||
|
job: Job
|
||||||
|
}) {
|
||||||
|
const { video, resolution, job } = options
|
||||||
|
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const extname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
|
||||||
return VideoPathManager.Instance.makeAvailableVideoFile(video.getMaxQualityFile().withVideoOrPlaylist(video), async videoInputPath => {
|
return VideoPathManager.Instance.makeAvailableVideoFile(video.getMaxQualityFile().withVideoOrPlaylist(video), async videoInputPath => {
|
||||||
const newVideoFile = new VideoFileModel({
|
const newVideoFile = new VideoFileModel({
|
||||||
resolution,
|
resolution,
|
||||||
extname,
|
extname: newExtname,
|
||||||
filename: generateWebTorrentVideoFilename(resolution, extname),
|
filename: generateWebTorrentVideoFilename(resolution, newExtname),
|
||||||
size: 0,
|
size: 0,
|
||||||
videoId: video.id
|
videoId: video.id
|
||||||
})
|
})
|
||||||
|
@ -117,7 +130,6 @@ function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: V
|
||||||
profile: CONFIG.TRANSCODING.PROFILE,
|
profile: CONFIG.TRANSCODING.PROFILE,
|
||||||
|
|
||||||
resolution,
|
resolution,
|
||||||
isPortraitMode: isPortrait,
|
|
||||||
|
|
||||||
job
|
job
|
||||||
}
|
}
|
||||||
|
@ -129,7 +141,13 @@ function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: V
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge an image with an audio file to create a video
|
// Merge an image with an audio file to create a video
|
||||||
function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoResolution, job: Job) {
|
function mergeAudioVideofile (options: {
|
||||||
|
video: MVideoFullLight
|
||||||
|
resolution: VideoResolution
|
||||||
|
job: Job
|
||||||
|
}) {
|
||||||
|
const { video, resolution, job } = options
|
||||||
|
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
|
||||||
|
@ -188,13 +206,11 @@ async function generateHlsPlaylistResolutionFromTS (options: {
|
||||||
video: MVideo
|
video: MVideo
|
||||||
concatenatedTsFilePath: string
|
concatenatedTsFilePath: string
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
isPortraitMode: boolean
|
|
||||||
isAAC: boolean
|
isAAC: boolean
|
||||||
}) {
|
}) {
|
||||||
return generateHlsPlaylistCommon({
|
return generateHlsPlaylistCommon({
|
||||||
video: options.video,
|
video: options.video,
|
||||||
resolution: options.resolution,
|
resolution: options.resolution,
|
||||||
isPortraitMode: options.isPortraitMode,
|
|
||||||
inputPath: options.concatenatedTsFilePath,
|
inputPath: options.concatenatedTsFilePath,
|
||||||
type: 'hls-from-ts' as 'hls-from-ts',
|
type: 'hls-from-ts' as 'hls-from-ts',
|
||||||
isAAC: options.isAAC
|
isAAC: options.isAAC
|
||||||
|
@ -207,14 +223,12 @@ function generateHlsPlaylistResolution (options: {
|
||||||
videoInputPath: string
|
videoInputPath: string
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
isPortraitMode: boolean
|
|
||||||
job?: Job
|
job?: Job
|
||||||
}) {
|
}) {
|
||||||
return generateHlsPlaylistCommon({
|
return generateHlsPlaylistCommon({
|
||||||
video: options.video,
|
video: options.video,
|
||||||
resolution: options.resolution,
|
resolution: options.resolution,
|
||||||
copyCodecs: options.copyCodecs,
|
copyCodecs: options.copyCodecs,
|
||||||
isPortraitMode: options.isPortraitMode,
|
|
||||||
inputPath: options.videoInputPath,
|
inputPath: options.videoInputPath,
|
||||||
type: 'hls' as 'hls',
|
type: 'hls' as 'hls',
|
||||||
job: options.job
|
job: options.job
|
||||||
|
@ -267,11 +281,10 @@ async function generateHlsPlaylistCommon (options: {
|
||||||
resolution: VideoResolution
|
resolution: VideoResolution
|
||||||
copyCodecs?: boolean
|
copyCodecs?: boolean
|
||||||
isAAC?: boolean
|
isAAC?: boolean
|
||||||
isPortraitMode: boolean
|
|
||||||
|
|
||||||
job?: Job
|
job?: Job
|
||||||
}) {
|
}) {
|
||||||
const { type, video, inputPath, resolution, copyCodecs, isPortraitMode, isAAC, job } = options
|
const { type, video, inputPath, resolution, copyCodecs, isAAC, job } = options
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
|
|
||||||
const videoTranscodedBasePath = join(transcodeDirectory, type)
|
const videoTranscodedBasePath = join(transcodeDirectory, type)
|
||||||
|
@ -292,7 +305,6 @@ async function generateHlsPlaylistCommon (options: {
|
||||||
|
|
||||||
resolution,
|
resolution,
|
||||||
copyCodecs,
|
copyCodecs,
|
||||||
isPortraitMode,
|
|
||||||
|
|
||||||
isAAC,
|
isAAC,
|
||||||
|
|
||||||
|
@ -350,3 +362,12 @@ async function generateHlsPlaylistCommon (options: {
|
||||||
|
|
||||||
return { resolutionPlaylistPath, videoFile: savedVideoFile }
|
return { resolutionPlaylistPath, videoFile: savedVideoFile }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildOriginalFileResolution (inputResolution: number) {
|
||||||
|
if (CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION === true) return toEven(inputResolution)
|
||||||
|
|
||||||
|
const resolutions = computeResolutionsToTranscode({ inputResolution, type: 'vod', includeInputResolution: false })
|
||||||
|
if (resolutions.length === 0) return toEven(inputResolution)
|
||||||
|
|
||||||
|
return Math.max(...resolutions)
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,9 @@ const customConfigUpdateValidator = [
|
||||||
body('transcoding.resolutions.1440p').isBoolean().withMessage('Should have a valid transcoding 1440p resolution enabled boolean'),
|
body('transcoding.resolutions.1440p').isBoolean().withMessage('Should have a valid transcoding 1440p resolution enabled boolean'),
|
||||||
body('transcoding.resolutions.2160p').isBoolean().withMessage('Should have a valid transcoding 2160p resolution enabled boolean'),
|
body('transcoding.resolutions.2160p').isBoolean().withMessage('Should have a valid transcoding 2160p resolution enabled boolean'),
|
||||||
|
|
||||||
|
body('transcoding.alwaysTranscodeOriginalResolution').isBoolean()
|
||||||
|
.withMessage('Should have a valid always transcode original resolution boolean'),
|
||||||
|
|
||||||
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'),
|
||||||
|
|
||||||
|
@ -91,6 +94,8 @@ const customConfigUpdateValidator = [
|
||||||
body('live.transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'),
|
body('live.transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'),
|
||||||
body('live.transcoding.resolutions.1440p').isBoolean().withMessage('Should have a valid transcoding 1440p resolution enabled boolean'),
|
body('live.transcoding.resolutions.1440p').isBoolean().withMessage('Should have a valid transcoding 1440p resolution enabled boolean'),
|
||||||
body('live.transcoding.resolutions.2160p').isBoolean().withMessage('Should have a valid transcoding 2160p resolution enabled boolean'),
|
body('live.transcoding.resolutions.2160p').isBoolean().withMessage('Should have a valid transcoding 2160p resolution enabled boolean'),
|
||||||
|
body('live.transcoding.alwaysTranscodeOriginalResolution').isBoolean()
|
||||||
|
.withMessage('Should have a valid always transcode live original resolution boolean'),
|
||||||
|
|
||||||
body('search.remoteUri.users').isBoolean().withMessage('Should have a remote URI search for users boolean'),
|
body('search.remoteUri.users').isBoolean().withMessage('Should have a remote URI search for users boolean'),
|
||||||
body('search.remoteUri.anonymous').isBoolean().withMessage('Should have a valid remote URI search for anonymous boolean'),
|
body('search.remoteUri.anonymous').isBoolean().withMessage('Should have a valid remote URI search for anonymous boolean'),
|
||||||
|
|
|
@ -114,6 +114,7 @@ describe('Test config API validators', function () {
|
||||||
'1440p': false,
|
'1440p': false,
|
||||||
'2160p': false
|
'2160p': false
|
||||||
},
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: false,
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
|
@ -145,7 +146,8 @@ describe('Test config API validators', function () {
|
||||||
'1080p': true,
|
'1080p': true,
|
||||||
'1440p': true,
|
'1440p': true,
|
||||||
'2160p': true
|
'2160p': true
|
||||||
}
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
videoStudio: {
|
videoStudio: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'mocha'
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import { basename, join } from 'path'
|
import { basename, join } from 'path'
|
||||||
import { ffprobePromise, getVideoStream } from '@server/helpers/ffmpeg'
|
import { ffprobePromise, getVideoStream } from '@server/helpers/ffmpeg'
|
||||||
import { checkLiveCleanup, checkLiveSegmentHash, checkResolutionsInMasterPlaylist, testImage } from '@server/tests/shared'
|
import { checkLiveSegmentHash, checkResolutionsInMasterPlaylist, getAllFiles, testImage } from '@server/tests/shared'
|
||||||
import { wait } from '@shared/core-utils'
|
import { wait } from '@shared/core-utils'
|
||||||
import {
|
import {
|
||||||
HttpStatusCode,
|
HttpStatusCode,
|
||||||
|
@ -468,7 +468,7 @@ describe('Test live', function () {
|
||||||
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
await testVideoResolutions(liveVideoId, resolutions)
|
await testVideoResolutions(liveVideoId, resolutions.concat([ 720 ]))
|
||||||
|
|
||||||
await stopFfmpeg(ffmpegCommand)
|
await stopFfmpeg(ffmpegCommand)
|
||||||
})
|
})
|
||||||
|
@ -580,10 +580,73 @@ describe('Test live', function () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should correctly have cleaned up the live files', async function () {
|
it('Should not generate an upper resolution than original file', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(400_000)
|
||||||
|
|
||||||
await checkLiveCleanup(servers[0], liveVideoId, [ 240, 360, 720 ])
|
const resolutions = [ 240, 480 ]
|
||||||
|
await updateConf(resolutions)
|
||||||
|
|
||||||
|
await servers[0].config.updateExistingSubConfig({
|
||||||
|
newConfig: {
|
||||||
|
live: {
|
||||||
|
transcoding: {
|
||||||
|
alwaysTranscodeOriginalResolution: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
liveVideoId = await createLiveWrapper(true)
|
||||||
|
|
||||||
|
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
|
||||||
|
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await testVideoResolutions(liveVideoId, resolutions)
|
||||||
|
|
||||||
|
await stopFfmpeg(ffmpegCommand)
|
||||||
|
await commands[0].waitUntilEnded({ videoId: liveVideoId })
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||||
|
|
||||||
|
const video = await servers[0].videos.get({ id: liveVideoId })
|
||||||
|
const hlsFiles = video.streamingPlaylists[0].files
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(0)
|
||||||
|
expect(hlsFiles).to.have.lengthOf(resolutions.length)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
|
||||||
|
expect(getAllFiles(video).map(f => f.resolution.id).sort()).to.deep.equal(resolutions)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should only keep the original resolution if all resolutions are disabled', async function () {
|
||||||
|
this.timeout(400_000)
|
||||||
|
|
||||||
|
await updateConf([])
|
||||||
|
liveVideoId = await createLiveWrapper(true)
|
||||||
|
|
||||||
|
const ffmpegCommand = await commands[0].sendRTMPStreamInVideo({ videoId: liveVideoId, fixtureName: 'video_short2.webm' })
|
||||||
|
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await testVideoResolutions(liveVideoId, [ 720 ])
|
||||||
|
|
||||||
|
await stopFfmpeg(ffmpegCommand)
|
||||||
|
await commands[0].waitUntilEnded({ videoId: liveVideoId })
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||||
|
|
||||||
|
const video = await servers[0].videos.get({ id: liveVideoId })
|
||||||
|
const hlsFiles = video.streamingPlaylists[0].files
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(0)
|
||||||
|
expect(hlsFiles).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
expect(hlsFiles[0].resolution.id).to.equal(720)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,7 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
||||||
expect(data.transcoding.resolutions['1080p']).to.be.true
|
expect(data.transcoding.resolutions['1080p']).to.be.true
|
||||||
expect(data.transcoding.resolutions['1440p']).to.be.true
|
expect(data.transcoding.resolutions['1440p']).to.be.true
|
||||||
expect(data.transcoding.resolutions['2160p']).to.be.true
|
expect(data.transcoding.resolutions['2160p']).to.be.true
|
||||||
|
expect(data.transcoding.alwaysTranscodeOriginalResolution).to.be.true
|
||||||
expect(data.transcoding.webtorrent.enabled).to.be.true
|
expect(data.transcoding.webtorrent.enabled).to.be.true
|
||||||
expect(data.transcoding.hls.enabled).to.be.true
|
expect(data.transcoding.hls.enabled).to.be.true
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
||||||
expect(data.live.transcoding.resolutions['1080p']).to.be.false
|
expect(data.live.transcoding.resolutions['1080p']).to.be.false
|
||||||
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.live.transcoding.alwaysTranscodeOriginalResolution).to.be.true
|
||||||
|
|
||||||
expect(data.videoStudio.enabled).to.be.false
|
expect(data.videoStudio.enabled).to.be.false
|
||||||
|
|
||||||
|
@ -181,6 +183,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.transcoding.resolutions['720p']).to.be.false
|
expect(data.transcoding.resolutions['720p']).to.be.false
|
||||||
expect(data.transcoding.resolutions['1080p']).to.be.false
|
expect(data.transcoding.resolutions['1080p']).to.be.false
|
||||||
expect(data.transcoding.resolutions['2160p']).to.be.false
|
expect(data.transcoding.resolutions['2160p']).to.be.false
|
||||||
|
expect(data.transcoding.alwaysTranscodeOriginalResolution).to.be.false
|
||||||
expect(data.transcoding.hls.enabled).to.be.false
|
expect(data.transcoding.hls.enabled).to.be.false
|
||||||
expect(data.transcoding.webtorrent.enabled).to.be.true
|
expect(data.transcoding.webtorrent.enabled).to.be.true
|
||||||
|
|
||||||
|
@ -200,6 +203,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.live.transcoding.resolutions['720p']).to.be.true
|
expect(data.live.transcoding.resolutions['720p']).to.be.true
|
||||||
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.live.transcoding.alwaysTranscodeOriginalResolution).to.be.false
|
||||||
|
|
||||||
expect(data.videoStudio.enabled).to.be.true
|
expect(data.videoStudio.enabled).to.be.true
|
||||||
|
|
||||||
|
@ -318,6 +322,7 @@ const newCustomConfig: CustomConfig = {
|
||||||
'1440p': false,
|
'1440p': false,
|
||||||
'2160p': false
|
'2160p': false
|
||||||
},
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: false,
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
|
@ -347,7 +352,8 @@ const newCustomConfig: CustomConfig = {
|
||||||
'1080p': true,
|
'1080p': true,
|
||||||
'1440p': true,
|
'1440p': true,
|
||||||
'2160p': true
|
'2160p': true
|
||||||
}
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
videoStudio: {
|
videoStudio: {
|
||||||
|
|
|
@ -7,11 +7,11 @@ import { canDoQuickTranscode } from '@server/helpers/ffmpeg'
|
||||||
import { generateHighBitrateVideo, generateVideoWithFramerate, getAllFiles } from '@server/tests/shared'
|
import { generateHighBitrateVideo, generateVideoWithFramerate, getAllFiles } from '@server/tests/shared'
|
||||||
import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
|
import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
|
||||||
import {
|
import {
|
||||||
getAudioStream,
|
|
||||||
buildFileMetadata,
|
buildFileMetadata,
|
||||||
|
getAudioStream,
|
||||||
getVideoStreamBitrate,
|
getVideoStreamBitrate,
|
||||||
getVideoStreamFPS,
|
|
||||||
getVideoStreamDimensionsInfo,
|
getVideoStreamDimensionsInfo,
|
||||||
|
getVideoStreamFPS,
|
||||||
hasAudioStream
|
hasAudioStream
|
||||||
} from '@shared/extra-utils'
|
} from '@shared/extra-utils'
|
||||||
import { HttpStatusCode, VideoState } from '@shared/models'
|
import { HttpStatusCode, VideoState } from '@shared/models'
|
||||||
|
@ -727,6 +727,82 @@ describe('Test video transcoding', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Bounded transcoding', function () {
|
||||||
|
|
||||||
|
it('Should not generate an upper resolution than original file', async function () {
|
||||||
|
this.timeout(120_000)
|
||||||
|
|
||||||
|
await servers[0].config.updateExistingSubConfig({
|
||||||
|
newConfig: {
|
||||||
|
transcoding: {
|
||||||
|
enabled: true,
|
||||||
|
hls: { enabled: true },
|
||||||
|
webtorrent: { enabled: true },
|
||||||
|
resolutions: {
|
||||||
|
'0p': false,
|
||||||
|
'144p': false,
|
||||||
|
'240p': true,
|
||||||
|
'360p': false,
|
||||||
|
'480p': true,
|
||||||
|
'720p': false,
|
||||||
|
'1080p': false,
|
||||||
|
'1440p': false,
|
||||||
|
'2160p': false
|
||||||
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video', fixture: 'video_short.webm' })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const video = await servers[0].videos.get({ id: uuid })
|
||||||
|
const hlsFiles = video.streamingPlaylists[0].files
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(2)
|
||||||
|
expect(hlsFiles).to.have.lengthOf(2)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-array-sort-compare
|
||||||
|
const resolutions = getAllFiles(video).map(f => f.resolution.id).sort()
|
||||||
|
expect(resolutions).to.deep.equal([ 240, 240, 480, 480 ])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should only keep the original resolution if all resolutions are disabled', async function () {
|
||||||
|
this.timeout(120_000)
|
||||||
|
|
||||||
|
await servers[0].config.updateExistingSubConfig({
|
||||||
|
newConfig: {
|
||||||
|
transcoding: {
|
||||||
|
resolutions: {
|
||||||
|
'0p': false,
|
||||||
|
'144p': false,
|
||||||
|
'240p': false,
|
||||||
|
'360p': false,
|
||||||
|
'480p': false,
|
||||||
|
'720p': false,
|
||||||
|
'1080p': false,
|
||||||
|
'1440p': false,
|
||||||
|
'2160p': false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video', fixture: 'video_short.webm' })
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const video = await servers[0].videos.get({ id: uuid })
|
||||||
|
const hlsFiles = video.streamingPlaylists[0].files
|
||||||
|
|
||||||
|
expect(video.files).to.have.lengthOf(1)
|
||||||
|
expect(hlsFiles).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
expect(video.files[0].resolution.id).to.equal(720)
|
||||||
|
expect(hlsFiles[0].resolution.id).to.equal(720)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
await cleanupTests(servers)
|
await cleanupTests(servers)
|
||||||
})
|
})
|
||||||
|
|
|
@ -68,6 +68,9 @@ async function checkResolutionsInMasterPlaylist (options: {
|
||||||
|
|
||||||
expect(masterPlaylist).to.match(reg)
|
expect(masterPlaylist).to.match(reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const playlistsLength = masterPlaylist.split('\n').filter(line => line.startsWith('#EXT-X-STREAM-INF:BANDWIDTH='))
|
||||||
|
expect(playlistsLength).to.have.lengthOf(resolutions.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -117,6 +117,8 @@ export interface CustomConfig {
|
||||||
|
|
||||||
resolutions: ConfigResolutions & { '0p': boolean }
|
resolutions: ConfigResolutions & { '0p': boolean }
|
||||||
|
|
||||||
|
alwaysTranscodeOriginalResolution: boolean
|
||||||
|
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
}
|
}
|
||||||
|
@ -144,6 +146,7 @@ export interface CustomConfig {
|
||||||
threads: number
|
threads: number
|
||||||
profile: string
|
profile: string
|
||||||
resolutions: ConfigResolutions
|
resolutions: ConfigResolutions
|
||||||
|
alwaysTranscodeOriginalResolution: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ContextType } from '../activitypub/context'
|
import { ContextType } from '../activitypub/context'
|
||||||
import { VideoState } from '../videos'
|
import { VideoState } from '../videos'
|
||||||
import { VideoStudioTaskCut } from '../videos/studio'
|
|
||||||
import { VideoResolution } from '../videos/file/video-resolution.enum'
|
import { VideoResolution } from '../videos/file/video-resolution.enum'
|
||||||
|
import { VideoStudioTaskCut } from '../videos/studio'
|
||||||
import { SendEmailOptions } from './emailer.model'
|
import { SendEmailOptions } from './emailer.model'
|
||||||
|
|
||||||
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused'
|
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused'
|
||||||
|
@ -126,7 +126,6 @@ export interface HLSTranscodingPayload extends BaseTranscodingPayload {
|
||||||
copyCodecs: boolean
|
copyCodecs: boolean
|
||||||
|
|
||||||
hasAudio: boolean
|
hasAudio: boolean
|
||||||
isPortraitMode?: boolean
|
|
||||||
|
|
||||||
autoDeleteWebTorrentIfNeeded: boolean
|
autoDeleteWebTorrentIfNeeded: boolean
|
||||||
isMaxQuality: boolean
|
isMaxQuality: boolean
|
||||||
|
@ -138,8 +137,6 @@ export interface NewWebTorrentResolutionTranscodingPayload extends BaseTranscodi
|
||||||
|
|
||||||
hasAudio: boolean
|
hasAudio: boolean
|
||||||
createHLSIfNeeded: boolean
|
createHLSIfNeeded: boolean
|
||||||
|
|
||||||
isPortraitMode?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
|
export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
|
||||||
|
|
|
@ -310,6 +310,7 @@ export class ConfigCommand extends AbstractCommand {
|
||||||
'1440p': false,
|
'1440p': false,
|
||||||
'2160p': false
|
'2160p': false
|
||||||
},
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: true,
|
||||||
webtorrent: {
|
webtorrent: {
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
|
@ -339,7 +340,8 @@ export class ConfigCommand extends AbstractCommand {
|
||||||
'1080p': true,
|
'1080p': true,
|
||||||
'1440p': true,
|
'1440p': true,
|
||||||
'2160p': true
|
'2160p': true
|
||||||
}
|
},
|
||||||
|
alwaysTranscodeOriginalResolution: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
videoStudio: {
|
videoStudio: {
|
||||||
|
|
Loading…
Reference in New Issue