From 3851e732c4b1da0bc0c40a46ed5a89d93cdc5389 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 4 Dec 2020 15:10:13 +0100 Subject: [PATCH] Fix audio issues with live replay --- server/helpers/ffmpeg-utils.ts | 9 ++-- .../job-queue/handlers/video-live-ending.ts | 18 +++---- server/lib/live-manager.ts | 48 ++++++++++++------- server/lib/video-transcoding.ts | 42 ++++------------ 4 files changed, 49 insertions(+), 68 deletions(-) diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 1093cb483..9d6fe76cb 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -455,11 +455,10 @@ async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTr async function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) { const videoPath = getHLSVideoPath(options) - command.inputOption('-safe 0') - command.inputOption('-f concat') - - command.outputOption('-c:v copy') - command.audioFilter('aresample=async=1:first_pts=0') + command.outputOption('-c copy') + // Required for example when copying an AAC stream from an MPEG-TS + // Since it's a bitstream filter, we don't need to reencode the audio + command.outputOption('-bsf:a aac_adtstoasc') addCommonHLSVODCommandOptions(command, videoPath) diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index e3c11caa2..4daf9249b 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts @@ -73,8 +73,8 @@ async function saveLive (video: MVideo, live: MVideoLive) { for (const file of rootFiles) { // Move remaining files in the replay directory - if (file.endsWith('.ts') || file.endsWith('.m3u8')) { - await copy(join(hlsDirectory, file), join(replayDirectory, file)) + if (file.endsWith('.ts')) { + await LiveManager.Instance.addSegmentToReplay(hlsDirectory, join(hlsDirectory, file)) } if (file.endsWith('.m3u8') && file !== 'master.m3u8') { @@ -100,23 +100,17 @@ async function saveLive (video: MVideo, live: MVideoLive) { await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) hlsPlaylist.VideoFiles = [] - const replayFiles = await readdir(replayDirectory) let durationDone: boolean for (const playlistFile of playlistFiles) { - const playlistPath = join(replayDirectory, playlistFile) - const { videoFileResolution, isPortraitMode } = await getVideoFileResolution(playlistPath) + const concatenatedTsFile = LiveManager.Instance.buildConcatenatedName(playlistFile) + const concatenatedTsFilePath = join(replayDirectory, concatenatedTsFile) - // Playlist name is for example 3.m3u8 - // Segments names are 3-0.ts 3-1.ts etc - const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-' - - const segmentFiles = replayFiles.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts')) + const { videoFileResolution, isPortraitMode } = await getVideoFileResolution(concatenatedTsFilePath) const outputPath = await generateHlsPlaylistFromTS({ video: videoWithFiles, - replayDirectory, - segmentFiles, + concatenatedTsFilePath, resolution: videoFileResolution, isPortraitMode }) diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index dcf016169..c2dd116a9 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts @@ -1,7 +1,7 @@ import * as chokidar from 'chokidar' import { FfmpegCommand } from 'fluent-ffmpeg' -import { copy, ensureDir, stat } from 'fs-extra' +import { appendFile, copy, ensureDir, readFile, stat } from 'fs-extra' import { basename, join } from 'path' import { isTestInstance } from '@server/helpers/core-utils' import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg-utils' @@ -24,6 +24,7 @@ import { PeerTubeSocket } from './peertube-socket' import { isAbleToUploadVideo } from './user' import { getHLSDirectory } from './video-paths' import { availableEncoders } from './video-transcoding-profiles' +import * as Bluebird from 'bluebird' import memoizee = require('memoizee') @@ -158,6 +159,32 @@ class LiveManager { this.segmentsSha256.delete(videoUUID) } + addSegmentToReplay (hlsVideoPath: string, segmentPath: string) { + const segmentName = basename(segmentPath) + const dest = join(hlsVideoPath, VIDEO_LIVE.REPLAY_DIRECTORY, this.buildConcatenatedName(segmentName)) + + return readFile(segmentPath) + .then(data => appendFile(dest, data)) + .catch(err => logger.error('Cannot copy segment %s to repay directory.', segmentPath, { err })) + } + + buildConcatenatedName (segmentOrPlaylistPath: string) { + const num = basename(segmentOrPlaylistPath).match(/^(\d+)(-|\.)/) + + return 'concat-' + num[1] + '.ts' + } + + private processSegments (hlsVideoPath: string, videoUUID: string, videoLive: MVideoLive, segmentPaths: string[]) { + Bluebird.mapSeries(segmentPaths, async previousSegment => { + // Add sha hash of previous segments, because ffmpeg should have finished generating them + await this.addSegmentSha(videoUUID, previousSegment) + + if (videoLive.saveReplay) { + await this.addSegmentToReplay(hlsVideoPath, previousSegment) + } + }).catch(err => logger.error('Cannot process segments in %s', hlsVideoPath, { err })) + } + private getContext () { return context } @@ -302,28 +329,13 @@ class LiveManager { const segmentsToProcessPerPlaylist: { [playlistId: string]: string[] } = {} const playlistIdMatcher = /^([\d+])-/ - const processSegments = (segmentsToProcess: string[]) => { - // Add sha hash of previous segments, because ffmpeg should have finished generating them - for (const previousSegment of segmentsToProcess) { - this.addSegmentSha(videoUUID, previousSegment) - .catch(err => logger.error('Cannot add sha segment of video %s -> %s.', videoUUID, previousSegment, { err })) - - if (videoLive.saveReplay) { - const segmentName = basename(previousSegment) - - copy(previousSegment, join(outPath, VIDEO_LIVE.REPLAY_DIRECTORY, segmentName)) - .catch(err => logger.error('Cannot copy segment %s to repay directory.', previousSegment, { err })) - } - } - } - const addHandler = segmentPath => { logger.debug('Live add handler of %s.', segmentPath) const playlistId = basename(segmentPath).match(playlistIdMatcher)[0] const segmentsToProcess = segmentsToProcessPerPlaylist[playlistId] || [] - processSegments(segmentsToProcess) + this.processSegments(outPath, videoUUID, videoLive, segmentsToProcess) segmentsToProcessPerPlaylist[playlistId] = [ segmentPath ] @@ -400,7 +412,7 @@ class LiveManager { .then(() => { // Process remaining segments hash for (const key of Object.keys(segmentsToProcessPerPlaylist)) { - processSegments(segmentsToProcessPerPlaylist[key]) + this.processSegments(outPath, videoUUID, videoLive, segmentsToProcessPerPlaylist[key]) } }) .catch(err => logger.error('Cannot close watchers of %s or process remaining hash segments.', outPath, { err })) diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 890b23a44..44ecf4cc9 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts @@ -1,4 +1,4 @@ -import { copyFile, ensureDir, move, remove, stat, writeFile } from 'fs-extra' +import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' import { basename, extname as extnameUtil, join } from 'path' import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models' @@ -166,41 +166,17 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video // Concat TS segments from a live video to a fragmented mp4 HLS playlist async function generateHlsPlaylistFromTS (options: { video: MVideoWithFile - replayDirectory: string - segmentFiles: string[] + concatenatedTsFilePath: string resolution: VideoResolution isPortraitMode: boolean }) { - const concatFilePath = join(options.replayDirectory, 'concat.txt') - - function cleaner () { - remove(concatFilePath) - .catch(err => logger.error('Cannot remove concat file in %s.', options.replayDirectory, { err })) - } - - // First concat the ts files to a mp4 file - const content = options.segmentFiles.map(f => 'file ' + f) - .join('\n') - - await writeFile(concatFilePath, content + '\n') - - try { - const outputPath = await generateHlsPlaylistCommon({ - video: options.video, - resolution: options.resolution, - isPortraitMode: options.isPortraitMode, - inputPath: concatFilePath, - type: 'hls-from-ts' as 'hls-from-ts' - }) - - cleaner() - - return outputPath - } catch (err) { - cleaner() - - throw err - } + return generateHlsPlaylistCommon({ + video: options.video, + resolution: options.resolution, + isPortraitMode: options.isPortraitMode, + inputPath: options.concatenatedTsFilePath, + type: 'hls-from-ts' as 'hls-from-ts' + }) } // Generate an HLS playlist from an input file, and update the master playlist -- 2.41.0