0.2.69 - Homepage loading improvements pass

This commit is contained in:
ponzischeme89
2026-08-16 21:20:55 +12:00
parent b9374baaf1
commit 845fa349e4
23 changed files with 663 additions and 262 deletions
+53 -13
View File
@@ -20,9 +20,12 @@ import (
// proportion to a feature whose fallback is simply not showing a button.
const (
// sustainSeconds is how long the credit-like state has to persist to count. Shorter than
// this and a dark establishing shot at the end of an act qualifies.
sustainSeconds = 45
// sustainSeconds is how long the credit-like state has to persist to count. Thirty
// seconds still rejects an ordinary fade or end-of-act beat, but admits the compact
// closing rolls common to half-hour and network television. It also fits inside the
// thirty-second forward half of the fine pass; the old forty-five-second requirement
// made refinement around a correctly centred transition impossible.
sustainSeconds = 30
// leadSeconds is how much ordinary programme has to precede the transition. Without it
// the changepoint can sit at the very first sampled frame, which is not evidence of a
@@ -34,10 +37,23 @@ const (
// "slightly more dark" in the middle of a night scene.
creditLikeFloor = 0.45
// onsetMeanCeiling requires the candidate cut itself to be genuinely dark. A dim final
// scene can share the roll's edge density and be followed by real credits soon enough to
// lift the sustain average; accepting it clips the scene. The detector deliberately does
// not claim bright or picture-backed credits without behavioural corroboration.
onsetMeanCeiling = 0.10
// minSeparation is how much better the tail has to score than the head. This is the
// primary guard against answering on noise, and it is set high because the cost of a
// wrong marker is somebody losing the end of an episode.
minSeparation = 0.18
// primary guard against answering on noise. A dark programme can make the absolute gap
// modest even at its real credits; the stay-credit-like guard below is what makes this
// lower bar safe.
minSeparation = 0.10
// minCreditLikeTailFraction says that credits continue to the end of the file. A dark
// scene may satisfy the short sustain window, but ordinary programme resumes afterwards;
// a real roll leaves most remaining samples credit-like even with black gaps, logos and
// production cards mixed through it.
minCreditLikeTailFraction = 0.65
// onsetFraction is how credit-like a frame has to be, relative to the established roll,
// to count as already part of it.
@@ -142,13 +158,17 @@ func (d *VisualDetector) Detect(ctx context.Context, media MediaInfo) (Detection
func creditScore(frame frameStats) float64 {
darkness := frame.DarkFraction
// Text produces a narrow *band* of edge density: a flat black frame has almost none, and
// a detailed photograph has far more than titles do. Scoring the band rather than the
// magnitude is what stops a bright, busy scene outscoring the credits.
const idealEdges = 0.030
// Text produces a band of edge density: a flat black frame has almost none, and a
// detailed photograph has far more than titles do. The useful band is deliberately
// broad. A sparse title card and a dense cast roll are both credits; the old triangle
// reached zero at twice one narrow ideal and rejected the latter outright.
const (
idealEdges = 0.020
maxEdges = 0.120
)
text := frame.EdgeDensity / idealEdges
if text > 1 {
text = 2 - text
text = 1 - (frame.EdgeDensity-idealEdges)/(maxEdges-idealEdges)
}
text = clamp01(text)
@@ -190,8 +210,13 @@ func findTransition(frames []frameStats, interval time.Duration) (int, float64,
// a few hundred frames, but the quadratic version is the kind of thing that stops being
// free the moment somebody widens the window.
prefix := make([]float64, len(scores)+1)
qualifying := make([]int, len(scores)+1)
for index, score := range scores {
prefix[index+1] = prefix[index] + score
qualifying[index+1] = qualifying[index]
if score >= creditLikeFloor {
qualifying[index+1]++
}
}
segmentMean := func(from, to int) float64 {
if to <= from {
@@ -202,6 +227,9 @@ func findTransition(frames []frameStats, interval time.Duration) (int, float64,
bestIndex, bestSeparation, found := 0, 0.0, false
for split := lead; split+sustain <= len(frames); split++ {
if frames[split].Mean > onsetMeanCeiling {
continue
}
head := segmentMean(0, split)
tail := segmentMean(split, len(frames))
// The sustained window immediately after the split has to qualify on its own, not
@@ -210,6 +238,11 @@ func findTransition(frames []frameStats, interval time.Duration) (int, float64,
if segmentMean(split, split+sustain) < creditLikeFloor {
continue
}
tailCount := len(frames) - split
creditLikeTail := qualifying[len(scores)] - qualifying[split]
if float64(creditLikeTail)/float64(tailCount) < minCreditLikeTailFraction {
continue
}
separation := tail - head
if separation < minSeparation || separation <= bestSeparation {
continue
@@ -222,8 +255,15 @@ func findTransition(frames []frameStats, interval time.Duration) (int, float64,
// The separation is reported for the split that earned it, never for the walked-back
// frame: confidence is a statement about how clearly the transition was found, and
// recomputing it over a fade would report the answer as weaker for having been improved.
return backOffToOnset(scores, bestIndex, segmentMean(bestIndex, min(bestIndex+sustain, len(scores))), interval),
bestSeparation, true
onset := backOffToOnset(
scores, bestIndex, segmentMean(bestIndex, min(bestIndex+sustain, len(scores))), interval,
)
// The score-only walk can step back onto a dim final picture whose texture resembles
// text. Preserve the same luminance guard that qualified the split itself.
for onset < bestIndex && frames[onset].Mean > onsetMeanCeiling {
onset++
}
return onset, bestSeparation, true
}
// backOffToOnset walks a split backwards through the credits' fade-in.