mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-17 13:10:27 -04:00
* refactor: rename core/transcode directory to core/stream * refactor: update all imports from core/transcode to core/stream * refactor: rename exported symbols to fit core/stream package name * refactor: simplify MediaStreamer interface to single NewStream method Remove the two-method interface (NewStream + DoStream) in favor of a single NewStream(ctx, mf, req) method. Callers are now responsible for fetching the MediaFile before calling NewStream. This removes the implicit DB lookup from the streamer, making it a pure streaming concern. * refactor: update all callers from DoStream to NewStream * chore: update wire_gen.go and stale comment for core/stream rename * refactor: update wire command to handle GO_BUILD_TAGS correctly Signed-off-by: Deluan <deluan@navidrome.org> * fix: distinguish not-found from internal errors in public stream handler * refactor: remove unused ID field from stream.Request * refactor: simplify ResolveRequestFromToken to receive *model.MediaFile Move MediaFile fetching responsibility to callers, making the method focused on token validation and request resolution. Remove ErrMediaNotFound (no longer produced). Update GetTranscodeStream handler to fetch the media file before calling ResolveRequestFromToken. * refactor: extend tokenTTL from 12 to 48 hours Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
172 lines
4.8 KiB
Go
172 lines
4.8 KiB
Go
package stream
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// adjustResult represents the outcome of applying a limitation to a transcoded stream value
|
|
type adjustResult int
|
|
|
|
const (
|
|
adjustNone adjustResult = iota // Value already satisfies the limitation
|
|
adjustAdjusted // Value was changed to fit the limitation
|
|
adjustCannotFit // Cannot satisfy the limitation (reject this profile)
|
|
)
|
|
|
|
// checkLimitations checks codec profile limitations against source stream details.
|
|
// Returns "" if all limitations pass, or a typed reason string for the first failure.
|
|
func checkLimitations(src *Details, limitations []Limitation) string {
|
|
for _, lim := range limitations {
|
|
var ok bool
|
|
var reason string
|
|
|
|
switch lim.Name {
|
|
case LimitationAudioChannels:
|
|
ok = checkIntLimitation(src.Channels, lim.Comparison, lim.Values)
|
|
reason = "audio channels not supported"
|
|
case LimitationAudioSamplerate:
|
|
ok = checkIntLimitation(src.SampleRate, lim.Comparison, lim.Values)
|
|
reason = "audio samplerate not supported"
|
|
case LimitationAudioBitrate:
|
|
ok = checkIntLimitation(src.Bitrate, lim.Comparison, lim.Values)
|
|
reason = "audio bitrate not supported"
|
|
case LimitationAudioBitdepth:
|
|
ok = checkIntLimitation(src.BitDepth, lim.Comparison, lim.Values)
|
|
reason = "audio bitdepth not supported"
|
|
case LimitationAudioProfile:
|
|
ok = checkStringLimitation(src.Profile, lim.Comparison, lim.Values)
|
|
reason = "audio profile not supported"
|
|
default:
|
|
continue
|
|
}
|
|
|
|
if !ok && lim.Required {
|
|
return reason
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// applyLimitation adjusts a transcoded stream parameter to satisfy the limitation.
|
|
// Returns the adjustment result.
|
|
func applyLimitation(sourceBitrate int, lim *Limitation, ts *Details) adjustResult {
|
|
switch lim.Name {
|
|
case LimitationAudioChannels:
|
|
return applyIntLimitation(lim.Comparison, lim.Values, ts.Channels, func(v int) { ts.Channels = v })
|
|
case LimitationAudioBitrate:
|
|
current := ts.Bitrate
|
|
if current == 0 {
|
|
current = sourceBitrate
|
|
}
|
|
return applyIntLimitation(lim.Comparison, lim.Values, current, func(v int) { ts.Bitrate = v })
|
|
case LimitationAudioSamplerate:
|
|
return applyIntLimitation(lim.Comparison, lim.Values, ts.SampleRate, func(v int) { ts.SampleRate = v })
|
|
case LimitationAudioBitdepth:
|
|
if ts.BitDepth > 0 {
|
|
return applyIntLimitation(lim.Comparison, lim.Values, ts.BitDepth, func(v int) { ts.BitDepth = v })
|
|
}
|
|
case LimitationAudioProfile:
|
|
// TODO: implement when audio profile data is available
|
|
}
|
|
return adjustNone
|
|
}
|
|
|
|
// applyIntLimitation applies a limitation comparison to a value.
|
|
// If the value needs adjusting, calls the setter and returns the result.
|
|
func applyIntLimitation(comparison string, values []string, current int, setter func(int)) adjustResult {
|
|
if len(values) == 0 {
|
|
return adjustNone
|
|
}
|
|
|
|
switch comparison {
|
|
case ComparisonLessThanEqual:
|
|
limit, ok := parseInt(values[0])
|
|
if !ok {
|
|
return adjustNone
|
|
}
|
|
if current <= limit {
|
|
return adjustNone
|
|
}
|
|
setter(limit)
|
|
return adjustAdjusted
|
|
case ComparisonGreaterThanEqual:
|
|
limit, ok := parseInt(values[0])
|
|
if !ok {
|
|
return adjustNone
|
|
}
|
|
if current >= limit {
|
|
return adjustNone
|
|
}
|
|
// Cannot upscale
|
|
return adjustCannotFit
|
|
case ComparisonEquals:
|
|
// Check if current value matches any allowed value
|
|
for _, v := range values {
|
|
if limit, ok := parseInt(v); ok && current == limit {
|
|
return adjustNone
|
|
}
|
|
}
|
|
// Find the closest allowed value below current (don't upscale)
|
|
var closest int
|
|
found := false
|
|
for _, v := range values {
|
|
if limit, ok := parseInt(v); ok && limit < current {
|
|
if !found || limit > closest {
|
|
closest = limit
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
if found {
|
|
setter(closest)
|
|
return adjustAdjusted
|
|
}
|
|
return adjustCannotFit
|
|
case ComparisonNotEquals:
|
|
for _, v := range values {
|
|
if limit, ok := parseInt(v); ok && current == limit {
|
|
return adjustCannotFit
|
|
}
|
|
}
|
|
return adjustNone
|
|
}
|
|
|
|
return adjustNone
|
|
}
|
|
|
|
func checkIntLimitation(value int, comparison string, values []string) bool {
|
|
return applyIntLimitation(comparison, values, value, func(int) {}) == adjustNone
|
|
}
|
|
|
|
// checkStringLimitation checks a string value against a limitation.
|
|
// Only Equals and NotEquals comparisons are meaningful for strings.
|
|
// LessThanEqual/GreaterThanEqual are not applicable and always pass.
|
|
func checkStringLimitation(value string, comparison string, values []string) bool {
|
|
switch comparison {
|
|
case ComparisonEquals:
|
|
for _, v := range values {
|
|
if strings.EqualFold(value, v) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
case ComparisonNotEquals:
|
|
for _, v := range values {
|
|
if strings.EqualFold(value, v) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return true
|
|
}
|
|
|
|
func parseInt(s string) (int, bool) {
|
|
v, err := strconv.Atoi(s)
|
|
if err != nil || v < 0 {
|
|
return 0, false
|
|
}
|
|
return v, true
|
|
}
|