package utils
import (
"fmt"
"math"
)
type ClipWindow struct {
Start float64
End float64
}
type FinalClipMode int
const (
FinalClipNone FinalClipMode = iota
FinalClipRemainder
FinalClipFull
FinalClipExtend
)
func ParseFinalClipMode(s string) (FinalClipMode, error) {
switch s {
case "none", "":
return FinalClipNone, nil
case "remainder":
return FinalClipRemainder, nil
case "full":
return FinalClipFull, nil
case "extend":
return FinalClipExtend, nil
default:
return 0, fmt.Errorf("invalid final-clip mode %q (want one of: none, remainder, full, extend)", s)
}
}
func roundTo(x float64, precision int) float64 {
if precision < 0 {
return x
}
scale := math.Pow(10, float64(precision))
return math.Round(x*scale) / scale
}
func GenerateClipTimes(fullDuration, clipDuration, clipOverlap float64, finalClip FinalClipMode, roundingPrecision int) ([]ClipWindow, error) {
if clipDuration <= 0 {
return nil, fmt.Errorf("clipDuration must be > 0, got %v", clipDuration)
}
if clipOverlap < 0 || clipOverlap >= clipDuration {
return nil, fmt.Errorf("clipOverlap must be in [0, clipDuration), got %v with clipDuration=%v", clipOverlap, clipDuration)
}
if fullDuration <= 0 {
return nil, fmt.Errorf("fullDuration must be > 0, got %v", fullDuration)
}
starts, ends := buildClipStartsEnds(fullDuration, clipDuration, clipOverlap, roundingPrecision)
switch finalClip {
case FinalClipNone:
return dedupClips(clipWindowsNone(starts, ends, fullDuration)), nil
case FinalClipRemainder:
return dedupClips(clipWindowsRemainder(starts, ends, fullDuration)), nil
case FinalClipFull:
return dedupClips(clipWindowsFull(starts, ends, fullDuration)), nil
case FinalClipExtend:
return dedupClips(clipWindowsExtend(starts, ends)), nil
default:
return nil, fmt.Errorf("invalid FinalClipMode %d", finalClip)
}
}
func buildClipStartsEnds(fullDuration, clipDuration, clipOverlap float64, roundingPrecision int) ([]float64, []float64) {
increment := clipDuration - clipOverlap
var starts []float64
for s := 0.0; s < fullDuration; s += increment {
starts = append(starts, roundTo(s, roundingPrecision))
}
if len(starts) == 0 {
starts = []float64{0}
}
ends := make([]float64, len(starts))
for i, s := range starts {
ends[i] = s + clipDuration
}
return starts, ends
}
func clipWindowsNone(starts, ends []float64, fullDuration float64) []ClipWindow {
out := make([]ClipWindow, 0, len(starts))
for i := range starts {
if ends[i] <= fullDuration {
out = append(out, ClipWindow{Start: starts[i], End: ends[i]})
}
}
return out
}
func clipWindowsRemainder(starts, ends []float64, fullDuration float64) []ClipWindow {
out := make([]ClipWindow, 0, len(starts))
for i := range starts {
e := ends[i]
if e > fullDuration {
e = fullDuration
}
out = append(out, ClipWindow{Start: starts[i], End: e})
}
return out
}
func clipWindowsFull(starts, ends []float64, fullDuration float64) []ClipWindow {
out := make([]ClipWindow, 0, len(starts))
for i := range starts {
s, e := starts[i], ends[i]
if e > fullDuration {
s -= e - fullDuration
e = fullDuration
if s < 0 {
s = 0
}
}
out = append(out, ClipWindow{Start: s, End: e})
}
return out
}
func clipWindowsExtend(starts, ends []float64) []ClipWindow {
out := make([]ClipWindow, 0, len(starts))
for i := range starts {
out = append(out, ClipWindow{Start: starts[i], End: ends[i]})
}
return out
}
func dedupClips(in []ClipWindow) []ClipWindow {
if len(in) <= 1 {
return in
}
seen := make(map[ClipWindow]bool, len(in))
out := make([]ClipWindow, 0, len(in))
for _, c := range in {
if !seen[c] {
seen[c] = true
out = append(out, c)
}
}
return out
}