aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers/ffmpeg-utils.ts
diff options
context:
space:
mode:
authorfrankdelange <yetangitu-f@unternet.org>2019-11-01 02:06:19 +0100
committerChocobozzz <me@florianbigard.com>2019-11-25 10:59:47 +0100
commit5c7d650827cc471a03e7fa18362bcbcbe5d30838 (patch)
tree41c96e3c9e2dcd4f15166e4f13b427ef116ea4f1 /server/helpers/ffmpeg-utils.ts
parentdee6fe1e4f5c024fd387e8c2b306c174b24aa8b3 (diff)
downloadPeerTube-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.ts45
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) {
34async function getVideoFileSize (path: string) { 35async 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
43async function getVideoFileResolution (path: string) { 49async function getVideoFileResolution (path: string) {
@@ -52,6 +58,10 @@ async function getVideoFileResolution (path: string) {
52async function getVideoFileFPS (path: string) { 58async 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
121type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' 131type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'split-audio'
122 132
123interface BaseTranscodeOptions { 133interface 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
152type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions 162interface SplitAudioTranscodeOptions extends BaseTranscodeOptions {
163 type: 'split-audio'
164}
165
166type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | SplitAudioTranscodeOptions | QuickTranscodeOptions
153 167
154function transcode (options: TranscodeOptions) { 168function 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
296async function buildAudioSplitCommand (command: ffmpeg.FfmpegCommand, options: SplitAudioTranscodeOptions) {
297 command = await presetAudioSplit(command)
298
299 return command
300}
301
279async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { 302async 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
510async function presetAudioSplit (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> {
511 return command
512 .format('mp4')
513 .audioCodec('copy')
514 .noVideo()
515}