🏡 index : ironmagma/quintodrome.git

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
}