diff options
author | frankdelange <yetangitu-f@unternet.org> | 2019-11-01 02:06:19 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-11-25 10:59:47 +0100 |
commit | 5c7d650827cc471a03e7fa18362bcbcbe5d30838 (patch) | |
tree | 41c96e3c9e2dcd4f15166e4f13b427ef116ea4f1 /server/helpers/ffmpeg-utils.ts | |
parent | dee6fe1e4f5c024fd387e8c2b306c174b24aa8b3 (diff) | |
download | PeerTube-5c7d650827cc471a03e7fa18362bcbcbe5d30838.tar.gz PeerTube-5c7d650827cc471a03e7fa18362bcbcbe5d30838.tar.zst PeerTube-5c7d650827cc471a03e7fa18362bcbcbe5d30838.zip |
Add audio-only option to transcoders and player
This patch adds an audio-only option to PeerTube by means of a new transcoding configuration which creates mp4 files which only contain an audio stream. This new transcoder has a resolution of '0' and is presented in the preferences and in the player resolution menu as 'Audio-only' (localised). When playing such streams the player shows the file thumbnail as background and disables controls autohide.
Audio-only files can be shared and streamed just like any other file. They can be downloaded as well, the resulting file will be an mp4 container with a single audio stream.
This patch is a proof of concept to show the feasibility of 'true' audio-only support. There are better ways of doing this which also enable multiple audio streams for a given video stream (e.g. DASH) but as this would entail a fundamental change in the way PeerTube works it is a bridge too far for a simple proof of concept.
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 45 |
1 files changed, 38 insertions, 7 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 7a4ac0970..2d9ce2bfa 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -14,6 +14,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
14 | 14 | ||
15 | // Put in the order we want to proceed jobs | 15 | // Put in the order we want to proceed jobs |
16 | const resolutions = [ | 16 | const resolutions = [ |
17 | VideoResolution.H_NOVIDEO, | ||
17 | VideoResolution.H_480P, | 18 | VideoResolution.H_480P, |
18 | VideoResolution.H_360P, | 19 | VideoResolution.H_360P, |
19 | VideoResolution.H_720P, | 20 | VideoResolution.H_720P, |
@@ -34,10 +35,15 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
34 | async function getVideoFileSize (path: string) { | 35 | async function getVideoFileSize (path: string) { |
35 | const videoStream = await getVideoStreamFromFile(path) | 36 | const videoStream = await getVideoStreamFromFile(path) |
36 | 37 | ||
37 | return { | 38 | return videoStream == null |
38 | width: videoStream.width, | 39 | ? { |
39 | height: videoStream.height | 40 | width: 0, |
40 | } | 41 | height: 0 |
42 | } | ||
43 | : { | ||
44 | width: videoStream.width, | ||
45 | height: videoStream.height | ||
46 | } | ||
41 | } | 47 | } |
42 | 48 | ||
43 | async function getVideoFileResolution (path: string) { | 49 | async function getVideoFileResolution (path: string) { |
@@ -52,6 +58,10 @@ async function getVideoFileResolution (path: string) { | |||
52 | async function getVideoFileFPS (path: string) { | 58 | async function getVideoFileFPS (path: string) { |
53 | const videoStream = await getVideoStreamFromFile(path) | 59 | const videoStream = await getVideoStreamFromFile(path) |
54 | 60 | ||
61 | if (videoStream == null) { | ||
62 | return 0 | ||
63 | } | ||
64 | |||
55 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { | 65 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { |
56 | const valuesText: string = videoStream[key] | 66 | const valuesText: string = videoStream[key] |
57 | if (!valuesText) continue | 67 | if (!valuesText) continue |
@@ -118,7 +128,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
118 | } | 128 | } |
119 | } | 129 | } |
120 | 130 | ||
121 | type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 131 | type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'split-audio' |
122 | 132 | ||
123 | interface BaseTranscodeOptions { | 133 | interface BaseTranscodeOptions { |
124 | type: TranscodeOptionsType | 134 | type: TranscodeOptionsType |
@@ -149,7 +159,11 @@ interface MergeAudioTranscodeOptions extends BaseTranscodeOptions { | |||
149 | audioPath: string | 159 | audioPath: string |
150 | } | 160 | } |
151 | 161 | ||
152 | type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions | 162 | interface SplitAudioTranscodeOptions extends BaseTranscodeOptions { |
163 | type: 'split-audio' | ||
164 | } | ||
165 | |||
166 | type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | SplitAudioTranscodeOptions | QuickTranscodeOptions | ||
153 | 167 | ||
154 | function transcode (options: TranscodeOptions) { | 168 | function transcode (options: TranscodeOptions) { |
155 | return new Promise<void>(async (res, rej) => { | 169 | return new Promise<void>(async (res, rej) => { |
@@ -163,6 +177,8 @@ function transcode (options: TranscodeOptions) { | |||
163 | command = await buildHLSCommand(command, options) | 177 | command = await buildHLSCommand(command, options) |
164 | } else if (options.type === 'merge-audio') { | 178 | } else if (options.type === 'merge-audio') { |
165 | command = await buildAudioMergeCommand(command, options) | 179 | command = await buildAudioMergeCommand(command, options) |
180 | } else if (options.type === 'split-audio') { | ||
181 | command = await buildAudioSplitCommand(command, options) | ||
166 | } else { | 182 | } else { |
167 | command = await buildx264Command(command, options) | 183 | command = await buildx264Command(command, options) |
168 | } | 184 | } |
@@ -198,6 +214,7 @@ async function canDoQuickTranscode (path: string): Promise<boolean> { | |||
198 | const resolution = await getVideoFileResolution(path) | 214 | const resolution = await getVideoFileResolution(path) |
199 | 215 | ||
200 | // check video params | 216 | // check video params |
217 | if (videoStream == null) return false | ||
201 | if (videoStream[ 'codec_name' ] !== 'h264') return false | 218 | if (videoStream[ 'codec_name' ] !== 'h264') return false |
202 | if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false | 219 | if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false |
203 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false | 220 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false |
@@ -276,6 +293,12 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M | |||
276 | return command | 293 | return command |
277 | } | 294 | } |
278 | 295 | ||
296 | async function buildAudioSplitCommand (command: ffmpeg.FfmpegCommand, options: SplitAudioTranscodeOptions) { | ||
297 | command = await presetAudioSplit(command) | ||
298 | |||
299 | return command | ||
300 | } | ||
301 | |||
279 | async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { | 302 | async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { |
280 | command = await presetCopy(command) | 303 | command = await presetCopy(command) |
281 | 304 | ||
@@ -327,7 +350,7 @@ function getVideoStreamFromFile (path: string) { | |||
327 | if (err) return rej(err) | 350 | if (err) return rej(err) |
328 | 351 | ||
329 | const videoStream = metadata.streams.find(s => s.codec_type === 'video') | 352 | const videoStream = metadata.streams.find(s => s.codec_type === 'video') |
330 | if (!videoStream) return rej(new Error('Cannot find video stream of ' + path)) | 353 | //if (!videoStream) return rej(new Error('Cannot find video stream of ' + path)) |
331 | 354 | ||
332 | return res(videoStream) | 355 | return res(videoStream) |
333 | }) | 356 | }) |
@@ -482,3 +505,11 @@ async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.Ffmpeg | |||
482 | .videoCodec('copy') | 505 | .videoCodec('copy') |
483 | .audioCodec('copy') | 506 | .audioCodec('copy') |
484 | } | 507 | } |
508 | |||
509 | |||
510 | async function presetAudioSplit (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | ||
511 | return command | ||
512 | .format('mp4') | ||
513 | .audioCodec('copy') | ||
514 | .noVideo() | ||
515 | } | ||