MCPcopy Create free account
hub / github.com/heygen-com/hyperframes / muxVideoWithAudio

Function muxVideoWithAudio

packages/engine/src/services/chunkEncoder.ts:738–802  ·  view source on GitHub ↗
(
  videoPath: string,
  audioPath: string,
  outputPath: string,
  signal?: AbortSignal,
  config?: MuxVideoWithAudioOptions,
  fps?: Fps,
)

Source from the content-addressed store, hash-verified

736}
737
738export async function muxVideoWithAudio(
739 videoPath: string,
740 audioPath: string,
741 outputPath: string,
742 signal?: AbortSignal,
743 config?: MuxVideoWithAudioOptions,
744 fps?: Fps,
745): Promise<MuxResult> {
746 const outputDir = dirname(outputPath);
747 if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
748
749 const isWebm = outputPath.endsWith(".webm");
750 const isMov = outputPath.endsWith(".mov");
751 const shouldCopyAudio = isWebm ? false : await shouldCopyAacSidecar(audioPath, config);
752 const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
753
754 if (isWebm) {
755 args.push("-c:a", "libopus", "-b:a", "128k");
756 } else if (isMov) {
757 if (shouldCopyAudio) {
758 args.push("-c:a", "copy");
759 } else {
760 args.push("-c:a", "aac", "-b:a", "192k");
761 }
762 } else {
763 // processCompositionAudio (audioMixer.ts) performs the AAC encode and
764 // owns the single encoder-priming interval. Copying that sidecar into
765 // MP4 preserves the correct priming metadata; re-encoding it during mux
766 // creates another priming interval that ffmpeg writes as an empty leading
767 // video edit list, which QuickTime/Safari render as a black first frame.
768 if (shouldCopyAudio) {
769 args.push("-c:a", "copy", "-movflags", "+faststart");
770 } else {
771 args.push("-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart");
772 }
773 }
774 // PTS bases can diverge during mux and reintroduce negative DTS. See
775 // buildEncoderArgs for the full reasoning on why that breaks playback.
776 args.push("-avoid_negative_ts", "make_zero");
777 if (fps !== undefined) {
778 // Set the exact output framerate so the muxer doesn't PTS-average a
779 // fractional rational like `360000/12001` instead of `30/1` into the
780 // output container metadata. `-c:v copy` is retained; no re-encode.
781 args.push("-r", fpsToFfmpegArg(fps));
782 }
783 args.push("-y", outputPath);
784
785 const processTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
786 const result = await runFfmpeg(args, { signal, timeout: processTimeout });
787
788 if (signal?.aborted) {
789 return {
790 success: false,
791 outputPath,
792 durationMs: result.durationMs,
793 error: "FFmpeg mux cancelled",
794 };
795 }

Callers 3

assembleFunction · 0.90
runAssembleStageFunction · 0.90

Calls 4

fpsToFfmpegArgFunction · 0.90
shouldCopyAacSidecarFunction · 0.85
formatFfmpegErrorFunction · 0.85
runFfmpegFunction · 0.50

Tested by

no test coverage detected