diff --git a/backend/rating.go b/backend/rating.go index c589ffb..5144447 100644 --- a/backend/rating.go +++ b/backend/rating.go @@ -203,15 +203,15 @@ func clothingSeverityWeight(label string) float64 { switch label { case "lingerie": - return 0.64 + return 0.50 case "panties", "bra": - return 0.58 + return 0.44 case "bikini": - return 0.42 + return 0.30 case "stockings", "heels": - return 0.40 + return 0.28 case "skirt", "dress", "hotpants", "croptop": - return 0.34 + return 0.22 default: return 0.00 } @@ -431,20 +431,21 @@ func contextualSegmentSeverityWeight(label string) float64 { var score float64 if set.HasPosition { + // Priorität: Position > Kleidung > Objekte (Kombination erhöht den Score). score = 0.66*set.Position + - 0.17*set.Body + - 0.10*set.Object + - 0.05*set.Clothing + + 0.15*set.Body + + 0.12*set.Clothing + + 0.05*set.Object + 0.02*set.Person if set.HasBody { score += 0.055 } - if set.HasObject { - score += 0.040 - } if set.HasClothing { + score += 0.030 + } + if set.HasObject { score += 0.020 } if set.HasPerson { @@ -461,29 +462,27 @@ func contextualSegmentSeverityWeight(label string) float64 { score = 0.54*set.Body + - 0.30*set.Object + - 0.14*set.Clothing + + 0.28*set.Clothing + + 0.16*set.Object + 0.02*set.Person - if set.HasBody && set.HasObject { - score += 0.09 - } if set.HasBody && set.HasClothing { + score += 0.05 + } + if set.HasBody && set.HasObject { score += 0.04 } - if set.HasObject && set.HasClothing { + if set.HasClothing && set.HasObject { score += 0.03 } if set.HasPerson && (set.HasBody || set.HasObject || set.HasClothing) { score += 0.015 } - // Kleidung alleine soll interessant sein können, aber nicht stark. if set.HasClothing && !set.HasBody && !set.HasObject { score = math.Min(score, 0.38) } - // Ohne Position nicht zu aggressiv. score = math.Min(score, 0.72) return ratingClamp01(score) @@ -524,18 +523,18 @@ func contextualSegmentSeverityWeightFromSet(set ratingSignalSet) float64 { if set.HasPosition { score = 0.66*set.Position + - 0.17*set.Body + - 0.10*set.Object + - 0.05*set.Clothing + + 0.15*set.Body + + 0.12*set.Clothing + + 0.05*set.Object + 0.02*set.Person if set.HasBody { score += 0.055 } - if set.HasObject { - score += 0.040 - } if set.HasClothing { + score += 0.030 + } + if set.HasObject { score += 0.020 } if set.HasPerson { @@ -551,17 +550,17 @@ func contextualSegmentSeverityWeightFromSet(set ratingSignalSet) float64 { score = 0.54*set.Body + - 0.30*set.Object + - 0.14*set.Clothing + + 0.28*set.Clothing + + 0.16*set.Object + 0.02*set.Person - if set.HasBody && set.HasObject { - score += 0.09 - } if set.HasBody && set.HasClothing { + score += 0.05 + } + if set.HasBody && set.HasObject { score += 0.04 } - if set.HasObject && set.HasClothing { + if set.HasClothing && set.HasObject { score += 0.03 } if set.HasPerson && (set.HasBody || set.HasObject || set.HasClothing) { @@ -1349,7 +1348,6 @@ func computeHighlightRatingWithUsername(segments []aiSegmentMeta, durationSec fl var totalWeighted float64 var positionEffectiveWeighted float64 - var contextEffectiveWeighted float64 var peakQuality float64 var longest float64 @@ -1391,11 +1389,12 @@ func computeHighlightRatingWithUsername(segments []aiSegmentMeta, durationSec fl totalWeighted += weightedDur set := ratingSignalSetFromLabel(s.Label) - if set.HasPosition { - positionEffectiveWeighted += effectiveWeightedDur - } else { - contextEffectiveWeighted += effectiveWeightedDur + if !set.HasPosition { + // Nur Positions-Segmente fließen ins Rating ein. + // Kleidung und Objekte wirken über Combo-Segmente (position+clothing etc.). + continue } + positionEffectiveWeighted += effectiveWeightedDur if quality > peakQuality { peakQuality = quality @@ -1420,29 +1419,22 @@ func computeHighlightRatingWithUsername(segments []aiSegmentMeta, durationSec fl avgConfidence := confSum / float64(n) positionDensity := positionEffectiveWeighted / videoMinutes - contextDensity := contextEffectiveWeighted / videoMinutes peakNorm := ratingSmoothStep(peakQuality) positionDensityNorm := ratingSoftCap(positionDensity, 5.5) - contextDensityNorm := ratingSoftCap(contextDensity, 8.5) coverageNorm := ratingSoftCap(weightedCoverageRatio, 0.20) - frequencyNorm := ratingSoftCap(segmentsPerMinute, 1.25) longestNorm := ratingSoftCap(longest, 24.0) confNorm := ratingSmoothStep((avgConfidence - 0.30) / 0.65) + // Positionen dominieren. Kleidung/Objekte fließen durch Combo-Segment-Qualität (peakNorm) ein. + // Keine Penalties, keine Caps – die Formel bewertet natürlich nach Positions-Intensität. + // Summe = 1.00. raw := - 0.32*peakNorm + - 0.25*positionDensityNorm + - 0.17*contextDensityNorm + - 0.12*coverageNorm + - 0.06*longestNorm + - 0.04*frequencyNorm + - 0.04*confNorm - - // Ohne Position darf es trotzdem gut werden, aber nicht automatisch 5 Sterne. - if positionEffectiveWeighted <= 0 { - raw = math.Min(raw, 0.76) - } + 0.42*positionDensityNorm + + 0.26*peakNorm + + 0.18*coverageNorm + + 0.08*longestNorm + + 0.06*confNorm // Sehr wenig Material nicht überbewerten. if totalFlagged < 5.0 && n <= 1 { @@ -1477,15 +1469,14 @@ func computeHighlightRatingWithUsername(segments []aiSegmentMeta, durationSec fl r.AvgConfidence = ratingRound(avgConfidence, 3) appLogf( - "✅ %s rating result score=%.1f stars=%d bonus=%.1f segments=%d flagged=%.2f weighted=%.2f coverage=%.4f weightedCoverage=%.4f longest=%.2f avgConf=%.3f", + "✅ %s rating score=%.1f stars=%d bonus=%.1f segs=%d flagged=%.1f posDensity=%.2f coverage=%.4f longest=%.1f avgConf=%.3f", ratingLogSubject(username), r.Score, r.Stars, ratingRound(excellenceBonus*100, 1), r.Segments, r.FlaggedSeconds, - r.WeightedFlaggedSeconds, - r.CoverageRatio, + positionDensity, r.WeightedCoverageRatio, r.LongestSegmentSeconds, r.AvgConfidence, diff --git a/backend/server.go b/backend/server.go index 79100da..c5cce95 100644 --- a/backend/server.go +++ b/backend/server.go @@ -870,7 +870,7 @@ func main() { appLogln("🔐 TLS Cert:", tlsCertFile()) appLogln("🔐 TLS Key: ", tlsKeyFile()) } else { - appLogln("🌐 HTTP-API aktiv: http://localhost:9999") + appLogln("🌐 HTTP-API aktiv: https://l14pbbk95100006.tegdssd.de:9999") } handler := withCORS(mux) diff --git a/backend/tray.go b/backend/tray.go index 67c4b79..207e556 100644 --- a/backend/tray.go +++ b/backend/tray.go @@ -108,7 +108,7 @@ func runTray(onQuit func(), statsFn func() TrayStats) { updateStatus() case <-mOpenFrontend.ClickedCh: - _ = openBrowser("https://localhost:9999") + _ = openBrowser("https://l14pbbk95100006.tegdssd.de:9999") case <-mOpenRecordDir.ClickedCh: _ = openFolder(resolveConfiguredDir(getSettings().RecordDir)) diff --git a/frontend/src/components/ui/VideoSplitModal.tsx b/frontend/src/components/ui/VideoSplitModal.tsx index c2870d7..4733e31 100644 --- a/frontend/src/components/ui/VideoSplitModal.tsx +++ b/frontend/src/components/ui/VideoSplitModal.tsx @@ -955,8 +955,19 @@ function segmentVisualKindFromText(value: unknown): SegmentVisualKind { function segmentVisualKind(seg: Segment): SegmentVisualKind { const rawLabel = String(seg.label || '').trim() - const prettyLabel = getSegmentLabelText(rawLabel) + // Combo-Labels werden in Backend-Prioritätsreihenfolge gebaut (position > body > object > clothing). + // Jeden Teil einzeln prüfen und das erste Ergebnis zurückgeben — so verhält sich auch RatingOverlay. + if (rawLabel.toLowerCase().startsWith('combo:')) { + const parts = rawLabel.slice(6).split('+') + for (const part of parts) { + const kind = segmentVisualKindFromText(part.trim()) + if (kind !== 'default') return kind + } + return 'default' + } + + const prettyLabel = getSegmentLabelText(rawLabel) const values = [rawLabel, prettyLabel] for (const value of values) {