Files

126 lines
3.6 KiB
Go
Raw Permalink Normal View History

2026-05-22 20:26:11 -04:00
package playback
import (
"strings"
"github.com/Silo-Server/silo-server/internal/lang"
"github.com/Silo-Server/silo-server/internal/models"
"github.com/Silo-Server/silo-server/internal/userstore"
)
// OriginalLanguageSentinel is the value stored in audio language preference
// columns to mean "use the media item's original language." It is resolved
// to a concrete language code in the playback handler before reaching
// SelectAudioTrack.
const OriginalLanguageSentinel = "original"
// AudioTrackPreference holds a per-series audio track preference.
type AudioTrackPreference struct {
AudioTrackIndex int
AudioLanguage string
TrackSignature *userstore.AudioTrackSignature
}
func langMatch(a, b string) bool {
if a == "" || b == "" {
return false
}
return lang.Canonical(a) == lang.Canonical(b)
}
// SelectAudioTrack determines which audio track to use based on preferences.
//
// Priority:
// 1. Series preference exact track signature
// 2. Series preference index (if track exists at that index with matching language)
// 3. Series preference language (first track matching that language)
// 4. Profile preferred language (first track matching)
// 5. File's default track (first track with Default: true)
// 6. First track (index 0)
func SelectAudioTrack(tracks []models.AudioTrack, preferredLang string, seriesPref *AudioTrackPreference) int {
if len(tracks) == 0 {
return 0
}
// 1. Series preference: try exact signature match first.
if seriesPref != nil {
if idx := findExactAudioTrack(tracks, seriesPref.TrackSignature); idx >= 0 {
return idx
}
// 2. Series preference: try exact index+language match.
if seriesPref.AudioTrackIndex >= 0 && seriesPref.AudioTrackIndex < len(tracks) {
if langMatch(tracks[seriesPref.AudioTrackIndex].Language, seriesPref.AudioLanguage) {
return seriesPref.AudioTrackIndex
}
}
// 3. Series preference: fall back to language match.
if seriesPref.AudioLanguage != "" {
for i, t := range tracks {
if langMatch(t.Language, seriesPref.AudioLanguage) {
return i
}
}
}
}
// 4. Profile language preference.
if preferredLang != "" {
for i, t := range tracks {
if langMatch(t.Language, preferredLang) {
return i
}
}
}
// 5. File's default track.
for i, t := range tracks {
if t.Default {
return i
}
}
// 6. First track.
return 0
}
// MatchAudioTrackAcrossVersions maps a selection made against one file's
// audio inventory onto another version of the same content. Track ordering is
// not stable across encodes, so carrying the raw ordinal can select a different
// language. Prefer the stable signature, then the selected language, and
// finally the effective file's default track.
func MatchAudioTrackAcrossVersions(
requestedTracks []models.AudioTrack,
effectiveTracks []models.AudioTrack,
requestedIndex int,
) int {
if len(effectiveTracks) == 0 {
return 0
}
if len(requestedTracks) == 0 {
return SelectAudioTrack(effectiveTracks, "", nil)
}
if requestedIndex < 0 || requestedIndex >= len(requestedTracks) {
requestedIndex = SelectAudioTrack(requestedTracks, "", nil)
}
selected := requestedTracks[requestedIndex]
return SelectAudioTrack(effectiveTracks, "", &AudioTrackPreference{
AudioTrackIndex: requestedIndex,
AudioLanguage: selected.Language,
TrackSignature: AudioTrackSignatureFromTrack(selected),
})
}
2026-05-22 20:26:11 -04:00
// BrowserSupportsAudioCodec returns true if the given audio codec can be
// played natively by web browsers without transcoding.
func BrowserSupportsAudioCodec(codec string) bool {
switch strings.ToLower(codec) {
case "aac", "mp3", "opus", "vorbis", "flac":
return true
default:
return false
}
}