This commit is contained in:
Linrador 2026-06-06 22:08:37 +02:00
parent 1a559fe76d
commit 7df52ba9ef
4 changed files with 233 additions and 35 deletions

View File

@ -2462,6 +2462,7 @@ type analyzeLabelSegmentPoint struct {
Label string Label string
Start float64 Start float64
End float64 End float64
Time float64
Score float64 Score float64
} }
@ -2550,10 +2551,19 @@ func buildLabelContinuitySegmentsFromAnalyzeHits(
score = 1 score = 1
} }
markerTime := hit.Time
if markerTime < 0 {
markerTime = (start + end) / 2
}
if duration > 0 {
markerTime = math.Max(0, math.Min(markerTime, duration))
}
byLabel[key] = append(byLabel[key], analyzeLabelSegmentPoint{ byLabel[key] = append(byLabel[key], analyzeLabelSegmentPoint{
Label: label, Label: label,
Start: start, Start: start,
End: end, End: end,
Time: markerTime,
Score: score, Score: score,
}) })
} }
@ -2580,6 +2590,8 @@ func buildLabelContinuitySegmentsFromAnalyzeHits(
curEndMarker := points[0].End curEndMarker := points[0].End
curScoreSum := points[0].Score curScoreSum := points[0].Score
curScoreCount := 1 curScoreCount := 1
curMarkerTime := points[0].Time
curBestScore := points[0].Score
for i := 1; i < len(points); i++ { for i := 1; i < len(points); i++ {
n := points[i] n := points[i]
@ -2598,6 +2610,10 @@ func buildLabelContinuitySegmentsFromAnalyzeHits(
curScoreSum += n.Score curScoreSum += n.Score
curScoreCount++ curScoreCount++
if n.Score > curBestScore {
curBestScore = n.Score
curMarkerTime = n.Time
}
continue continue
} }
@ -2605,6 +2621,7 @@ func buildLabelContinuitySegmentsFromAnalyzeHits(
curLabel, curLabel,
curStartMarker, curStartMarker,
curEndMarker, curEndMarker,
curMarkerTime,
curScoreSum, curScoreSum,
curScoreCount, curScoreCount,
halfSample, halfSample,
@ -2620,12 +2637,15 @@ func buildLabelContinuitySegmentsFromAnalyzeHits(
curEndMarker = n.End curEndMarker = n.End
curScoreSum = n.Score curScoreSum = n.Score
curScoreCount = 1 curScoreCount = 1
curMarkerTime = n.Time
curBestScore = n.Score
} }
segment := makeLabelContinuitySegment( segment := makeLabelContinuitySegment(
curLabel, curLabel,
curStartMarker, curStartMarker,
curEndMarker, curEndMarker,
curMarkerTime,
curScoreSum, curScoreSum,
curScoreCount, curScoreCount,
halfSample, halfSample,
@ -2654,6 +2674,7 @@ func makeLabelContinuitySegment(
label string, label string,
startMarker float64, startMarker float64,
endMarker float64, endMarker float64,
markerTime float64,
scoreSum float64, scoreSum float64,
scoreCount int, scoreCount int,
halfSample float64, halfSample float64,
@ -2680,6 +2701,13 @@ func makeLabelContinuitySegment(
score = scoreSum / float64(scoreCount) score = scoreSum / float64(scoreCount)
} }
if markerTime < 0 {
markerTime = (start + end) / 2
}
if duration > 0 {
markerTime = math.Max(0, math.Min(markerTime, duration))
}
return aiSegmentMeta{ return aiSegmentMeta{
Label: label, Label: label,
StartSeconds: start, StartSeconds: start,
@ -2689,6 +2717,8 @@ func makeLabelContinuitySegment(
AutoSelected: true, AutoSelected: true,
Position: segmentPositionFromAnalyzeLabel(label), Position: segmentPositionFromAnalyzeLabel(label),
Tags: segmentTagsFromAnalyzeLabel(label), Tags: segmentTagsFromAnalyzeLabel(label),
MarkerSeconds: markerTime,
PreviewSeconds: markerTime,
} }
} }

View File

@ -104,6 +104,8 @@ type aiSegmentMeta struct {
AutoSelected bool `json:"autoSelected,omitempty"` AutoSelected bool `json:"autoSelected,omitempty"`
Position string `json:"position,omitempty"` Position string `json:"position,omitempty"`
Tags []string `json:"tags,omitempty"` Tags []string `json:"tags,omitempty"`
MarkerSeconds float64 `json:"markerSeconds,omitempty"`
PreviewSeconds float64 `json:"previewSeconds,omitempty"`
} }
type aiAnalysisMeta struct { type aiAnalysisMeta struct {

View File

@ -23,11 +23,11 @@ type aiRatingMeta struct {
const ( const (
// Normales Zusammenziehen sehr kurzer Pausen. // Normales Zusammenziehen sehr kurzer Pausen.
ratingMaxSilentGapSec = 6.5 ratingMaxSilentGapSec = 4.5
// Größere Lücken dürfen überbrückt werden, // Größere Lücken dürfen überbrückt werden,
// wenn beide Seiten stark genug / thematisch ähnlich sind. // wenn beide Seiten stark genug / thematisch ähnlich sind.
ratingBridgeStrongGapSec = 35.0 ratingBridgeStrongGapSec = 12.0
ratingMinSegmentDurationSec = 2.5 ratingMinSegmentDurationSec = 2.5
) )
@ -743,7 +743,41 @@ func ratingBridgeStrengthFromSet(set ratingSignalSet) float64 {
return strength return strength
} }
func ratingPositionSetFromLabels(labels map[string]bool) map[string]bool {
out := map[string]bool{}
for label := range labels {
normalized := normalizeRatingSignalLabel(label)
if strings.HasPrefix(normalized, "position:") {
out[normalized] = true
}
}
return out
}
func ratingPositionSetsConflict(a map[string]bool, b map[string]bool) bool {
ap := ratingPositionSetFromLabels(a)
bp := ratingPositionSetFromLabels(b)
if len(ap) == 0 || len(bp) == 0 {
return false
}
for label := range ap {
if bp[label] {
return false
}
}
return true
}
func ratingLabelsBridgeCompatible(a map[string]bool, b map[string]bool) bool { func ratingLabelsBridgeCompatible(a map[string]bool, b map[string]bool) bool {
if ratingPositionSetsConflict(a, b) {
return false
}
for la := range a { for la := range a {
na := normalizeRatingSignalLabel(la) na := normalizeRatingSignalLabel(la)
if na == "" { if na == "" {
@ -765,9 +799,11 @@ func ratingLabelsBridgeCompatible(a map[string]bool, b map[string]bool) bool {
as := ratingSignalSetFromLabels(a) as := ratingSignalSetFromLabels(a)
bs := ratingSignalSetFromLabels(b) bs := ratingSignalSetFromLabels(b)
if as.HasPosition && bs.HasPosition { // Wichtig:
return true // Unterschiedliche Positionen NICHT pauschal verbinden.
} // Vorher hat "Doggy" + "Reverse Cowgirl" als kompatibel gegolten,
// nur weil beide irgendeine Position waren.
if as.HasBody && bs.HasBody { if as.HasBody && bs.HasBody {
return true return true
} }
@ -845,6 +881,94 @@ func mergeRatingActivitySegments(
scoreSum float64 scoreSum float64
scoreWeight float64 scoreWeight float64
labels map[string]bool labels map[string]bool
positionWeights map[string]float64
marker float64
markerWeight float64
}
var addRatingPositionWeight func(weights map[string]float64, label string, weight float64)
addRatingPositionWeight = func(weights map[string]float64, label string, weight float64) {
label = strings.ToLower(strings.TrimSpace(label))
if label == "" {
return
}
if strings.HasPrefix(label, "combo:") {
raw := strings.TrimPrefix(label, "combo:")
for _, part := range strings.Split(raw, "+") {
addRatingPositionWeight(weights, part, weight)
}
return
}
normalized := normalizeRatingSignalLabel(label)
if strings.HasPrefix(normalized, "position:") {
weights[normalized] += weight
}
}
effectiveRatingLabelsForBlock := func(labels map[string]bool, positionWeights map[string]float64) map[string]bool {
out := map[string]bool{}
bestPosition := ""
bestWeight := -1.0
for label := range labels {
normalized := normalizeRatingSignalLabel(label)
if normalized == "" {
continue
}
if strings.HasPrefix(normalized, "position:") {
w := positionWeights[normalized]
if w <= 0 {
w = ratingSegmentSeverity(normalized)
}
if w > bestWeight {
bestWeight = w
bestPosition = normalized
}
continue
}
out[normalized] = true
}
if bestPosition != "" {
out[bestPosition] = true
}
return out
}
segmentMarker := func(s aiSegmentMeta) float64 {
if s.PreviewSeconds > 0 {
return s.PreviewSeconds
}
if s.MarkerSeconds > 0 {
return s.MarkerSeconds
}
if s.EndSeconds > s.StartSeconds {
return (s.StartSeconds + s.EndSeconds) / 2
}
return s.StartSeconds
}
segmentMarkerWeight := func(s aiSegmentMeta) float64 {
conf := ratingClamp01(s.Score)
if conf <= 0 {
conf = 0.50
}
sev := ratingSegmentSeverity(s.Label)
if sev <= 0 {
sev = 0.50
}
return conf * sev
} }
newBlock := func(s aiSegmentMeta) activityBlock { newBlock := func(s aiSegmentMeta) activityBlock {
@ -857,12 +981,21 @@ func mergeRatingActivitySegments(
conf = 0.50 conf = 0.50
} }
positionWeights := map[string]float64{}
addRatingPositionWeight(positionWeights, s.Label, conf*math.Max(1, dur))
marker := segmentMarker(s)
markerWeight := segmentMarkerWeight(s)
return activityBlock{ return activityBlock{
start: s.StartSeconds, start: s.StartSeconds,
end: s.EndSeconds, end: s.EndSeconds,
scoreSum: conf * dur, scoreSum: conf * dur,
scoreWeight: dur, scoreWeight: dur,
labels: labels, labels: labels,
positionWeights: positionWeights,
marker: marker,
markerWeight: markerWeight,
} }
} }
@ -872,7 +1005,8 @@ func mergeRatingActivitySegments(
return aiSegmentMeta{}, false return aiSegmentMeta{}, false
} }
label := buildRatingComboLabel(b.labels) effectiveLabels := effectiveRatingLabelsForBlock(b.labels, b.positionWeights)
label := buildRatingComboLabel(effectiveLabels)
if label == "" { if label == "" {
return aiSegmentMeta{}, false return aiSegmentMeta{}, false
} }
@ -901,6 +1035,8 @@ func mergeRatingActivitySegments(
AutoSelected: true, AutoSelected: true,
Position: segmentPositionFromAnalyzeLabel(label), Position: segmentPositionFromAnalyzeLabel(label),
Tags: segmentTagsFromAnalyzeLabel(label), Tags: segmentTagsFromAnalyzeLabel(label),
MarkerSeconds: b.marker,
PreviewSeconds: b.marker,
}, true }, true
} }
@ -956,8 +1092,17 @@ func mergeRatingActivitySegments(
if conf <= 0 { if conf <= 0 {
conf = 0.50 conf = 0.50
} }
cur.scoreSum += conf * dur cur.scoreSum += conf * dur
cur.scoreWeight += dur cur.scoreWeight += dur
addRatingPositionWeight(cur.positionWeights, n.Label, conf*math.Max(1, dur))
}
nextMarkerWeight := segmentMarkerWeight(n)
if nextMarkerWeight > cur.markerWeight {
cur.markerWeight = nextMarkerWeight
cur.marker = segmentMarker(n)
} }
addRatingLabelsFromSegment(cur.labels, n.Label) addRatingLabelsFromSegment(cur.labels, n.Label)

View File

@ -888,22 +888,13 @@ function segmentAnchorTime(
obj: Record<string, unknown>, obj: Record<string, unknown>,
durationSeconds: number durationSeconds: number
): number | undefined { ): number | undefined {
const start = firstSegmentTimeNumber(obj, [
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'startMs',
'startRatio',
'startPercent',
'startPct',
], durationSeconds)
const marker = firstSegmentTimeNumber(obj, [ const marker = firstSegmentTimeNumber(obj, [
'markerTime', 'previewSeconds',
'markerSec', 'previewSec',
'previewTime',
'markerSeconds', 'markerSeconds',
'markerSec',
'markerTime',
'time', 'time',
't', 't',
'timestamp', 'timestamp',
@ -918,18 +909,41 @@ function segmentAnchorTime(
'timestampPct', 'timestampPct',
], durationSeconds) ], durationSeconds)
if (typeof start === 'number') return start
if (typeof marker === 'number') return marker if (typeof marker === 'number') return marker
return undefined const start = firstSegmentTimeNumber(obj, [
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'startMs',
'startRatio',
'startPercent',
'startPct',
], durationSeconds)
return typeof start === 'number' ? start : undefined
} }
function segmentPreviewTime( function segmentPreviewTime(
obj: Record<string, unknown>, obj: Record<string, unknown>,
durationSeconds: number durationSeconds: number
): number | undefined { ): number | undefined {
const anchor = segmentAnchorTime(obj, durationSeconds) const marker = segmentAnchorTime(obj, durationSeconds)
if (typeof anchor === 'number') return anchor if (typeof marker === 'number') return marker
const start = firstSegmentTimeNumber(obj, [
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'startMs',
'startRatio',
'startPercent',
'startPct',
], durationSeconds)
const end = firstSegmentTimeNumber(obj, [ const end = firstSegmentTimeNumber(obj, [
'endSec', 'endSec',
@ -943,7 +957,14 @@ function segmentPreviewTime(
'endPct', 'endPct',
], durationSeconds) ], durationSeconds)
return typeof end === 'number' ? end : undefined if (typeof start === 'number' && typeof end === 'number') {
return Math.max(0, (start + end) / 2)
}
if (typeof start === 'number') return start
if (typeof end === 'number') return end
return undefined
} }
function segmentOpenTime( function segmentOpenTime(