From 679c12e69c9f3a2d003ee3abe8b8da49f25b2bd3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 6 Aug 2021 13:35:25 +0200 Subject: Improve target bitrate calculation --- server/controllers/api/videos/upload.ts | 2 +- server/helpers/ffmpeg-utils.ts | 35 +++++++++++++------- server/helpers/ffprobe-utils.ts | 16 +++++---- .../migrations/0075-video-resolutions.ts | 18 +++++----- server/lib/job-queue/handlers/video-file-import.ts | 10 +++--- server/lib/job-queue/handlers/video-import.ts | 6 ++-- server/lib/job-queue/handlers/video-live-ending.ts | 4 +-- server/lib/job-queue/handlers/video-transcoding.ts | 4 +-- server/lib/live/live-manager.ts | 13 +++++--- server/lib/live/shared/muxing-session.ts | 9 ++++- .../lib/transcoding/video-transcoding-profiles.ts | 32 ++++++++---------- server/tests/api/live/live.ts | 2 +- server/tests/api/videos/video-transcoder.ts | 30 +++++------------ server/tests/cli/optimize-old-videos.ts | 20 ++++-------- server/tests/cli/print-transcode-command.ts | 10 +++--- server/tests/helpers/core-utils.ts | 38 ++++++++++++++++++++++ 16 files changed, 143 insertions(+), 106 deletions(-) (limited to 'server') diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts index 408f677ff..89f50714d 100644 --- a/server/controllers/api/videos/upload.ts +++ b/server/controllers/api/videos/upload.ts @@ -239,7 +239,7 @@ async function buildNewFile (video: MVideo, videoPhysicalFile: express.VideoUplo videoFile.resolution = DEFAULT_AUDIO_RESOLUTION } else { videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path) - videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution + videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).resolution } videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname) diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 7f84a049f..830625cc6 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -3,10 +3,18 @@ import * as ffmpeg from 'fluent-ffmpeg' import { readFile, remove, writeFile } from 'fs-extra' import { dirname, join } from 'path' import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants' -import { AvailableEncoders, EncoderOptions, EncoderOptionsBuilder, EncoderProfile, VideoResolution } from '../../shared/models/videos' +import { pick } from '@shared/core-utils' +import { + AvailableEncoders, + EncoderOptions, + EncoderOptionsBuilder, + EncoderOptionsBuilderParams, + EncoderProfile, + VideoResolution +} from '../../shared/models/videos' import { CONFIG } from '../initializers/config' import { execPromise, promisify0 } from './core-utils' -import { computeFPS, ffprobePromise, getAudioStream, getVideoFileBitrate, getVideoFileFPS } from './ffprobe-utils' +import { computeFPS, ffprobePromise, getAudioStream, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from './ffprobe-utils' import { processImage } from './image-utils' import { logger } from './logger' @@ -217,13 +225,16 @@ async function getLiveTranscodingCommand (options: { masterPlaylistName: string resolutions: number[] + + // Input information fps: number bitrate: number + ratio: number availableEncoders: AvailableEncoders profile: string }) { - const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName } = options + const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options const input = rtmpUrl const command = getFFmpeg(input, 'live') @@ -253,9 +264,12 @@ async function getLiveTranscodingCommand (options: { availableEncoders, profile, - fps: resolutionFPS, inputBitrate: bitrate, + inputRatio: ratio, + resolution, + fps: resolutionFPS, + streamNum: i, videoType: 'live' as 'live' } @@ -502,7 +516,7 @@ function getHLSVideoPath (options: HLSTranscodeOptions | HLSFromTSTranscodeOptio // Run encoder builder depending on available encoders // Try encoders by priority: if the encoder is available, run the chosen profile or fallback to the default one // If the default one does not exist, check the next encoder -async function getEncoderBuilderResult (options: { +async function getEncoderBuilderResult (options: EncoderOptionsBuilderParams & { streamType: 'video' | 'audio' input: string @@ -510,13 +524,8 @@ async function getEncoderBuilderResult (options: { profile: string videoType: 'vod' | 'live' - - resolution: number - inputBitrate: number - fps?: number - streamNum?: number }) { - const { availableEncoders, input, profile, resolution, streamType, fps, inputBitrate, streamNum, videoType } = options + const { availableEncoders, profile, streamType, videoType } = options const encodersToTry = availableEncoders.encodersToTry[videoType][streamType] const encoders = availableEncoders.available[videoType] @@ -546,7 +555,7 @@ async function getEncoderBuilderResult (options: { } } - const result = await builder({ input, resolution, inputBitrate, fps, streamNum }) + const result = await builder(pick(options, [ 'input', 'resolution', 'inputBitrate', 'fps', 'inputRatio', 'streamNum' ])) return { result, @@ -581,6 +590,7 @@ async function presetVideo (options: { // Audio encoder const parsedAudio = await getAudioStream(input, probe) const bitrate = await getVideoFileBitrate(input, probe) + const { ratio } = await getVideoFileResolution(input, probe) let streamsToProcess: StreamType[] = [ 'audio', 'video' ] @@ -600,6 +610,7 @@ async function presetVideo (options: { profile, fps, inputBitrate: bitrate, + inputRatio: ratio, videoType: 'vod' as 'vod' }) diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts index bc87e49b1..e58444b07 100644 --- a/server/helpers/ffprobe-utils.ts +++ b/server/helpers/ffprobe-utils.ts @@ -1,5 +1,6 @@ import * as ffmpeg from 'fluent-ffmpeg' -import { getMaxBitrate, VideoFileMetadata, VideoResolution } from '../../shared/models/videos' +import { getMaxBitrate } from '@shared/core-utils' +import { VideoFileMetadata, VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos' import { CONFIG } from '../initializers/config' import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' import { logger } from './logger' @@ -75,7 +76,7 @@ function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) { } } -async function getVideoStreamSize (path: string, existingProbe?: ffmpeg.FfprobeData) { +async function getVideoStreamSize (path: string, existingProbe?: ffmpeg.FfprobeData): Promise<{ width: number, height: number }> { const videoStream = await getVideoStreamFromFile(path, existingProbe) return videoStream === null @@ -146,7 +147,10 @@ async function getVideoFileResolution (path: string, existingProbe?: ffmpeg.Ffpr const size = await getVideoStreamSize(path, existingProbe) return { - videoFileResolution: Math.min(size.height, size.width), + width: size.width, + height: size.height, + ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width), + resolution: Math.min(size.height, size.width), isPortraitMode: size.height > size.width } } @@ -243,7 +247,7 @@ async function canDoQuickVideoTranscode (path: string, probe?: ffmpeg.FfprobeDat const videoStream = await getVideoStreamFromFile(path, probe) const fps = await getVideoFileFPS(path, probe) const bitRate = await getVideoFileBitrate(path, probe) - const resolution = await getVideoFileResolution(path, probe) + const resolutionData = await getVideoFileResolution(path, probe) // If ffprobe did not manage to guess the bitrate if (!bitRate) return false @@ -253,7 +257,7 @@ async function canDoQuickVideoTranscode (path: string, probe?: ffmpeg.FfprobeDat if (videoStream['codec_name'] !== 'h264') return false if (videoStream['pix_fmt'] !== 'yuv420p') return false if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false - if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false + if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false return true } @@ -278,7 +282,7 @@ async function canDoQuickAudioTranscode (path: string, probe?: ffmpeg.FfprobeDat return true } -function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number { +function getClosestFramerateStandard > (fps: number, type: K) { return VIDEO_TRANSCODING_FPS[type].slice(0) .sort((a, b) => fps % a - fps % b)[0] } diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts index 496125adb..6e8e47acb 100644 --- a/server/initializers/migrations/0075-video-resolutions.ts +++ b/server/initializers/migrations/0075-video-resolutions.ts @@ -27,17 +27,15 @@ function up (utils: { const ext = matches[2] const p = getVideoFileResolution(join(videoFileDir, videoFile)) - .then(height => { + .then(async ({ resolution }) => { const oldTorrentName = uuid + '.torrent' - const newTorrentName = uuid + '-' + height + '.torrent' - return rename(join(torrentDir, oldTorrentName), join(torrentDir, newTorrentName)).then(() => height) - }) - .then(height => { - const newVideoFileName = uuid + '-' + height + '.' + ext - return rename(join(videoFileDir, videoFile), join(videoFileDir, newVideoFileName)).then(() => height) - }) - .then(height => { - const query = 'UPDATE "VideoFiles" SET "resolution" = ' + height + + const newTorrentName = uuid + '-' + resolution + '.torrent' + await rename(join(torrentDir, oldTorrentName), join(torrentDir, newTorrentName)).then(() => resolution) + + const newVideoFileName = uuid + '-' + resolution + '.' + ext + await rename(join(videoFileDir, videoFile), join(videoFileDir, newVideoFileName)).then(() => resolution) + + const query = 'UPDATE "VideoFiles" SET "resolution" = ' + resolution + ' WHERE "videoId" = (SELECT "id" FROM "Videos" WHERE "uuid" = \'' + uuid + '\')' return utils.sequelize.query(query) }) diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 4d199f247..2f4abf730 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts @@ -32,7 +32,7 @@ async function processVideoFileImport (job: Bull.Job) { const newResolutionPayload = { type: 'new-resolution-to-webtorrent' as 'new-resolution-to-webtorrent', videoUUID: video.uuid, - resolution: data.videoFileResolution, + resolution: data.resolution, isPortraitMode: data.isPortraitMode, copyCodecs: false, isNewVideo: false @@ -51,13 +51,13 @@ export { // --------------------------------------------------------------------------- async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) { - const { videoFileResolution } = await getVideoFileResolution(inputFilePath) + const { resolution } = await getVideoFileResolution(inputFilePath) const { size } = await stat(inputFilePath) const fps = await getVideoFileFPS(inputFilePath) const fileExt = getLowercaseExtension(inputFilePath) - const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === videoFileResolution) + const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === resolution) if (currentVideoFile) { // Remove old file and old torrent @@ -69,9 +69,9 @@ async function updateVideoFile (video: MVideoFullLight, inputFilePath: string) { } const newVideoFile = new VideoFileModel({ - resolution: videoFileResolution, + resolution, extname: fileExt, - filename: generateWebTorrentVideoFilename(videoFileResolution, fileExt), + filename: generateWebTorrentVideoFilename(resolution, fileExt), size, fps, videoId: video.id diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 5fd2039b1..fec553f2b 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -114,7 +114,7 @@ async function processFile (downloader: () => Promise, videoImport: MVid throw new Error('The user video quota is exceeded with this video to import.') } - const { videoFileResolution } = await getVideoFileResolution(tempVideoPath) + const { resolution } = await getVideoFileResolution(tempVideoPath) const fps = await getVideoFileFPS(tempVideoPath) const duration = await getDurationFromVideoFile(tempVideoPath) @@ -122,9 +122,9 @@ async function processFile (downloader: () => Promise, videoImport: MVid const fileExt = getLowercaseExtension(tempVideoPath) const videoFileData = { extname: fileExt, - resolution: videoFileResolution, + resolution, size: stats.size, - filename: generateWebTorrentVideoFilename(videoFileResolution, fileExt), + filename: generateWebTorrentVideoFilename(resolution, fileExt), fps, videoId: videoImport.videoId } diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 386ccdc7b..aa5bd573a 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts @@ -96,12 +96,12 @@ async function saveLive (video: MVideo, live: MVideoLive, streamingPlaylist: MSt const probe = await ffprobePromise(concatenatedTsFilePath) const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe) - const { videoFileResolution, isPortraitMode } = await getVideoFileResolution(concatenatedTsFilePath, probe) + const { resolution, isPortraitMode } = await getVideoFileResolution(concatenatedTsFilePath, probe) const outputPath = await generateHlsPlaylistResolutionFromTS({ video: videoWithFiles, concatenatedTsFilePath, - resolution: videoFileResolution, + resolution, isPortraitMode, isAAC: audioStream?.codec_name === 'aac' }) diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 2abb351ce..876d1460c 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -136,7 +136,7 @@ async function onVideoFileOptimizer ( if (videoArg === undefined) return undefined // Outside the transaction (IO on disk) - const { videoFileResolution, isPortraitMode } = await videoArg.getMaxQualityResolution() + const { resolution, isPortraitMode } = await videoArg.getMaxQualityResolution() // Maybe the video changed in database, refresh it const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) @@ -155,7 +155,7 @@ async function onVideoFileOptimizer ( }) const hasHls = await createHlsJobIfEnabled(user, originalFileHLSPayload) - const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, videoFileResolution, isPortraitMode, 'webtorrent') + const hasNewResolutions = await createLowerResolutionsJobs(videoDatabase, user, resolution, isPortraitMode, 'webtorrent') if (!hasHls && !hasNewResolutions) { // No transcoding to do, it's now published diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts index b19ecef6f..2a429fb33 100644 --- a/server/lib/live/live-manager.ts +++ b/server/lib/live/live-manager.ts @@ -202,7 +202,7 @@ class LiveManager { const now = Date.now() const probe = await ffprobePromise(rtmpUrl) - const [ { videoFileResolution }, fps, bitrate ] = await Promise.all([ + const [ { resolution, ratio }, fps, bitrate ] = await Promise.all([ getVideoFileResolution(rtmpUrl, probe), getVideoFileFPS(rtmpUrl, probe), getVideoFileBitrate(rtmpUrl, probe) @@ -210,13 +210,13 @@ class LiveManager { logger.info( '%s probing took %d ms (bitrate: %d, fps: %d, resolution: %d)', - rtmpUrl, Date.now() - now, bitrate, fps, videoFileResolution, lTags(sessionId, video.uuid) + rtmpUrl, Date.now() - now, bitrate, fps, resolution, lTags(sessionId, video.uuid) ) - const allResolutions = this.buildAllResolutionsToTranscode(videoFileResolution) + const allResolutions = this.buildAllResolutionsToTranscode(resolution) logger.info( - 'Will mux/transcode live video of original resolution %d.', videoFileResolution, + 'Will mux/transcode live video of original resolution %d.', resolution, { allResolutions, ...lTags(sessionId, video.uuid) } ) @@ -229,6 +229,7 @@ class LiveManager { rtmpUrl, fps, bitrate, + ratio, allResolutions }) } @@ -240,9 +241,10 @@ class LiveManager { rtmpUrl: string fps: number bitrate: number + ratio: number allResolutions: number[] }) { - const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, bitrate, rtmpUrl } = options + const { sessionId, videoLive, streamingPlaylist, allResolutions, fps, bitrate, ratio, rtmpUrl } = options const videoUUID = videoLive.Video.uuid const localLTags = lTags(sessionId, videoUUID) @@ -257,6 +259,7 @@ class LiveManager { streamingPlaylist, rtmpUrl, bitrate, + ratio, fps, allResolutions }) diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts index 62708b14b..a80abc843 100644 --- a/server/lib/live/shared/muxing-session.ts +++ b/server/lib/live/shared/muxing-session.ts @@ -54,9 +54,11 @@ class MuxingSession extends EventEmitter { private readonly streamingPlaylist: MStreamingPlaylistVideo private readonly rtmpUrl: string private readonly fps: number - private readonly bitrate: number private readonly allResolutions: number[] + private readonly bitrate: number + private readonly ratio: number + private readonly videoId: number private readonly videoUUID: string private readonly saveReplay: boolean @@ -85,6 +87,7 @@ class MuxingSession extends EventEmitter { rtmpUrl: string fps: number bitrate: number + ratio: number allResolutions: number[] }) { super() @@ -96,7 +99,10 @@ class MuxingSession extends EventEmitter { this.streamingPlaylist = options.streamingPlaylist this.rtmpUrl = options.rtmpUrl this.fps = options.fps + this.bitrate = options.bitrate + this.ratio = options.bitrate + this.allResolutions = options.allResolutions this.videoId = this.videoLive.Video.id @@ -122,6 +128,7 @@ class MuxingSession extends EventEmitter { resolutions: this.allResolutions, fps: this.fps, bitrate: this.bitrate, + ratio: this.ratio, availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), profile: CONFIG.LIVE.TRANSCODING.PROFILE diff --git a/server/lib/transcoding/video-transcoding-profiles.ts b/server/lib/transcoding/video-transcoding-profiles.ts index 2309f38d4..bca6dfccd 100644 --- a/server/lib/transcoding/video-transcoding-profiles.ts +++ b/server/lib/transcoding/video-transcoding-profiles.ts @@ -1,23 +1,24 @@ import { logger } from '@server/helpers/logger' -import { AvailableEncoders, EncoderOptionsBuilder, getTargetBitrate, VideoResolution } from '../../../shared/models/videos' +import { getAverageBitrate } from '@shared/core-utils' +import { AvailableEncoders, EncoderOptionsBuilder, EncoderOptionsBuilderParams } from '../../../shared/models/videos' import { buildStreamSuffix, resetSupportedEncoders } from '../../helpers/ffmpeg-utils' import { canDoQuickAudioTranscode, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '../../helpers/ffprobe-utils' -import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' /** * * Available encoders and profiles for the transcoding jobs * These functions are used by ffmpeg-utils that will get the encoders and options depending on the chosen profile * + * Resources: + * * https://slhck.info/video/2017/03/01/rate-control.html + * * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate */ -// Resources: -// * https://slhck.info/video/2017/03/01/rate-control.html -// * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate +const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async (options: EncoderOptionsBuilderParams) => { + const { fps, inputRatio, inputBitrate } = options + if (!fps) return { outputOptions: [ ] } -const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ inputBitrate, resolution, fps }) => { - const targetBitrate = buildTargetBitrate({ inputBitrate, resolution, fps }) - if (!targetBitrate) return { outputOptions: [ ] } + const targetBitrate = capBitrate(inputBitrate, getAverageBitrate({ ...options, fps, ratio: inputRatio })) return { outputOptions: [ @@ -29,8 +30,10 @@ const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ inputBitrat } } -const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = async ({ resolution, fps, inputBitrate, streamNum }) => { - const targetBitrate = buildTargetBitrate({ inputBitrate, resolution, fps }) +const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = async (options: EncoderOptionsBuilderParams) => { + const { streamNum, fps, inputBitrate, inputRatio } = options + + const targetBitrate = capBitrate(inputBitrate, getAverageBitrate({ ...options, fps, ratio: inputRatio })) return { outputOptions: [ @@ -231,14 +234,7 @@ export { // --------------------------------------------------------------------------- -function buildTargetBitrate (options: { - inputBitrate: number - resolution: VideoResolution - fps: number -}) { - const { inputBitrate, resolution, fps } = options - - const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) +function capBitrate (inputBitrate: number, targetBitrate: number) { if (!inputBitrate) return targetBitrate return Math.min(targetBitrate, inputBitrate) diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts index 4095cdb1c..ba952aff5 100644 --- a/server/tests/api/live/live.ts +++ b/server/tests/api/live/live.ts @@ -3,7 +3,7 @@ import 'mocha' import * as chai from 'chai' import { basename, join } from 'path' -import { ffprobePromise, getVideoFileBitrate, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' +import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' import { checkLiveCleanupAfterSave, checkLiveSegmentHash, diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index 2a09e95bf..f67752d69 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts @@ -3,6 +3,7 @@ import 'mocha' import * as chai from 'chai' import { omit } from 'lodash' +import { getMaxBitrate } from '@shared/core-utils' import { buildAbsoluteFixturePath, cleanupTests, @@ -17,8 +18,7 @@ import { waitJobs, webtorrentAdd } from '@shared/extra-utils' -import { getMaxBitrate, HttpStatusCode, VideoResolution, VideoState } from '@shared/models' -import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' +import { HttpStatusCode, VideoState } from '@shared/models' import { canDoQuickTranscode, getAudioStream, @@ -191,15 +191,6 @@ describe('Test video transcoding', function () { it('Should accept and transcode additional extensions', async function () { this.timeout(300_000) - let tempFixturePath: string - - { - tempFixturePath = await generateHighBitrateVideo() - - const bitrate = await getVideoFileBitrate(tempFixturePath) - expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS)) - } - for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) { const attributes = { name: fixture, @@ -555,14 +546,7 @@ describe('Test video transcoding', function () { it('Should respect maximum bitrate values', async function () { this.timeout(160_000) - let tempFixturePath: string - - { - tempFixturePath = await generateHighBitrateVideo() - - const bitrate = await getVideoFileBitrate(tempFixturePath) - expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS)) - } + const tempFixturePath = await generateHighBitrateVideo() const attributes = { name: 'high bitrate video', @@ -586,10 +570,12 @@ describe('Test video transcoding', function () { const bitrate = await getVideoFileBitrate(path) const fps = await getVideoFileFPS(path) - const { videoFileResolution } = await getVideoFileResolution(path) + const dataResolution = await getVideoFileResolution(path) + + expect(resolution).to.equal(resolution) - expect(videoFileResolution).to.equal(resolution) - expect(bitrate).to.be.below(getMaxBitrate(videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) + const maxBitrate = getMaxBitrate({ ...dataResolution, fps }) + expect(bitrate).to.be.below(maxBitrate) } } }) diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts index 579b2e7d8..9b75ae164 100644 --- a/server/tests/cli/optimize-old-videos.ts +++ b/server/tests/cli/optimize-old-videos.ts @@ -2,6 +2,7 @@ import 'mocha' import * as chai from 'chai' +import { getMaxBitrate } from '@shared/core-utils' import { cleanupTests, createMultipleServers, @@ -12,9 +13,7 @@ import { wait, waitJobs } from '@shared/extra-utils' -import { getMaxBitrate, VideoResolution } from '@shared/models' import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffprobe-utils' -import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' const expect = chai.expect @@ -30,14 +29,7 @@ describe('Test optimize old videos', function () { await doubleFollow(servers[0], servers[1]) - let tempFixturePath: string - - { - tempFixturePath = await generateHighBitrateVideo() - - const bitrate = await getVideoFileBitrate(tempFixturePath) - expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS)) - } + const tempFixturePath = await generateHighBitrateVideo() // Upload two videos for our needs await servers[0].videos.upload({ attributes: { name: 'video1', fixture: tempFixturePath } }) @@ -88,10 +80,12 @@ describe('Test optimize old videos', function () { const path = servers[0].servers.buildWebTorrentFilePath(file.fileUrl) const bitrate = await getVideoFileBitrate(path) const fps = await getVideoFileFPS(path) - const resolution = await getVideoFileResolution(path) + const data = await getVideoFileResolution(path) + + expect(data.resolution).to.equal(file.resolution.id) - expect(resolution.videoFileResolution).to.equal(file.resolution.id) - expect(bitrate).to.be.below(getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) + const maxBitrate = getMaxBitrate({ ...data, fps }) + expect(bitrate).to.be.below(maxBitrate) } } }) diff --git a/server/tests/cli/print-transcode-command.ts b/server/tests/cli/print-transcode-command.ts index 3a7969e68..e328a6072 100644 --- a/server/tests/cli/print-transcode-command.ts +++ b/server/tests/cli/print-transcode-command.ts @@ -3,16 +3,16 @@ import 'mocha' import * as chai from 'chai' import { getVideoFileBitrate, getVideoFileFPS } from '@server/helpers/ffprobe-utils' -import { CLICommand } from '@shared/extra-utils' -import { getTargetBitrate, VideoResolution } from '../../../shared/models/videos' -import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' +import { getMaxBitrate } from '@shared/core-utils' +import { buildAbsoluteFixturePath, CLICommand } from '@shared/extra-utils' +import { VideoResolution } from '../../../shared/models/videos' const expect = chai.expect describe('Test create transcoding jobs', function () { it('Should print the correct command for each resolution', async function () { - const fixturePath = 'server/tests/fixtures/video_short.webm' + const fixturePath = buildAbsoluteFixturePath('video_short.webm') const fps = await getVideoFileFPS(fixturePath) const bitrate = await getVideoFileBitrate(fixturePath) @@ -21,7 +21,7 @@ describe('Test create transcoding jobs', function () { VideoResolution.H_1080P ]) { const command = await CLICommand.exec(`npm run print-transcode-command -- ${fixturePath} -r ${resolution}`) - const targetBitrate = Math.min(getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS), bitrate) + const targetBitrate = Math.min(getMaxBitrate({ resolution, fps, ratio: 16 / 9 }), bitrate) expect(command).to.includes(`-vf scale=w=-2:h=${resolution}`) expect(command).to.includes(`-y -acodec aac -vcodec libx264`) diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts index d5cac51a3..a6bf5b4c5 100644 --- a/server/tests/helpers/core-utils.ts +++ b/server/tests/helpers/core-utils.ts @@ -4,6 +4,8 @@ import 'mocha' import * as chai from 'chai' import { snakeCase } from 'lodash' import validator from 'validator' +import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils' +import { VideoResolution } from '@shared/models' import { objectConverter, parseBytes } from '../../helpers/core-utils' const expect = chai.expect @@ -46,6 +48,9 @@ describe('Parse Bytes', function () { it('Should be invalid when given invalid value', async function () { expect(parseBytes('6GB 1GB')).to.be.eq(6) }) +}) + +describe('Object', function () { it('Should convert an object', async function () { function keyConverter (k: string) { @@ -94,3 +99,36 @@ describe('Parse Bytes', function () { expect(obj['my_super_key']).to.be.undefined }) }) + +describe('Bitrate', function () { + + it('Should get appropriate max bitrate', function () { + const tests = [ + { resolution: VideoResolution.H_240P, ratio: 16 / 9, fps: 24, min: 600, max: 800 }, + { resolution: VideoResolution.H_360P, ratio: 16 / 9, fps: 24, min: 1200, max: 1600 }, + { resolution: VideoResolution.H_480P, ratio: 16 / 9, fps: 24, min: 2000, max: 2300 }, + { resolution: VideoResolution.H_720P, ratio: 16 / 9, fps: 24, min: 4000, max: 4400 }, + { resolution: VideoResolution.H_1080P, ratio: 16 / 9, fps: 24, min: 8000, max: 10000 }, + { resolution: VideoResolution.H_4K, ratio: 16 / 9, fps: 24, min: 25000, max: 30000 } + ] + + for (const test of tests) { + expect(getMaxBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000) + } + }) + + it('Should get appropriate average bitrate', function () { + const tests = [ + { resolution: VideoResolution.H_240P, ratio: 16 / 9, fps: 24, min: 350, max: 450 }, + { resolution: VideoResolution.H_360P, ratio: 16 / 9, fps: 24, min: 700, max: 900 }, + { resolution: VideoResolution.H_480P, ratio: 16 / 9, fps: 24, min: 1100, max: 1300 }, + { resolution: VideoResolution.H_720P, ratio: 16 / 9, fps: 24, min: 2300, max: 2500 }, + { resolution: VideoResolution.H_1080P, ratio: 16 / 9, fps: 24, min: 4700, max: 5000 }, + { resolution: VideoResolution.H_4K, ratio: 16 / 9, fps: 24, min: 15000, max: 17000 } + ] + + for (const test of tests) { + expect(getAverageBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000) + } + }) +}) -- cgit v1.2.3