ranim/cmd/render/
audio.rs1use std::path::Path;
8
9use anyhow::{Context, anyhow, bail};
10use ranim_core::audio::{MASTER_CHANNELS, MASTER_SAMPLE_RATE, write_wav};
11use tracing::info;
12
13use crate::OutputFormat;
14
15pub(crate) fn audio_codec(format: OutputFormat) -> Option<&'static str> {
18 match format {
19 OutputFormat::Mp4 | OutputFormat::Mov => Some("aac"),
20 OutputFormat::Webm => Some("libopus"),
21 OutputFormat::Gif => None,
22 }
23}
24
25pub(crate) fn mux_audio_into_video(
31 video_path: &Path,
32 pcm: &[f32],
33 format: OutputFormat,
34) -> anyhow::Result<()> {
35 let Some(codec) = audio_codec(format) else {
36 bail!("format {format:?} cannot store audio");
37 };
38 let wav_path = video_path.with_extension(format!("ranim-audio-{}.wav", std::process::id()));
39 write_wav(&wav_path, pcm, MASTER_SAMPLE_RATE, MASTER_CHANNELS)
40 .with_context(|| format!("failed to write {}", wav_path.display()))?;
41
42 let muxed_path = video_path.with_extension(format!(
43 "ranim-muxed-{}.{}",
44 std::process::id(),
45 video_path
46 .extension()
47 .and_then(|e| e.to_str())
48 .unwrap_or("mp4"),
49 ));
50
51 let ffmpeg = ["ffmpeg", "./ffmpeg"]
52 .into_iter()
53 .find(|bin| Path::new(bin).exists() || which::which(bin).is_ok())
54 .ok_or_else(|| anyhow!("ffmpeg not found"))?;
55 let status = std::process::Command::new(ffmpeg)
56 .args(["-y", "-v", "error", "-i"])
57 .arg(video_path)
58 .args(["-i"])
59 .arg(&wav_path)
60 .args([
61 "-map",
62 "0:v:0",
63 "-map",
64 "1:a:0",
65 "-c:v",
66 "copy",
67 "-c:a",
68 codec,
69 "-shortest",
70 ])
71 .arg(&muxed_path)
72 .status()
73 .context("failed to spawn ffmpeg for audio muxing")?;
74
75 let _ = std::fs::remove_file(&wav_path);
76 if !status.success() {
77 let _ = std::fs::remove_file(&muxed_path);
78 bail!("ffmpeg audio muxing failed with {status}");
79 }
80 std::fs::rename(&muxed_path, video_path).with_context(|| {
81 format!(
82 "failed to replace {} with muxed output",
83 video_path.display()
84 )
85 })?;
86 info!("muxed audio into {}", video_path.display());
87 Ok(())
88}