fix: preserve all audio tracks and align language metadata indices
Bug #5: extractAudio invoked ffmpeg without `-map`, so default stream selection kept only one audio track from sources with multiple audio streams (e.g. eng/fra/jpn Blu-rays). It now enumerates audio streams from the streamLangs already fetched by the caller and runs one `ffmpeg -map 0🅰️<n>` per stream, writing audio.<n>.wav. Bug #11: the audio language metadata loop in encodeVideo computed the output index by counting source-side audio streams with a lower Index, which drifted when some source streams lacked a language tag. It now walks opusFiles in output order and looks up the language at the matching source-audio position via a sorted helper. These ship together because #11 was masked by #5: when only one audio track survived extraction, the broken index calculation never produced a visible misalignment. Fixing #5 alone would have caused multi-track outputs with shuffled language tags; both fixes are required to land correct multi-track output.
This commit is contained in:
+50
-19
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -223,7 +224,7 @@ func (e *Encoder) calculateZscaleWidth(path string, originalHeight int) (string,
|
||||
|
||||
func (e *Encoder) Transcode(input string, job *types.Job, metadata *types.Metadata, interlaced bool, streamLangs []StreamMetadata) error {
|
||||
dir := filepath.Dir(input)
|
||||
audioWavs, err := e.extractAudio(input, dir)
|
||||
audioWavs, err := e.extractAudio(input, dir, streamLangs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("extracting audio: %w", err)
|
||||
}
|
||||
@@ -242,16 +243,39 @@ func (e *Encoder) Transcode(input string, job *types.Job, metadata *types.Metada
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Encoder) extractAudio(input, dir string) ([]string, error) {
|
||||
cmd := exec.Command(e.ffmpegPath, "-i", input,
|
||||
"-vn", "-c:a", "pcm_s16le", "-ar", "48000",
|
||||
"-f", "wav", filepath.Join(dir, "audio.wav"))
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return nil, fmt.Errorf("ffmpeg extract: %s %w", out, err)
|
||||
// audioStreamsInSourceOrder returns the audio entries of streamLangs sorted by
|
||||
// their source-side Index. The resulting slice position is the 0-based audio
|
||||
// stream index used by ffmpeg selectors like `0:a:<n>`.
|
||||
func audioStreamsInSourceOrder(streamLangs []StreamMetadata) []StreamMetadata {
|
||||
var audio []StreamMetadata
|
||||
for _, s := range streamLangs {
|
||||
if s.CodecType == "audio" {
|
||||
audio = append(audio, s)
|
||||
}
|
||||
}
|
||||
sort.Slice(audio, func(i, j int) bool { return audio[i].Index < audio[j].Index })
|
||||
return audio
|
||||
}
|
||||
|
||||
func (e *Encoder) extractAudio(input, dir string, streamLangs []StreamMetadata) ([]string, error) {
|
||||
audio := audioStreamsInSourceOrder(streamLangs)
|
||||
if len(audio) == 0 {
|
||||
return nil, fmt.Errorf("no audio streams found")
|
||||
}
|
||||
|
||||
matches, _ := filepath.Glob(filepath.Join(dir, "audio.wav"))
|
||||
return matches, nil
|
||||
wavs := make([]string, 0, len(audio))
|
||||
for srcAudioIndex := range audio {
|
||||
wavPath := filepath.Join(dir, fmt.Sprintf("audio.%d.wav", srcAudioIndex))
|
||||
cmd := exec.Command(e.ffmpegPath, "-i", input,
|
||||
"-map", fmt.Sprintf("0:a:%d", srcAudioIndex),
|
||||
"-vn", "-c:a", "pcm_s16le", "-ar", "48000",
|
||||
"-f", "wav", wavPath)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
return wavs, fmt.Errorf("ffmpeg extract a:%d: %s %w", srcAudioIndex, out, err)
|
||||
}
|
||||
wavs = append(wavs, wavPath)
|
||||
}
|
||||
return wavs, nil
|
||||
}
|
||||
|
||||
func (e *Encoder) encodeOpus(wavs []string, dir string) ([]string, error) {
|
||||
@@ -319,17 +343,24 @@ func (e *Encoder) encodeVideo(input string, opusFiles []string, job *types.Job,
|
||||
args = append(args, "-c:s", "copy")
|
||||
args = append(args, "-map_metadata", "-1")
|
||||
|
||||
for _, stream := range streamLangs {
|
||||
if stream.CodecType == "audio" && stream.Language != "" {
|
||||
idx := 1
|
||||
for _, s := range streamLangs {
|
||||
if s.CodecType == "audio" && s.Index < stream.Index {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
args = append(args, fmt.Sprintf("-metadata:s:a:%d", idx-1), fmt.Sprintf("language=%s", stream.Language))
|
||||
fmt.Printf("DEBUG setting audio language: stream %d -> language=%s\n", idx-1, stream.Language)
|
||||
// Walk opusFiles by output audio index (0..N-1) and look up the source
|
||||
// audio language at the same source-audio-index position. This keeps the
|
||||
// `-metadata:s:a:i` index aligned with the output stream order, regardless
|
||||
// of whether some source streams lacked a language tag.
|
||||
audioStreams := audioStreamsInSourceOrder(streamLangs)
|
||||
for i := range opusFiles {
|
||||
if i >= len(audioStreams) {
|
||||
break
|
||||
}
|
||||
lang := audioStreams[i].Language
|
||||
if lang == "" {
|
||||
continue
|
||||
}
|
||||
args = append(args, fmt.Sprintf("-metadata:s:a:%d", i), fmt.Sprintf("language=%s", lang))
|
||||
fmt.Printf("DEBUG setting audio language: stream %d -> language=%s\n", i, lang)
|
||||
}
|
||||
|
||||
for _, stream := range streamLangs {
|
||||
if stream.CodecType == "subtitle" && stream.Language != "" {
|
||||
idx := 0
|
||||
for _, s := range streamLangs {
|
||||
|
||||
Reference in New Issue
Block a user