Files
av1-go/audio.go
2025-12-05 21:56:57 +01:00

136 lines
4.1 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"strings"
)
// ConvertAudioTrack processes a single audio track: extract -> normalize -> encode
func ConvertAudioTrack(index int, ch int, lang string, audioTempDir string, sourceFile string, shouldDownmix bool) (string, error) {
tempExtracted := filepath.Join(audioTempDir, fmt.Sprintf("track_%d_extracted.flac", index))
tempNormalized := filepath.Join(audioTempDir, fmt.Sprintf("track_%d_normalized.flac", index))
finalOpus := filepath.Join(audioTempDir, fmt.Sprintf("track_%d_final.opus", index))
LogInfo(" - Extracting Audio Track #%d to FLAC...", index)
ffmpegArgs := []string{
"-v", "quiet", "-stats", "-y", "-i", sourceFile, "-map", fmt.Sprintf("0:%d", index), "-map_metadata", "-1",
}
if shouldDownmix && ch >= 6 {
if ch == 6 {
ffmpegArgs = append(ffmpegArgs, "-af", "pan=stereo|c0=c2+0.30*c0+0.30*c4|c1=c2+0.30*c1+0.30*c5")
} else if ch == 8 {
ffmpegArgs = append(ffmpegArgs, "-af", "pan=stereo|c0=c2+0.30*c0+0.30*c4+0.30*c6|c1=c2+0.30*c1+0.30*c5+0.30*c7")
} else {
ffmpegArgs = append(ffmpegArgs, "-ac", "2")
}
}
ffmpegArgs = append(ffmpegArgs, "-c:a", "flac", tempExtracted)
_, err := RunCmd("ffmpeg", ffmpegArgs, false)
if err != nil {
return "", fmt.Errorf("failed to extract audio: %w", err)
}
LogInfo(" - Normalizing Audio Track #%d with ffmpeg (loudnorm 2-pass)...", index)
LogInfo(" - Pass 1: Analyzing...")
analyzeCmd := []string{
"-v", "info", "-i", tempExtracted, "-af", "loudnorm=I=-23:LRA=7:tp=-1:print_format=json", "-f", "null", "-",
}
// We need to capture stderr for the JSON output
// RunCmd with captureOutput=true captures stdout. ffmpeg prints stats to stderr.
// We need a custom run for this to capture stderr.
// Re-implementing specific run logic here for simplicity or extending RunCmd?
// Let's extend RunCmd logic locally here since it's specific.
// Actually, RunCmd returns stdout. We need stderr.
// Let's use exec.Command directly.
LogInfo("Executing: ffmpeg %s", strings.Join(analyzeCmd, " "))
cmd := exec.Command("ffmpeg", analyzeCmd...)
outputBytes, err := cmd.CombinedOutput() // loudnorm json is on stderr, but sometimes mixed.
// Actually loudnorm prints to stderr.
outputStr := string(outputBytes)
if err != nil {
return "", fmt.Errorf("loudnorm analysis failed: %w", err)
}
// Parse JSON from output
jsonStart := strings.Index(outputStr, "{")
if jsonStart == -1 {
return "", fmt.Errorf("could not find JSON start in loudnorm output")
}
// Find the matching closing brace
jsonEnd := -1
braceLevel := 0
for i, char := range outputStr[jsonStart:] {
if char == '{' {
braceLevel++
} else if char == '}' {
braceLevel--
if braceLevel == 0 {
jsonEnd = jsonStart + i + 1
break
}
}
}
if jsonEnd == -1 {
return "", fmt.Errorf("could not find JSON end in loudnorm output")
}
var stats LoudnormStats
err = json.Unmarshal([]byte(outputStr[jsonStart:jsonEnd]), &stats)
if err != nil {
return "", fmt.Errorf("failed to parse loudnorm stats: %w", err)
}
LogInfo(" - Pass 2: Applying normalization...")
filterComplex := fmt.Sprintf("loudnorm=I=-23:LRA=7:tp=-1:measured_i=%s:measured_lra=%s:measured_tp=%s:measured_thresh=%s:offset=%s",
stats.InputI, stats.InputLra, stats.InputTp, stats.InputThresh, stats.TargetOffset)
normArgs := []string{
"-v", "quiet", "-stats", "-y", "-i", tempExtracted, "-af", filterComplex, "-c:a", "flac", tempNormalized,
}
_, err = RunCmd("ffmpeg", normArgs, false)
if err != nil {
return "", fmt.Errorf("failed to apply normalization: %w", err)
}
// Bitrate selection
bitrate := "96k"
isDownmixed := shouldDownmix && ch >= 6
if isDownmixed {
bitrate = "128k"
} else {
switch ch {
case 1:
bitrate = "64k"
case 2:
bitrate = "128k"
case 6:
bitrate = "256k"
case 8:
bitrate = "384k"
default:
bitrate = "96k"
}
}
LogInfo(" - Encoding Audio Track #%d to Opus at %s...", index, bitrate)
opusArgs := []string{
"--vbr", "--bitrate", bitrate, tempNormalized, finalOpus,
}
_, err = RunCmd("opusenc", opusArgs, false)
if err != nil {
return "", fmt.Errorf("failed to encode to opus: %w", err)
}
return finalOpus, nil
}