From 7df52ba9ef5a739401c76ea679a357fdeafbe94f Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:08:37 +0200 Subject: [PATCH] bugfixes --- backend/analyze.go | 30 ++++ backend/meta.go | 2 + backend/rating.go | 177 +++++++++++++++++-- frontend/src/components/ui/RatingOverlay.tsx | 59 +++++-- 4 files changed, 233 insertions(+), 35 deletions(-) diff --git a/backend/analyze.go b/backend/analyze.go index 98501fe..b6a5f24 100644 --- a/backend/analyze.go +++ b/backend/analyze.go @@ -2462,6 +2462,7 @@ type analyzeLabelSegmentPoint struct { Label string Start float64 End float64 + Time float64 Score float64 } @@ -2550,10 +2551,19 @@ func buildLabelContinuitySegmentsFromAnalyzeHits( 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{ Label: label, Start: start, End: end, + Time: markerTime, Score: score, }) } @@ -2580,6 +2590,8 @@ func buildLabelContinuitySegmentsFromAnalyzeHits( curEndMarker := points[0].End curScoreSum := points[0].Score curScoreCount := 1 + curMarkerTime := points[0].Time + curBestScore := points[0].Score for i := 1; i < len(points); i++ { n := points[i] @@ -2598,6 +2610,10 @@ func buildLabelContinuitySegmentsFromAnalyzeHits( curScoreSum += n.Score curScoreCount++ + if n.Score > curBestScore { + curBestScore = n.Score + curMarkerTime = n.Time + } continue } @@ -2605,6 +2621,7 @@ func buildLabelContinuitySegmentsFromAnalyzeHits( curLabel, curStartMarker, curEndMarker, + curMarkerTime, curScoreSum, curScoreCount, halfSample, @@ -2620,12 +2637,15 @@ func buildLabelContinuitySegmentsFromAnalyzeHits( curEndMarker = n.End curScoreSum = n.Score curScoreCount = 1 + curMarkerTime = n.Time + curBestScore = n.Score } segment := makeLabelContinuitySegment( curLabel, curStartMarker, curEndMarker, + curMarkerTime, curScoreSum, curScoreCount, halfSample, @@ -2654,6 +2674,7 @@ func makeLabelContinuitySegment( label string, startMarker float64, endMarker float64, + markerTime float64, scoreSum float64, scoreCount int, halfSample float64, @@ -2680,6 +2701,13 @@ func makeLabelContinuitySegment( score = scoreSum / float64(scoreCount) } + if markerTime < 0 { + markerTime = (start + end) / 2 + } + if duration > 0 { + markerTime = math.Max(0, math.Min(markerTime, duration)) + } + return aiSegmentMeta{ Label: label, StartSeconds: start, @@ -2689,6 +2717,8 @@ func makeLabelContinuitySegment( AutoSelected: true, Position: segmentPositionFromAnalyzeLabel(label), Tags: segmentTagsFromAnalyzeLabel(label), + MarkerSeconds: markerTime, + PreviewSeconds: markerTime, } } diff --git a/backend/meta.go b/backend/meta.go index 3b74c44..72f8158 100644 --- a/backend/meta.go +++ b/backend/meta.go @@ -104,6 +104,8 @@ type aiSegmentMeta struct { AutoSelected bool `json:"autoSelected,omitempty"` Position string `json:"position,omitempty"` Tags []string `json:"tags,omitempty"` + MarkerSeconds float64 `json:"markerSeconds,omitempty"` + PreviewSeconds float64 `json:"previewSeconds,omitempty"` } type aiAnalysisMeta struct { diff --git a/backend/rating.go b/backend/rating.go index 6c9c5fb..54eb004 100644 --- a/backend/rating.go +++ b/backend/rating.go @@ -23,11 +23,11 @@ type aiRatingMeta struct { const ( // Normales Zusammenziehen sehr kurzer Pausen. - ratingMaxSilentGapSec = 6.5 + ratingMaxSilentGapSec = 4.5 // Größere Lücken dürfen überbrückt werden, // wenn beide Seiten stark genug / thematisch ähnlich sind. - ratingBridgeStrongGapSec = 35.0 + ratingBridgeStrongGapSec = 12.0 ratingMinSegmentDurationSec = 2.5 ) @@ -743,7 +743,41 @@ func ratingBridgeStrengthFromSet(set ratingSignalSet) float64 { 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 { + if ratingPositionSetsConflict(a, b) { + return false + } + for la := range a { na := normalizeRatingSignalLabel(la) if na == "" { @@ -765,9 +799,11 @@ func ratingLabelsBridgeCompatible(a map[string]bool, b map[string]bool) bool { as := ratingSignalSetFromLabels(a) bs := ratingSignalSetFromLabels(b) - if as.HasPosition && bs.HasPosition { - return true - } + // Wichtig: + // Unterschiedliche Positionen NICHT pauschal verbinden. + // Vorher hat "Doggy" + "Reverse Cowgirl" als kompatibel gegolten, + // nur weil beide irgendeine Position waren. + if as.HasBody && bs.HasBody { return true } @@ -840,11 +876,99 @@ func mergeRatingActivitySegments( }) type activityBlock struct { - start float64 - end float64 - scoreSum float64 - scoreWeight float64 - labels map[string]bool + start float64 + end float64 + scoreSum float64 + scoreWeight float64 + 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 { @@ -857,12 +981,21 @@ func mergeRatingActivitySegments( conf = 0.50 } + positionWeights := map[string]float64{} + addRatingPositionWeight(positionWeights, s.Label, conf*math.Max(1, dur)) + + marker := segmentMarker(s) + markerWeight := segmentMarkerWeight(s) + return activityBlock{ - start: s.StartSeconds, - end: s.EndSeconds, - scoreSum: conf * dur, - scoreWeight: dur, - labels: labels, + start: s.StartSeconds, + end: s.EndSeconds, + scoreSum: conf * dur, + scoreWeight: dur, + labels: labels, + positionWeights: positionWeights, + marker: marker, + markerWeight: markerWeight, } } @@ -872,7 +1005,8 @@ func mergeRatingActivitySegments( return aiSegmentMeta{}, false } - label := buildRatingComboLabel(b.labels) + effectiveLabels := effectiveRatingLabelsForBlock(b.labels, b.positionWeights) + label := buildRatingComboLabel(effectiveLabels) if label == "" { return aiSegmentMeta{}, false } @@ -901,6 +1035,8 @@ func mergeRatingActivitySegments( AutoSelected: true, Position: segmentPositionFromAnalyzeLabel(label), Tags: segmentTagsFromAnalyzeLabel(label), + MarkerSeconds: b.marker, + PreviewSeconds: b.marker, }, true } @@ -956,8 +1092,17 @@ func mergeRatingActivitySegments( if conf <= 0 { conf = 0.50 } + cur.scoreSum += conf * 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) diff --git a/frontend/src/components/ui/RatingOverlay.tsx b/frontend/src/components/ui/RatingOverlay.tsx index cedfdfb..020f7e8 100644 --- a/frontend/src/components/ui/RatingOverlay.tsx +++ b/frontend/src/components/ui/RatingOverlay.tsx @@ -888,22 +888,13 @@ function segmentAnchorTime( obj: Record, durationSeconds: number ): number | undefined { - const start = firstSegmentTimeNumber(obj, [ - 'startSec', - 'startSeconds', - 'start_s', - 'start', - 'from', - 'startMs', - 'startRatio', - 'startPercent', - 'startPct', - ], durationSeconds) - const marker = firstSegmentTimeNumber(obj, [ - 'markerTime', - 'markerSec', + 'previewSeconds', + 'previewSec', + 'previewTime', 'markerSeconds', + 'markerSec', + 'markerTime', 'time', 't', 'timestamp', @@ -918,18 +909,41 @@ function segmentAnchorTime( 'timestampPct', ], durationSeconds) - if (typeof start === 'number') return start 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( obj: Record, durationSeconds: number ): number | undefined { - const anchor = segmentAnchorTime(obj, durationSeconds) - if (typeof anchor === 'number') return anchor + const marker = segmentAnchorTime(obj, durationSeconds) + 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, [ 'endSec', @@ -943,7 +957,14 @@ function segmentPreviewTime( 'endPct', ], 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(