diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 1d610eff2..c8d6969ff 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -674,7 +674,12 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut // Constrained Encoding (VBV) // https://slhck.info/video/2017/03/01/rate-control.html // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate - const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) + let targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) + + // Don't transcode to an higher bitrate than the original file + const fileBitrate = await getVideoFileBitrate(input) + targetBitrate = Math.min(targetBitrate, fileBitrate) + localCommand = localCommand.outputOptions([ `-maxrate ${targetBitrate}`, `-bufsize ${targetBitrate * 2}` ]) // Keyframe interval of 2 seconds for faster seeking and resolution switching. diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index a1959e1a9..a3d7b8707 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts @@ -20,6 +20,7 @@ import { generateHighBitrateVideo, generateVideoWithFramerate, getMyVideos, + getServerFileSize, getVideo, getVideoFileMetadataUrl, getVideosList, @@ -27,6 +28,7 @@ import { root, ServerInfo, setAccessTokensToServers, + updateCustomSubConfig, uploadVideo, uploadVideoAndGetId, waitJobs, webtorrentAdd @@ -468,6 +470,41 @@ describe('Test video transcoding', function () { } }) + it('Should not transcode to an higher bitrate than the original file', async function () { + this.timeout(160000) + + const config = { + transcoding: { + enabled: true, + resolutions: { + '240p': true, + '360p': true, + '480p': true, + '720p': true, + '1080p': true + }, + webtorrent: { enabled: true }, + hls: { enabled: true } + } + } + await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) + + const videoAttributes = { + name: 'low bitrate', + fixture: 'low-bitrate.mp4' + } + + const resUpload = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) + const videoUUID = resUpload.body.video.uuid + + await waitJobs(servers) + + const resolutions = [ 240, 360, 480, 720, 1080 ] + for (const r of resolutions) { + expect(await getServerFileSize(servers[1], `videos/${videoUUID}-${r}.mp4`)).to.be.below(43) + } + }) + it('Should provide valid ffprobe data', async function () { this.timeout(160000) diff --git a/server/tests/fixtures/low-bitrate.mp4 b/server/tests/fixtures/low-bitrate.mp4 new file mode 100644 index 000000000..69004eccc Binary files /dev/null and b/server/tests/fixtures/low-bitrate.mp4 differ diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts index dfd6e28cb..3c8191ae8 100644 --- a/shared/extra-utils/miscs/miscs.ts +++ b/shared/extra-utils/miscs/miscs.ts @@ -4,7 +4,7 @@ import * as chai from 'chai' import { basename, dirname, isAbsolute, join, resolve } from 'path' import * as request from 'supertest' import * as WebTorrent from 'webtorrent' -import { ensureDir, pathExists, readFile } from 'fs-extra' +import { ensureDir, pathExists, readFile, stat } from 'fs-extra' import * as ffmpeg from 'fluent-ffmpeg' const expect = chai.expect @@ -130,6 +130,12 @@ async function generateVideoWithFramerate (fps = 60) { return tempFixturePath } +async function getFileSize (path: string) { + const stats = await stat(path) + + return stats.size +} + // --------------------------------------------------------------------------- export { @@ -138,6 +144,7 @@ export { areHttpImportTestsDisabled, buildServerDirectory, webtorrentAdd, + getFileSize, immutableAssign, testImage, buildAbsoluteFixturePath, diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts index 6140cebb5..a647b0eb4 100644 --- a/shared/extra-utils/server/servers.ts +++ b/shared/extra-utils/server/servers.ts @@ -6,7 +6,7 @@ import { copy, pathExists, readdir, readFile, remove } from 'fs-extra' import { join } from 'path' import { randomInt } from '../../core-utils/miscs/miscs' import { VideoChannel } from '../../models/videos' -import { root, wait } from '../miscs/miscs' +import { getFileSize, root, wait } from '../miscs/miscs' interface ServerInfo { app: ChildProcess @@ -318,11 +318,18 @@ async function waitUntilLog (server: ServerInfo, str: string, count = 1, strictC } } +async function getServerFileSize (server: ServerInfo, subPath: string) { + const path = join(root(), 'test' + server.internalServerNumber, subPath) + + return getFileSize(path) +} + // --------------------------------------------------------------------------- export { checkDirectoryIsEmpty, checkTmpIsEmpty, + getServerFileSize, ServerInfo, parallelTests, cleanupTests,