diff options
Diffstat (limited to 'server/helpers')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 76 |
1 files changed, 39 insertions, 37 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 085635b5a..c6b8a0eb0 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -110,7 +110,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
110 | // Transcode meta function | 110 | // Transcode meta function |
111 | // --------------------------------------------------------------------------- | 111 | // --------------------------------------------------------------------------- |
112 | 112 | ||
113 | type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio' | 113 | type TranscodeOptionsType = 'hls' | 'hls-from-ts' | 'quick-transcode' | 'video' | 'merge-audio' | 'only-audio' |
114 | 114 | ||
115 | interface BaseTranscodeOptions { | 115 | interface BaseTranscodeOptions { |
116 | type: TranscodeOptionsType | 116 | type: TranscodeOptionsType |
@@ -134,6 +134,14 @@ interface HLSTranscodeOptions extends BaseTranscodeOptions { | |||
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | interface HLSFromTSTranscodeOptions extends BaseTranscodeOptions { | ||
138 | type: 'hls-from-ts' | ||
139 | |||
140 | hlsPlaylist: { | ||
141 | videoFilename: string | ||
142 | } | ||
143 | } | ||
144 | |||
137 | interface QuickTranscodeOptions extends BaseTranscodeOptions { | 145 | interface QuickTranscodeOptions extends BaseTranscodeOptions { |
138 | type: 'quick-transcode' | 146 | type: 'quick-transcode' |
139 | } | 147 | } |
@@ -153,6 +161,7 @@ interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions { | |||
153 | 161 | ||
154 | type TranscodeOptions = | 162 | type TranscodeOptions = |
155 | HLSTranscodeOptions | 163 | HLSTranscodeOptions |
164 | | HLSFromTSTranscodeOptions | ||
156 | | VideoTranscodeOptions | 165 | | VideoTranscodeOptions |
157 | | MergeAudioTranscodeOptions | 166 | | MergeAudioTranscodeOptions |
158 | | OnlyAudioTranscodeOptions | 167 | | OnlyAudioTranscodeOptions |
@@ -163,6 +172,7 @@ const builders: { | |||
163 | } = { | 172 | } = { |
164 | 'quick-transcode': buildQuickTranscodeCommand, | 173 | 'quick-transcode': buildQuickTranscodeCommand, |
165 | 'hls': buildHLSVODCommand, | 174 | 'hls': buildHLSVODCommand, |
175 | 'hls-from-ts': buildHLSVODFromTSCommand, | ||
166 | 'merge-audio': buildAudioMergeCommand, | 176 | 'merge-audio': buildAudioMergeCommand, |
167 | 'only-audio': buildOnlyAudioCommand, | 177 | 'only-audio': buildOnlyAudioCommand, |
168 | 'video': buildx264VODCommand | 178 | 'video': buildx264VODCommand |
@@ -292,31 +302,6 @@ function getLiveMuxingCommand (rtmpUrl: string, outPath: string) { | |||
292 | return command | 302 | return command |
293 | } | 303 | } |
294 | 304 | ||
295 | async function hlsPlaylistToFragmentedMP4 (replayDirectory: string, segmentFiles: string[], outputPath: string) { | ||
296 | const concatFilePath = join(replayDirectory, 'concat.txt') | ||
297 | |||
298 | function cleaner () { | ||
299 | remove(concatFilePath) | ||
300 | .catch(err => logger.error('Cannot remove concat file in %s.', replayDirectory, { err })) | ||
301 | } | ||
302 | |||
303 | // First concat the ts files to a mp4 file | ||
304 | const content = segmentFiles.map(f => 'file ' + f) | ||
305 | .join('\n') | ||
306 | |||
307 | await writeFile(concatFilePath, content + '\n') | ||
308 | |||
309 | const command = getFFmpeg(concatFilePath) | ||
310 | command.inputOption('-safe 0') | ||
311 | command.inputOption('-f concat') | ||
312 | |||
313 | command.outputOption('-c:v copy') | ||
314 | command.audioFilter('aresample=async=1:first_pts=0') | ||
315 | command.output(outputPath) | ||
316 | |||
317 | return runCommand(command, cleaner) | ||
318 | } | ||
319 | |||
320 | function buildStreamSuffix (base: string, streamNum?: number) { | 305 | function buildStreamSuffix (base: string, streamNum?: number) { |
321 | if (streamNum !== undefined) { | 306 | if (streamNum !== undefined) { |
322 | return `${base}:${streamNum}` | 307 | return `${base}:${streamNum}` |
@@ -336,8 +321,7 @@ export { | |||
336 | generateImageFromVideoFile, | 321 | generateImageFromVideoFile, |
337 | TranscodeOptions, | 322 | TranscodeOptions, |
338 | TranscodeOptionsType, | 323 | TranscodeOptionsType, |
339 | transcode, | 324 | transcode |
340 | hlsPlaylistToFragmentedMP4 | ||
341 | } | 325 | } |
342 | 326 | ||
343 | // --------------------------------------------------------------------------- | 327 | // --------------------------------------------------------------------------- |
@@ -447,6 +431,16 @@ function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { | |||
447 | return command | 431 | return command |
448 | } | 432 | } |
449 | 433 | ||
434 | function addCommonHLSVODCommandOptions (command: ffmpeg.FfmpegCommand, outputPath: string) { | ||
435 | return command.outputOption('-hls_time 4') | ||
436 | .outputOption('-hls_list_size 0') | ||
437 | .outputOption('-hls_playlist_type vod') | ||
438 | .outputOption('-hls_segment_filename ' + outputPath) | ||
439 | .outputOption('-hls_segment_type fmp4') | ||
440 | .outputOption('-f hls') | ||
441 | .outputOption('-hls_flags single_file') | ||
442 | } | ||
443 | |||
450 | async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { | 444 | async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { |
451 | const videoPath = getHLSVideoPath(options) | 445 | const videoPath = getHLSVideoPath(options) |
452 | 446 | ||
@@ -454,19 +448,27 @@ async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTr | |||
454 | else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command) | 448 | else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command) |
455 | else command = await buildx264VODCommand(command, options) | 449 | else command = await buildx264VODCommand(command, options) |
456 | 450 | ||
457 | command = command.outputOption('-hls_time 4') | 451 | addCommonHLSVODCommandOptions(command, videoPath) |
458 | .outputOption('-hls_list_size 0') | 452 | |
459 | .outputOption('-hls_playlist_type vod') | 453 | return command |
460 | .outputOption('-hls_segment_filename ' + videoPath) | 454 | } |
461 | .outputOption('-hls_segment_type fmp4') | 455 | |
462 | .outputOption('-f hls') | 456 | async function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) { |
463 | .outputOption('-hls_flags single_file') | 457 | const videoPath = getHLSVideoPath(options) |
458 | |||
459 | command.inputOption('-safe 0') | ||
460 | command.inputOption('-f concat') | ||
461 | |||
462 | command.outputOption('-c:v copy') | ||
463 | command.audioFilter('aresample=async=1:first_pts=0') | ||
464 | |||
465 | addCommonHLSVODCommandOptions(command, videoPath) | ||
464 | 466 | ||
465 | return command | 467 | return command |
466 | } | 468 | } |
467 | 469 | ||
468 | async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) { | 470 | async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) { |
469 | if (options.type !== 'hls') return | 471 | if (options.type !== 'hls' && options.type !== 'hls-from-ts') return |
470 | 472 | ||
471 | const fileContent = await readFile(options.outputPath) | 473 | const fileContent = await readFile(options.outputPath) |
472 | 474 | ||
@@ -480,7 +482,7 @@ async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) { | |||
480 | await writeFile(options.outputPath, newContent) | 482 | await writeFile(options.outputPath, newContent) |
481 | } | 483 | } |
482 | 484 | ||
483 | function getHLSVideoPath (options: HLSTranscodeOptions) { | 485 | function getHLSVideoPath (options: HLSTranscodeOptions | HLSFromTSTranscodeOptions) { |
484 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` | 486 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` |
485 | } | 487 | } |
486 | 488 | ||