0.2.69 - Homepage loading improvements pass
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user