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"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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 {
|
func (e *Encoder) Transcode(input string, job *types.Job, metadata *types.Metadata, interlaced bool, streamLangs []StreamMetadata) error {
|
||||||
dir := filepath.Dir(input)
|
dir := filepath.Dir(input)
|
||||||
audioWavs, err := e.extractAudio(input, dir)
|
audioWavs, err := e.extractAudio(input, dir, streamLangs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("extracting audio: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Encoder) extractAudio(input, dir string) ([]string, error) {
|
// audioStreamsInSourceOrder returns the audio entries of streamLangs sorted by
|
||||||
cmd := exec.Command(e.ffmpegPath, "-i", input,
|
// their source-side Index. The resulting slice position is the 0-based audio
|
||||||
"-vn", "-c:a", "pcm_s16le", "-ar", "48000",
|
// stream index used by ffmpeg selectors like `0:a:<n>`.
|
||||||
"-f", "wav", filepath.Join(dir, "audio.wav"))
|
func audioStreamsInSourceOrder(streamLangs []StreamMetadata) []StreamMetadata {
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
var audio []StreamMetadata
|
||||||
return nil, fmt.Errorf("ffmpeg extract: %s %w", out, err)
|
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"))
|
wavs := make([]string, 0, len(audio))
|
||||||
return matches, nil
|
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) {
|
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, "-c:s", "copy")
|
||||||
args = append(args, "-map_metadata", "-1")
|
args = append(args, "-map_metadata", "-1")
|
||||||
|
|
||||||
for _, stream := range streamLangs {
|
// Walk opusFiles by output audio index (0..N-1) and look up the source
|
||||||
if stream.CodecType == "audio" && stream.Language != "" {
|
// audio language at the same source-audio-index position. This keeps the
|
||||||
idx := 1
|
// `-metadata:s:a:i` index aligned with the output stream order, regardless
|
||||||
for _, s := range streamLangs {
|
// of whether some source streams lacked a language tag.
|
||||||
if s.CodecType == "audio" && s.Index < stream.Index {
|
audioStreams := audioStreamsInSourceOrder(streamLangs)
|
||||||
idx++
|
for i := range opusFiles {
|
||||||
}
|
if i >= len(audioStreams) {
|
||||||
}
|
break
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
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 != "" {
|
if stream.CodecType == "subtitle" && stream.Language != "" {
|
||||||
idx := 0
|
idx := 0
|
||||||
for _, s := range streamLangs {
|
for _, s := range streamLangs {
|
||||||
|
|||||||
Reference in New Issue
Block a user