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,
|
||||
concurrency: CONCURRENCY_VALIDATOR,
|
||||
resolutions: {},
|
||||
alwaysTranscodeOriginalResolution: null,
|
||||
hls: {
|
||||
enabled: null
|
||||
},
|
||||
|
@ -197,7 +198,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
|||
enabled: null,
|
||||
threads: TRANSCODING_THREADS_VALIDATOR,
|
||||
profile: null,
|
||||
resolutions: {}
|
||||
resolutions: {},
|
||||
alwaysTranscodeOriginalResolution: null
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
<ng-container ngProjectAs="description" i18n>
|
||||
Small latency disables P2P and high latency can increase P2P ratio
|
||||
</ng-container>
|
||||
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
|
@ -115,8 +114,8 @@
|
|||
<label i18n for="liveTranscodingThreads">Live resolutions to generate</label>
|
||||
|
||||
<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">
|
||||
<my-peertube-checkbox
|
||||
[inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
|
||||
|
@ -127,8 +126,18 @@
|
|||
</ng-template>
|
||||
</my-peertube-checkbox>
|
||||
</div>
|
||||
|
||||
</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>
|
||||
|
||||
|
|
|
@ -111,7 +111,13 @@
|
|||
<label i18n>Resolutions to generate per enabled format</label>
|
||||
|
||||
<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.
|
||||
</span>
|
||||
|
||||
|
|
|
@ -403,6 +403,9 @@ transcoding:
|
|||
1440p: 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)
|
||||
# If you also enabled the hls format, it will multiply videos storage by 2
|
||||
# If disabled, breaks federation with PeerTube instances < 2.1
|
||||
|
@ -496,6 +499,9 @@ live:
|
|||
1440p: false
|
||||
2160p: false
|
||||
|
||||
# Also transcode original resolution, even if it's above your maximum enabled resolution
|
||||
always_transcode_original_resolution: true
|
||||
|
||||
video_studio:
|
||||
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
||||
# If enabled, users can create transcoding tasks as they wish
|
||||
|
|
|
@ -413,6 +413,9 @@ transcoding:
|
|||
1440p: 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)
|
||||
# If you also enabled the hls format, it will multiply videos storage by 2
|
||||
# If disabled, breaks federation with PeerTube instances < 2.1
|
||||
|
@ -506,6 +509,9 @@ live:
|
|||
1440p: false
|
||||
2160p: false
|
||||
|
||||
# Also transcode original resolution, even if it's above your maximum enabled resolution
|
||||
always_transcode_original_resolution: true
|
||||
|
||||
video_studio:
|
||||
# Enable video edition by users (cut, add intro/outro, add watermark etc)
|
||||
# If enabled, users can create transcoding tasks as they wish
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { program } from 'commander'
|
||||
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 { addTranscodingJob } from '@server/lib/video'
|
||||
import { VideoState, VideoTranscodingPayload } from '@shared/models'
|
||||
|
@ -53,7 +53,7 @@ async function run () {
|
|||
if (options.generateHls || CONFIG.TRANSCODING.WEBTORRENT.ENABLED === false) {
|
||||
const resolutionsEnabled = options.resolution
|
||||
? [ parseInt(options.resolution) ]
|
||||
: computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ])
|
||||
: computeResolutionsToTranscode({ inputResolution: maxResolution, type: 'vod', includeInputResolution: true })
|
||||
|
||||
for (const resolution of resolutionsEnabled) {
|
||||
dataInput.push({
|
||||
|
@ -61,8 +61,6 @@ async function run () {
|
|||
videoUUID: video.uuid,
|
||||
resolution,
|
||||
|
||||
// FIXME: check the file has audio and is not in portrait mode
|
||||
isPortraitMode: false,
|
||||
hasAudio: true,
|
||||
|
||||
copyCodecs: false,
|
||||
|
|
|
@ -31,8 +31,7 @@ async function run (path: string, cmd: any) {
|
|||
availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(),
|
||||
profile: 'default',
|
||||
|
||||
resolution: +cmd.resolution,
|
||||
isPortraitMode: false
|
||||
resolution: +cmd.resolution
|
||||
} as TranscodeVODOptions
|
||||
|
||||
let command = ffmpeg(options.inputPath)
|
||||
|
|
|
@ -227,6 +227,7 @@ function customConfig (): CustomConfig {
|
|||
'1440p': CONFIG.TRANSCODING.RESOLUTIONS['1440p'],
|
||||
'2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: CONFIG.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION,
|
||||
webtorrent: {
|
||||
enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
|
||||
},
|
||||
|
@ -256,7 +257,8 @@ function customConfig (): CustomConfig {
|
|||
'1080p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1080p'],
|
||||
'1440p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['1440p'],
|
||||
'2160p': CONFIG.LIVE.TRANSCODING.RESOLUTIONS['2160p']
|
||||
}
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import express from 'express'
|
||||
import { computeLowerResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
||||
import { computeResolutionsToTranscode } from '@server/helpers/ffmpeg'
|
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger'
|
||||
import { addTranscodingJob } from '@server/lib/video'
|
||||
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 { resolution: maxResolution, isPortraitMode, audioStream } = await video.probeMaxQualityFile()
|
||||
const { resolution: maxResolution, audioStream } = await video.probeMaxQualityFile()
|
||||
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',
|
||||
body
|
||||
)
|
||||
|
@ -50,7 +50,6 @@ async function createTranscoding (req: express.Request, res: express.Response) {
|
|||
type: 'new-resolution-to-hls',
|
||||
videoUUID: video.uuid,
|
||||
resolution,
|
||||
isPortraitMode,
|
||||
hasAudio: !!audioStream,
|
||||
copyCodecs: false,
|
||||
isNewVideo: false,
|
||||
|
@ -64,8 +63,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
|
|||
isNewVideo: false,
|
||||
resolution,
|
||||
hasAudio: !!audioStream,
|
||||
createHLSIfNeeded: false,
|
||||
isPortraitMode
|
||||
createHLSIfNeeded: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { AvailableEncoders, VideoResolution } from '@shared/models'
|
|||
import { logger, loggerTagsFactory } from '../logger'
|
||||
import { getFFmpeg, runCommand } from './ffmpeg-commons'
|
||||
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'
|
||||
|
||||
const lTags = loggerTagsFactory('ffmpeg')
|
||||
|
@ -27,8 +27,6 @@ interface BaseTranscodeVODOptions {
|
|||
|
||||
resolution: number
|
||||
|
||||
isPortraitMode?: boolean
|
||||
|
||||
job?: Job
|
||||
}
|
||||
|
||||
|
@ -115,13 +113,17 @@ export {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
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)
|
||||
|
||||
let scaleFilterValue: string
|
||||
|
||||
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=-2:h=${options.resolution}`
|
||||
}
|
||||
|
|
|
@ -90,15 +90,21 @@ async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
|
|||
// 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'
|
||||
? CONFIG.TRANSCODING.RESOLUTIONS
|
||||
: CONFIG.LIVE.TRANSCODING.RESOLUTIONS
|
||||
|
||||
const resolutionsEnabled: number[] = []
|
||||
const resolutionsEnabled = new Set<number>()
|
||||
|
||||
// Put in the order we want to proceed jobs
|
||||
const resolutions: VideoResolution[] = [
|
||||
const availableResolutions: VideoResolution[] = [
|
||||
VideoResolution.H_NOVIDEO,
|
||||
VideoResolution.H_480P,
|
||||
VideoResolution.H_360P,
|
||||
|
@ -110,13 +116,17 @@ function computeLowerResolutionsToTranscode (videoFileResolution: number, type:
|
|||
VideoResolution.H_4K
|
||||
]
|
||||
|
||||
for (const resolution of resolutions) {
|
||||
if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
|
||||
resolutionsEnabled.push(resolution)
|
||||
for (const resolution of availableResolutions) {
|
||||
if (configResolutions[resolution + 'p'] === true && inputResolution > resolution) {
|
||||
resolutionsEnabled.add(resolution)
|
||||
}
|
||||
}
|
||||
|
||||
return resolutionsEnabled
|
||||
if (includeInputResolution) {
|
||||
resolutionsEnabled.add(inputResolution)
|
||||
}
|
||||
|
||||
return Array.from(resolutionsEnabled)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -224,7 +234,7 @@ export {
|
|||
computeFPS,
|
||||
getClosestFramerateStandard,
|
||||
|
||||
computeLowerResolutionsToTranscode,
|
||||
computeResolutionsToTranscode,
|
||||
|
||||
canDoQuickTranscode,
|
||||
canDoQuickVideoTranscode,
|
||||
|
|
|
@ -30,7 +30,7 @@ function checkMissedConfig () {
|
|||
'transcoding.profile', 'transcoding.concurrency',
|
||||
'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.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',
|
||||
'auto_blacklist.videos.of_users.enabled', 'trending.videos.interval_days',
|
||||
'client.videos.miniature.display_author_avatar',
|
||||
|
@ -59,7 +59,7 @@ function checkMissedConfig () {
|
|||
'live.transcoding.enabled', 'live.transcoding.threads', 'live.transcoding.profile',
|
||||
'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.1440p', 'live.transcoding.resolutions.2160p'
|
||||
'live.transcoding.resolutions.1440p', 'live.transcoding.resolutions.2160p', 'live.transcoding.always_transcode_original_resolution'
|
||||
]
|
||||
|
||||
const requiredAlternatives = [
|
||||
|
|
|
@ -309,6 +309,7 @@ const CONFIG = {
|
|||
get THREADS () { return config.get<number>('transcoding.threads') },
|
||||
get CONCURRENCY () { return config.get<number>('transcoding.concurrency') },
|
||||
get PROFILE () { return config.get<string>('transcoding.profile') },
|
||||
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('transcoding.always_transcode_original_resolution') },
|
||||
RESOLUTIONS: {
|
||||
get '0p' () { return config.get<boolean>('transcoding.resolutions.0p') },
|
||||
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 PROFILE () { return config.get<string>('live.transcoding.profile') },
|
||||
|
||||
get ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION () { return config.get<boolean>('live.transcoding.always_transcode_original_resolution') },
|
||||
|
||||
RESOLUTIONS: {
|
||||
get '144p' () { return config.get<boolean>('live.transcoding.resolutions.144p') },
|
||||
get '240p' () { return config.get<boolean>('live.transcoding.resolutions.240p') },
|
||||
|
|
|
@ -213,13 +213,12 @@ async function assignReplayFilesToVideo (options: {
|
|||
const probe = await ffprobePromise(concatenatedTsFilePath)
|
||||
const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe)
|
||||
|
||||
const { resolution, isPortraitMode } = await getVideoStreamDimensionsInfo(concatenatedTsFilePath, probe)
|
||||
const { resolution } = await getVideoStreamDimensionsInfo(concatenatedTsFilePath, probe)
|
||||
|
||||
const { resolutionPlaylistPath: outputPath } = await generateHlsPlaylistResolutionFromTS({
|
||||
video,
|
||||
concatenatedTsFilePath,
|
||||
resolution,
|
||||
isPortraitMode,
|
||||
isAAC: audioStream?.codec_name === 'aac'
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Job } from 'bull'
|
||||
import { TranscodeVODOptionsType } from '@server/helpers/ffmpeg'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
import { addTranscodingJob, getTranscodingJobPriority } from '@server/lib/video'
|
||||
import { VideoPathManager } from '@server/lib/video-path-manager'
|
||||
import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state'
|
||||
|
@ -16,7 +17,7 @@ import {
|
|||
VideoTranscodingPayload
|
||||
} from '@shared/models'
|
||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||
import { computeLowerResolutionsToTranscode } from '../../../helpers/ffmpeg'
|
||||
import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg'
|
||||
import { logger, loggerTagsFactory } from '../../../helpers/logger'
|
||||
import { CONFIG } from '../../../initializers/config'
|
||||
import { VideoModel } from '../../../models/video/video'
|
||||
|
@ -26,7 +27,6 @@ import {
|
|||
optimizeOriginalVideofile,
|
||||
transcodeNewWebTorrentResolution
|
||||
} from '../../transcoding/transcoding'
|
||||
import { Hooks } from '@server/lib/plugins/hooks'
|
||||
|
||||
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,
|
||||
resolution: payload.resolution,
|
||||
copyCodecs: payload.copyCodecs,
|
||||
isPortraitMode: payload.isPortraitMode || false,
|
||||
job
|
||||
})
|
||||
})
|
||||
|
@ -117,7 +116,7 @@ async function handleNewWebTorrentResolutionJob (
|
|||
) {
|
||||
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))
|
||||
|
||||
|
@ -127,7 +126,7 @@ async function handleNewWebTorrentResolutionJob (
|
|||
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))
|
||||
|
||||
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))
|
||||
|
||||
|
@ -137,7 +136,7 @@ async function handleWebTorrentMergeAudioJob (job: Job, payload: MergeAudioTrans
|
|||
async function handleWebTorrentOptimizeJob (job: Job, payload: OptimizeTranscodingPayload, video: MVideoFullLight, user: MUserId) {
|
||||
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))
|
||||
|
||||
|
@ -161,7 +160,6 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
|
|||
video,
|
||||
user,
|
||||
videoFileResolution: payload.resolution,
|
||||
isPortraitMode: payload.isPortraitMode,
|
||||
hasAudio: payload.hasAudio,
|
||||
isNewVideo: payload.isNewVideo ?? true,
|
||||
type: 'hls'
|
||||
|
@ -178,7 +176,7 @@ async function onVideoFirstWebTorrentTranscoding (
|
|||
transcodeType: TranscodeVODOptionsType,
|
||||
user: MUserId
|
||||
) {
|
||||
const { resolution, isPortraitMode, audioStream } = await videoArg.probeMaxQualityFile()
|
||||
const { resolution, audioStream } = await videoArg.probeMaxQualityFile()
|
||||
|
||||
// Maybe the video changed in database, refresh it
|
||||
const videoDatabase = await VideoModel.loadFull(videoArg.uuid)
|
||||
|
@ -189,7 +187,6 @@ async function onVideoFirstWebTorrentTranscoding (
|
|||
const originalFileHLSPayload = {
|
||||
...payload,
|
||||
|
||||
isPortraitMode,
|
||||
hasAudio: !!audioStream,
|
||||
resolution: videoDatabase.getMaxQualityFile().resolution,
|
||||
// If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues
|
||||
|
@ -202,7 +199,6 @@ async function onVideoFirstWebTorrentTranscoding (
|
|||
user,
|
||||
videoFileResolution: resolution,
|
||||
hasAudio: !!audioStream,
|
||||
isPortraitMode,
|
||||
type: 'webtorrent',
|
||||
isNewVideo: payload.isNewVideo ?? true
|
||||
})
|
||||
|
@ -235,7 +231,6 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
|||
videoUUID: string
|
||||
resolution: number
|
||||
hasAudio: boolean
|
||||
isPortraitMode?: boolean
|
||||
copyCodecs: boolean
|
||||
isMaxQuality: boolean
|
||||
isNewVideo?: boolean
|
||||
|
@ -250,7 +245,7 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
|
|||
type: 'new-resolution-to-hls',
|
||||
autoDeleteWebTorrentIfNeeded: true,
|
||||
|
||||
...pick(payload, [ 'videoUUID', 'resolution', 'isPortraitMode', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ])
|
||||
...pick(payload, [ 'videoUUID', 'resolution', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ])
|
||||
}
|
||||
|
||||
await addTranscodingJob(hlsTranscodingPayload, jobOptions)
|
||||
|
@ -262,16 +257,15 @@ async function createLowerResolutionsJobs (options: {
|
|||
video: MVideoFullLight
|
||||
user: MUserId
|
||||
videoFileResolution: number
|
||||
isPortraitMode: boolean
|
||||
hasAudio: boolean
|
||||
isNewVideo: boolean
|
||||
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
|
||||
const resolutionsEnabled = await Hooks.wrapObject(
|
||||
computeLowerResolutionsToTranscode(videoFileResolution, 'vod'),
|
||||
computeResolutionsToTranscode({ inputResolution: videoFileResolution, type: 'vod', includeInputResolution: false }),
|
||||
'filter:transcoding.auto.lower-resolutions-to-transcode.result',
|
||||
options
|
||||
)
|
||||
|
@ -289,7 +283,6 @@ async function createLowerResolutionsJobs (options: {
|
|||
type: 'new-resolution-to-webtorrent',
|
||||
videoUUID: video.uuid,
|
||||
resolution,
|
||||
isPortraitMode,
|
||||
hasAudio,
|
||||
createHLSIfNeeded: true,
|
||||
isNewVideo
|
||||
|
@ -303,7 +296,6 @@ async function createLowerResolutionsJobs (options: {
|
|||
type: 'new-resolution-to-hls',
|
||||
videoUUID: video.uuid,
|
||||
resolution,
|
||||
isPortraitMode,
|
||||
hasAudio,
|
||||
copyCodecs: false,
|
||||
isMaxQuality: false,
|
||||
|
|
|
@ -4,7 +4,7 @@ import { createServer, Server } from 'net'
|
|||
import { join } from 'path'
|
||||
import { createServer as createServerTLS, Server as ServerTLS } from 'tls'
|
||||
import {
|
||||
computeLowerResolutionsToTranscode,
|
||||
computeResolutionsToTranscode,
|
||||
ffprobePromise,
|
||||
getLiveSegmentTime,
|
||||
getVideoStreamBitrate,
|
||||
|
@ -26,10 +26,10 @@ import { federateVideoIfNeeded } from '../activitypub/videos'
|
|||
import { JobQueue } from '../job-queue'
|
||||
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '../paths'
|
||||
import { PeerTubeSocket } from '../peertube-socket'
|
||||
import { Hooks } from '../plugins/hooks'
|
||||
import { LiveQuotaStore } from './live-quota-store'
|
||||
import { cleanupPermanentLive } from './live-utils'
|
||||
import { MuxingSession } from './shared'
|
||||
import { Hooks } from '../plugins/hooks'
|
||||
|
||||
const NodeRtmpSession = require('node-media-server/src/node_rtmp_session')
|
||||
const context = require('node-media-server/src/node_core_ctx')
|
||||
|
@ -456,11 +456,17 @@ class LiveManager {
|
|||
}
|
||||
|
||||
private buildAllResolutionsToTranscode (originResolution: number) {
|
||||
const includeInputResolution = CONFIG.LIVE.TRANSCODING.ALWAYS_TRANSCODE_ORIGINAL_RESOLUTION
|
||||
|
||||
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> {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { VideoResolution, VideoStorage } from '../../../shared/models/videos'
|
|||
import {
|
||||
buildFileMetadata,
|
||||
canDoQuickTranscode,
|
||||
computeResolutionsToTranscode,
|
||||
getVideoStreamDuration,
|
||||
getVideoStreamFPS,
|
||||
transcodeVOD,
|
||||
|
@ -32,7 +33,13 @@ import { VideoTranscodingProfilesManager } from './default-transcoding-profiles'
|
|||
*/
|
||||
|
||||
// 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 newExtname = '.mp4'
|
||||
|
||||
|
@ -43,7 +50,7 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
|||
? 'quick-transcode'
|
||||
: 'video'
|
||||
|
||||
const resolution = toEven(inputVideoFile.resolution)
|
||||
const resolution = buildOriginalFileResolution(inputVideoFile.resolution)
|
||||
|
||||
const transcodeOptions: TranscodeVODOptions = {
|
||||
type: transcodeType,
|
||||
|
@ -63,6 +70,7 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
|||
await transcodeVOD(transcodeOptions)
|
||||
|
||||
// Important to do this before getVideoFilename() to take in account the new filename
|
||||
inputVideoFile.resolution = resolution
|
||||
inputVideoFile.extname = newExtname
|
||||
inputVideoFile.filename = generateWebTorrentVideoFilename(resolution, newExtname)
|
||||
inputVideoFile.storage = VideoStorage.FILE_SYSTEM
|
||||
|
@ -76,17 +84,22 @@ function optimizeOriginalVideofile (video: MVideoFullLight, inputVideoFile: MVid
|
|||
})
|
||||
}
|
||||
|
||||
// Transcode the original video file to a lower resolution
|
||||
// We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
|
||||
function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: VideoResolution, isPortrait: boolean, job: Job) {
|
||||
// Transcode the original video file to a lower resolution compatible with WebTorrent
|
||||
function transcodeNewWebTorrentResolution (options: {
|
||||
video: MVideoFullLight
|
||||
resolution: VideoResolution
|
||||
job: Job
|
||||
}) {
|
||||
const { video, resolution, job } = options
|
||||
|
||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||
const extname = '.mp4'
|
||||
const newExtname = '.mp4'
|
||||
|
||||
return VideoPathManager.Instance.makeAvailableVideoFile(video.getMaxQualityFile().withVideoOrPlaylist(video), async videoInputPath => {
|
||||
const newVideoFile = new VideoFileModel({
|
||||
resolution,
|
||||
extname,
|
||||
filename: generateWebTorrentVideoFilename(resolution, extname),
|
||||
extname: newExtname,
|
||||
filename: generateWebTorrentVideoFilename(resolution, newExtname),
|
||||
size: 0,
|
||||
videoId: video.id
|
||||
})
|
||||
|
@ -117,7 +130,6 @@ function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: V
|
|||
profile: CONFIG.TRANSCODING.PROFILE,
|
||||
|
||||
resolution,
|
||||
isPortraitMode: isPortrait,
|
||||
|
||||
job
|
||||
}
|
||||
|
@ -129,7 +141,13 @@ function transcodeNewWebTorrentResolution (video: MVideoFullLight, resolution: V
|
|||
}
|
||||
|
||||
// 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 newExtname = '.mp4'
|
||||
|
||||
|
@ -188,13 +206,11 @@ async function generateHlsPlaylistResolutionFromTS (options: {
|
|||
video: MVideo
|
||||
concatenatedTsFilePath: string
|
||||
resolution: VideoResolution
|
||||
isPortraitMode: boolean
|
||||
isAAC: boolean
|
||||
}) {
|
||||
return generateHlsPlaylistCommon({
|
||||
video: options.video,
|
||||
resolution: options.resolution,
|
||||
isPortraitMode: options.isPortraitMode,
|
||||
inputPath: options.concatenatedTsFilePath,
|
||||
type: 'hls-from-ts' as 'hls-from-ts',
|
||||
isAAC: options.isAAC
|
||||
|
@ -207,14 +223,12 @@ function generateHlsPlaylistResolution (options: {
|
|||
videoInputPath: string
|
||||
resolution: VideoResolution
|
||||
copyCodecs: boolean
|
||||
isPortraitMode: boolean
|
||||
job?: Job
|
||||
}) {
|
||||
return generateHlsPlaylistCommon({
|
||||
video: options.video,
|
||||
resolution: options.resolution,
|
||||
copyCodecs: options.copyCodecs,
|
||||
isPortraitMode: options.isPortraitMode,
|
||||
inputPath: options.videoInputPath,
|
||||
type: 'hls' as 'hls',
|
||||
job: options.job
|
||||
|
@ -267,11 +281,10 @@ async function generateHlsPlaylistCommon (options: {
|
|||
resolution: VideoResolution
|
||||
copyCodecs?: boolean
|
||||
isAAC?: boolean
|
||||
isPortraitMode: boolean
|
||||
|
||||
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 videoTranscodedBasePath = join(transcodeDirectory, type)
|
||||
|
@ -292,7 +305,6 @@ async function generateHlsPlaylistCommon (options: {
|
|||
|
||||
resolution,
|
||||
copyCodecs,
|
||||
isPortraitMode,
|
||||
|
||||
isAAC,
|
||||
|
||||
|
@ -350,3 +362,12 @@ async function generateHlsPlaylistCommon (options: {
|
|||
|
||||
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.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.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.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.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.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,
|
||||
'2160p': false
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: false,
|
||||
webtorrent: {
|
||||
enabled: true
|
||||
},
|
||||
|
@ -145,7 +146,8 @@ describe('Test config API validators', function () {
|
|||
'1080p': true,
|
||||
'1440p': true,
|
||||
'2160p': true
|
||||
}
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: false
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'mocha'
|
|||
import * as chai from 'chai'
|
||||
import { basename, join } from 'path'
|
||||
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 {
|
||||
HttpStatusCode,
|
||||
|
@ -468,7 +468,7 @@ describe('Test live', function () {
|
|||
await waitUntilLivePublishedOnAllServers(servers, liveVideoId)
|
||||
await waitJobs(servers)
|
||||
|
||||
await testVideoResolutions(liveVideoId, resolutions)
|
||||
await testVideoResolutions(liveVideoId, resolutions.concat([ 720 ]))
|
||||
|
||||
await stopFfmpeg(ffmpegCommand)
|
||||
})
|
||||
|
@ -580,10 +580,73 @@ describe('Test live', function () {
|
|||
}
|
||||
})
|
||||
|
||||
it('Should correctly have cleaned up the live files', async function () {
|
||||
this.timeout(30000)
|
||||
it('Should not generate an upper resolution than original file', async function () {
|
||||
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['1440p']).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.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['1440p']).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
|
||||
|
||||
|
@ -181,6 +183,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
|||
expect(data.transcoding.resolutions['720p']).to.be.false
|
||||
expect(data.transcoding.resolutions['1080p']).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.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['1080p']).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
|
||||
|
||||
|
@ -318,6 +322,7 @@ const newCustomConfig: CustomConfig = {
|
|||
'1440p': false,
|
||||
'2160p': false
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: false,
|
||||
webtorrent: {
|
||||
enabled: true
|
||||
},
|
||||
|
@ -347,7 +352,8 @@ const newCustomConfig: CustomConfig = {
|
|||
'1080p': true,
|
||||
'1440p': true,
|
||||
'2160p': true
|
||||
}
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: false
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
|
@ -7,11 +7,11 @@ import { canDoQuickTranscode } from '@server/helpers/ffmpeg'
|
|||
import { generateHighBitrateVideo, generateVideoWithFramerate, getAllFiles } from '@server/tests/shared'
|
||||
import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
|
||||
import {
|
||||
getAudioStream,
|
||||
buildFileMetadata,
|
||||
getAudioStream,
|
||||
getVideoStreamBitrate,
|
||||
getVideoStreamFPS,
|
||||
getVideoStreamDimensionsInfo,
|
||||
getVideoStreamFPS,
|
||||
hasAudioStream
|
||||
} from '@shared/extra-utils'
|
||||
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 () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
|
|
|
@ -68,6 +68,9 @@ async function checkResolutionsInMasterPlaylist (options: {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -117,6 +117,8 @@ export interface CustomConfig {
|
|||
|
||||
resolutions: ConfigResolutions & { '0p': boolean }
|
||||
|
||||
alwaysTranscodeOriginalResolution: boolean
|
||||
|
||||
webtorrent: {
|
||||
enabled: boolean
|
||||
}
|
||||
|
@ -144,6 +146,7 @@ export interface CustomConfig {
|
|||
threads: number
|
||||
profile: string
|
||||
resolutions: ConfigResolutions
|
||||
alwaysTranscodeOriginalResolution: boolean
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ContextType } from '../activitypub/context'
|
||||
import { VideoState } from '../videos'
|
||||
import { VideoStudioTaskCut } from '../videos/studio'
|
||||
import { VideoResolution } from '../videos/file/video-resolution.enum'
|
||||
import { VideoStudioTaskCut } from '../videos/studio'
|
||||
import { SendEmailOptions } from './emailer.model'
|
||||
|
||||
export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' | 'paused'
|
||||
|
@ -126,7 +126,6 @@ export interface HLSTranscodingPayload extends BaseTranscodingPayload {
|
|||
copyCodecs: boolean
|
||||
|
||||
hasAudio: boolean
|
||||
isPortraitMode?: boolean
|
||||
|
||||
autoDeleteWebTorrentIfNeeded: boolean
|
||||
isMaxQuality: boolean
|
||||
|
@ -138,8 +137,6 @@ export interface NewWebTorrentResolutionTranscodingPayload extends BaseTranscodi
|
|||
|
||||
hasAudio: boolean
|
||||
createHLSIfNeeded: boolean
|
||||
|
||||
isPortraitMode?: boolean
|
||||
}
|
||||
|
||||
export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
|
||||
|
|
|
@ -310,6 +310,7 @@ export class ConfigCommand extends AbstractCommand {
|
|||
'1440p': false,
|
||||
'2160p': false
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: true,
|
||||
webtorrent: {
|
||||
enabled: true
|
||||
},
|
||||
|
@ -339,7 +340,8 @@ export class ConfigCommand extends AbstractCommand {
|
|||
'1080p': true,
|
||||
'1440p': true,
|
||||
'2160p': true
|
||||
}
|
||||
},
|
||||
alwaysTranscodeOriginalResolution: true
|
||||
}
|
||||
},
|
||||
videoStudio: {
|
||||
|
|
Loading…
Reference in New Issue