aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/helpers/ffmpeg/ffmpeg-options.ts2
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/lib/transcoding/transcoding-quick-transcode.ts51
-rw-r--r--server/tests/api/transcoding/transcoder.ts6
-rw-r--r--server/tests/helpers/core-utils.ts6
-rw-r--r--server/tests/peertube-runner/live-transcoding.ts2
-rw-r--r--server/tests/peertube-runner/studio-transcoding.ts2
-rw-r--r--server/tests/shared/generate.ts4
-rw-r--r--shared/core-utils/videos/bitrate.ts12
-rw-r--r--shared/ffmpeg/ffmpeg-default-transcoding-profile.ts61
10 files changed, 76 insertions, 72 deletions
diff --git a/server/helpers/ffmpeg/ffmpeg-options.ts b/server/helpers/ffmpeg/ffmpeg-options.ts
index db6350d39..64d7c4179 100644
--- a/server/helpers/ffmpeg/ffmpeg-options.ts
+++ b/server/helpers/ffmpeg/ffmpeg-options.ts
@@ -11,7 +11,7 @@ export function getFFmpegCommandWrapperOptions (type: CommandType, availableEnco
11 availableEncoders, 11 availableEncoders,
12 profile: getProfile(type), 12 profile: getProfile(type),
13 13
14 niceness: FFMPEG_NICE[type], 14 niceness: FFMPEG_NICE[type.toUpperCase()],
15 tmpDirectory: CONFIG.STORAGE.TMP_DIR, 15 tmpDirectory: CONFIG.STORAGE.TMP_DIR,
16 threads: getThreads(type), 16 threads: getThreads(type),
17 17
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 6a757a0ff..adf24b73f 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -469,7 +469,7 @@ const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
469 DISLIKE: 'dislike' 469 DISLIKE: 'dislike'
470} 470}
471 471
472const FFMPEG_NICE: { [ id: string ]: number } = { 472const FFMPEG_NICE = {
473 // parent process defaults to niceness = 0 473 // parent process defaults to niceness = 0
474 // reminder: lower = higher priority, max value is 19, lowest is -20 474 // reminder: lower = higher priority, max value is 19, lowest is -20
475 LIVE: 5, // prioritize over VOD and THUMBNAIL 475 LIVE: 5, // prioritize over VOD and THUMBNAIL
diff --git a/server/lib/transcoding/transcoding-quick-transcode.ts b/server/lib/transcoding/transcoding-quick-transcode.ts
index b7f921890..53f12cd06 100644
--- a/server/lib/transcoding/transcoding-quick-transcode.ts
+++ b/server/lib/transcoding/transcoding-quick-transcode.ts
@@ -1,16 +1,6 @@
1import { FfprobeData } from 'fluent-ffmpeg' 1import { FfprobeData } from 'fluent-ffmpeg'
2import { CONFIG } from '@server/initializers/config' 2import { CONFIG } from '@server/initializers/config'
3import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants' 3import { canDoQuickAudioTranscode, canDoQuickVideoTranscode, ffprobePromise } from '@shared/ffmpeg'
4import { getMaxBitrate } from '@shared/core-utils'
5import {
6 ffprobePromise,
7 getAudioStream,
8 getMaxAudioBitrate,
9 getVideoStream,
10 getVideoStreamBitrate,
11 getVideoStreamDimensionsInfo,
12 getVideoStreamFPS
13} from '@shared/ffmpeg'
14 4
15export async function canDoQuickTranscode (path: string, existingProbe?: FfprobeData): Promise<boolean> { 5export async function canDoQuickTranscode (path: string, existingProbe?: FfprobeData): Promise<boolean> {
16 if (CONFIG.TRANSCODING.PROFILE !== 'default') return false 6 if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
@@ -20,42 +10,3 @@ export async function canDoQuickTranscode (path: string, existingProbe?: Ffprobe
20 return await canDoQuickVideoTranscode(path, probe) && 10 return await canDoQuickVideoTranscode(path, probe) &&
21 await canDoQuickAudioTranscode(path, probe) 11 await canDoQuickAudioTranscode(path, probe)
22} 12}
23
24export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
25 const parsedAudio = await getAudioStream(path, probe)
26
27 if (!parsedAudio.audioStream) return true
28
29 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
30
31 const audioBitrate = parsedAudio.bitrate
32 if (!audioBitrate) return false
33
34 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
35 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
36
37 const channelLayout = parsedAudio.audioStream['channel_layout']
38 // Causes playback issues with Chrome
39 if (!channelLayout || channelLayout === 'unknown' || channelLayout === 'quad') return false
40
41 return true
42}
43
44export async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
45 const videoStream = await getVideoStream(path, probe)
46 const fps = await getVideoStreamFPS(path, probe)
47 const bitRate = await getVideoStreamBitrate(path, probe)
48 const resolutionData = await getVideoStreamDimensionsInfo(path, probe)
49
50 // If ffprobe did not manage to guess the bitrate
51 if (!bitRate) return false
52
53 // check video params
54 if (!videoStream) return false
55 if (videoStream['codec_name'] !== 'h264') return false
56 if (videoStream['pix_fmt'] !== 'yuv420p') return false
57 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
58 if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false
59
60 return true
61}
diff --git a/server/tests/api/transcoding/transcoder.ts b/server/tests/api/transcoding/transcoder.ts
index fa78b58bb..8a0a7f6d2 100644
--- a/server/tests/api/transcoding/transcoder.ts
+++ b/server/tests/api/transcoding/transcoder.ts
@@ -3,7 +3,7 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { canDoQuickTranscode } from '@server/lib/transcoding/transcoding-quick-transcode' 4import { canDoQuickTranscode } from '@server/lib/transcoding/transcoding-quick-transcode'
5import { checkWebTorrentWorks, generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared' 5import { checkWebTorrentWorks, generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared'
6import { buildAbsoluteFixturePath, getAllFiles, getMaxBitrate, getMinLimitBitrate, omit } from '@shared/core-utils' 6import { buildAbsoluteFixturePath, getAllFiles, getMaxTheoreticalBitrate, getMinTheoreticalBitrate, omit } from '@shared/core-utils'
7import { 7import {
8 ffprobePromise, 8 ffprobePromise,
9 getAudioStream, 9 getAudioStream,
@@ -564,7 +564,7 @@ describe('Test video transcoding', function () {
564 564
565 expect(resolution).to.equal(resolution) 565 expect(resolution).to.equal(resolution)
566 566
567 const maxBitrate = getMaxBitrate({ ...dataResolution, fps }) 567 const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
568 expect(bitrate).to.be.below(maxBitrate) 568 expect(bitrate).to.be.below(maxBitrate)
569 } 569 }
570 } 570 }
@@ -611,7 +611,7 @@ describe('Test video transcoding', function () {
611 const bitrate = await getVideoStreamBitrate(path) 611 const bitrate = await getVideoStreamBitrate(path)
612 612
613 const inputBitrate = 60_000 613 const inputBitrate = 60_000
614 const limit = getMinLimitBitrate({ fps: 10, ratio: 1, resolution: r }) 614 const limit = getMinTheoreticalBitrate({ fps: 10, ratio: 1, resolution: r })
615 let belowValue = Math.max(inputBitrate, limit) 615 let belowValue = Math.max(inputBitrate, limit)
616 belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise 616 belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise
617 617
diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts
index de6ba4f82..cd2f07e4a 100644
--- a/server/tests/helpers/core-utils.ts
+++ b/server/tests/helpers/core-utils.ts
@@ -3,7 +3,7 @@
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { snakeCase } from 'lodash' 4import { snakeCase } from 'lodash'
5import validator from 'validator' 5import validator from 'validator'
6import { getAverageBitrate, getMaxBitrate } from '@shared/core-utils' 6import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate } from '@shared/core-utils'
7import { VideoResolution } from '@shared/models' 7import { VideoResolution } from '@shared/models'
8import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils' 8import { objectConverter, parseBytes, parseDurationToMs } from '../../helpers/core-utils'
9 9
@@ -128,7 +128,7 @@ describe('Bitrate', function () {
128 ] 128 ]
129 129
130 for (const test of tests) { 130 for (const test of tests) {
131 expect(getMaxBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000) 131 expect(getMaxTheoreticalBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
132 } 132 }
133 }) 133 })
134 134
@@ -144,7 +144,7 @@ describe('Bitrate', function () {
144 ] 144 ]
145 145
146 for (const test of tests) { 146 for (const test of tests) {
147 expect(getAverageBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000) 147 expect(getAverageTheoreticalBitrate(test)).to.be.above(test.min * 1000).and.below(test.max * 1000)
148 } 148 }
149 }) 149 })
150}) 150})
diff --git a/server/tests/peertube-runner/live-transcoding.ts b/server/tests/peertube-runner/live-transcoding.ts
index 1e94eabcd..31716d545 100644
--- a/server/tests/peertube-runner/live-transcoding.ts
+++ b/server/tests/peertube-runner/live-transcoding.ts
@@ -145,7 +145,7 @@ describe('Test Live transcoding in peertube-runner program', function () {
145 const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken() 145 const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
146 146
147 peertubeRunner = new PeerTubeRunnerProcess() 147 peertubeRunner = new PeerTubeRunnerProcess()
148 await peertubeRunner.runServer({ hideLogs: false }) 148 await peertubeRunner.runServer()
149 await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' }) 149 await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' })
150 }) 150 })
151 151
diff --git a/server/tests/peertube-runner/studio-transcoding.ts b/server/tests/peertube-runner/studio-transcoding.ts
index cca905e2f..204836c4d 100644
--- a/server/tests/peertube-runner/studio-transcoding.ts
+++ b/server/tests/peertube-runner/studio-transcoding.ts
@@ -75,7 +75,7 @@ describe('Test studio transcoding in peertube-runner program', function () {
75 const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken() 75 const registrationToken = await servers[0].runnerRegistrationTokens.getFirstRegistrationToken()
76 76
77 peertubeRunner = new PeerTubeRunnerProcess() 77 peertubeRunner = new PeerTubeRunnerProcess()
78 await peertubeRunner.runServer({ hideLogs: false }) 78 await peertubeRunner.runServer()
79 await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' }) 79 await peertubeRunner.registerPeerTubeInstance({ server: servers[0], registrationToken, runnerName: 'runner' })
80 }) 80 })
81 81
diff --git a/server/tests/shared/generate.ts b/server/tests/shared/generate.ts
index b0c8dba66..3788b049f 100644
--- a/server/tests/shared/generate.ts
+++ b/server/tests/shared/generate.ts
@@ -2,7 +2,7 @@ import { expect } from 'chai'
2import ffmpeg from 'fluent-ffmpeg' 2import ffmpeg from 'fluent-ffmpeg'
3import { ensureDir, pathExists } from 'fs-extra' 3import { ensureDir, pathExists } from 'fs-extra'
4import { dirname } from 'path' 4import { dirname } from 'path'
5import { buildAbsoluteFixturePath, getMaxBitrate } from '@shared/core-utils' 5import { buildAbsoluteFixturePath, getMaxTheoreticalBitrate } from '@shared/core-utils'
6import { getVideoStreamBitrate, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@shared/ffmpeg' 6import { getVideoStreamBitrate, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@shared/ffmpeg'
7 7
8async function ensureHasTooBigBitrate (fixturePath: string) { 8async function ensureHasTooBigBitrate (fixturePath: string) {
@@ -10,7 +10,7 @@ async function ensureHasTooBigBitrate (fixturePath: string) {
10 const dataResolution = await getVideoStreamDimensionsInfo(fixturePath) 10 const dataResolution = await getVideoStreamDimensionsInfo(fixturePath)
11 const fps = await getVideoStreamFPS(fixturePath) 11 const fps = await getVideoStreamFPS(fixturePath)
12 12
13 const maxBitrate = getMaxBitrate({ ...dataResolution, fps }) 13 const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
14 expect(bitrate).to.be.above(maxBitrate) 14 expect(bitrate).to.be.above(maxBitrate)
15} 15}
16 16
diff --git a/shared/core-utils/videos/bitrate.ts b/shared/core-utils/videos/bitrate.ts
index 30d22df09..6be027826 100644
--- a/shared/core-utils/videos/bitrate.ts
+++ b/shared/core-utils/videos/bitrate.ts
@@ -40,7 +40,7 @@ const maxBitPerPixel: BitPerPixel = {
40 [VideoResolution.H_4K]: 0.14 40 [VideoResolution.H_4K]: 0.14
41} 41}
42 42
43function getAverageBitrate (options: { 43function getAverageTheoreticalBitrate (options: {
44 resolution: VideoResolution 44 resolution: VideoResolution
45 ratio: number 45 ratio: number
46 fps: number 46 fps: number
@@ -51,7 +51,7 @@ function getAverageBitrate (options: {
51 return targetBitrate 51 return targetBitrate
52} 52}
53 53
54function getMaxBitrate (options: { 54function getMaxTheoreticalBitrate (options: {
55 resolution: VideoResolution 55 resolution: VideoResolution
56 ratio: number 56 ratio: number
57 fps: number 57 fps: number
@@ -62,7 +62,7 @@ function getMaxBitrate (options: {
62 return targetBitrate 62 return targetBitrate
63} 63}
64 64
65function getMinLimitBitrate (options: { 65function getMinTheoreticalBitrate (options: {
66 resolution: VideoResolution 66 resolution: VideoResolution
67 ratio: number 67 ratio: number
68 fps: number 68 fps: number
@@ -76,9 +76,9 @@ function getMinLimitBitrate (options: {
76// --------------------------------------------------------------------------- 76// ---------------------------------------------------------------------------
77 77
78export { 78export {
79 getAverageBitrate, 79 getAverageTheoreticalBitrate,
80 getMaxBitrate, 80 getMaxTheoreticalBitrate,
81 getMinLimitBitrate 81 getMinTheoreticalBitrate
82} 82}
83 83
84// --------------------------------------------------------------------------- 84// ---------------------------------------------------------------------------
diff --git a/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts
index f7fc49465..8a3f32011 100644
--- a/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts
+++ b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts
@@ -1,5 +1,15 @@
1import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' 1import { FfprobeData } from 'fluent-ffmpeg'
2import { buildStreamSuffix, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/ffmpeg' 2import { getAverageTheoreticalBitrate, getMaxTheoreticalBitrate, getMinTheoreticalBitrate } from '@shared/core-utils'
3import {
4 buildStreamSuffix,
5 ffprobePromise,
6 getAudioStream,
7 getMaxAudioBitrate,
8 getVideoStream,
9 getVideoStreamBitrate,
10 getVideoStreamDimensionsInfo,
11 getVideoStreamFPS
12} from '@shared/ffmpeg'
3import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models' 13import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models'
4 14
5const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { 15const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => {
@@ -34,6 +44,10 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
34const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => { 44const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => {
35 const probe = await ffprobePromise(input) 45 const probe = await ffprobePromise(input)
36 46
47 if (canCopyAudio && await canDoQuickAudioTranscode(input, probe)) {
48 return { copy: true, outputOptions: [ ] }
49 }
50
37 const parsedAudio = await getAudioStream(input, probe) 51 const parsedAudio = await getAudioStream(input, probe)
38 52
39 // We try to reduce the ceiling bitrate by making rough matches of bitrates 53 // We try to reduce the ceiling bitrate by making rough matches of bitrates
@@ -95,6 +109,45 @@ export function getDefaultEncodersToTry () {
95 } 109 }
96} 110}
97 111
112export async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
113 const parsedAudio = await getAudioStream(path, probe)
114
115 if (!parsedAudio.audioStream) return true
116
117 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
118
119 const audioBitrate = parsedAudio.bitrate
120 if (!audioBitrate) return false
121
122 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
123 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
124
125 const channelLayout = parsedAudio.audioStream['channel_layout']
126 // Causes playback issues with Chrome
127 if (!channelLayout || channelLayout === 'unknown' || channelLayout === 'quad') return false
128
129 return true
130}
131
132export async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
133 const videoStream = await getVideoStream(path, probe)
134 const fps = await getVideoStreamFPS(path, probe)
135 const bitRate = await getVideoStreamBitrate(path, probe)
136 const resolutionData = await getVideoStreamDimensionsInfo(path, probe)
137
138 // If ffprobe did not manage to guess the bitrate
139 if (!bitRate) return false
140
141 // check video params
142 if (!videoStream) return false
143 if (videoStream['codec_name'] !== 'h264') return false
144 if (videoStream['pix_fmt'] !== 'yuv420p') return false
145 if (fps < 2 || fps > 65) return false
146 if (bitRate > getMaxTheoreticalBitrate({ ...resolutionData, fps })) return false
147
148 return true
149}
150
98// --------------------------------------------------------------------------- 151// ---------------------------------------------------------------------------
99 152
100function getTargetBitrate (options: { 153function getTargetBitrate (options: {
@@ -105,8 +158,8 @@ function getTargetBitrate (options: {
105}) { 158}) {
106 const { inputBitrate, resolution, ratio, fps } = options 159 const { inputBitrate, resolution, ratio, fps } = options
107 160
108 const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio })) 161 const capped = capBitrate(inputBitrate, getAverageTheoreticalBitrate({ resolution, fps, ratio }))
109 const limit = getMinLimitBitrate({ resolution, fps, ratio }) 162 const limit = getMinTheoreticalBitrate({ resolution, fps, ratio })
110 163
111 return Math.max(limit, capped) 164 return Math.max(limit, capped)
112} 165}