From 6b333ee78a92d77fff110595cbfe66d329dabc68 Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Sat, 6 Jun 2026 20:50:41 +0200 Subject: [PATCH] bugfixes --- backend/analyze.go | 30 +++ backend/meta.go | 2 + backend/myfreecams_online.go | 184 +------------------ backend/rating.go | 177 ++++++++++++++++-- backend/recorder_settings.json | 2 +- backend/server.go | 1 - frontend/src/components/ui/RatingOverlay.tsx | 59 ++++-- 7 files changed, 241 insertions(+), 214 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/myfreecams_online.go b/backend/myfreecams_online.go index e72831b..93bc82a 100644 --- a/backend/myfreecams_online.go +++ b/backend/myfreecams_online.go @@ -535,85 +535,6 @@ func refreshMyFreeCamsSnapshotNow(ctx context.Context, store *ModelStore) (time. return fetchedAt, okCount, nil } -func startMyFreeCamsOnlinePoller(store *ModelStore) { - const interval = mfcOnlinePollInterval - - if store != nil { - setMyFreeCamsOnlineModelStore(store) - } - - lastLoggedErr := "" - - refreshOnce := func(trigger string) { - if !myFreeCamsProviderAPIEnabled() { - if verboseLogs() { - appLogln("ℹ️ [myfreecams] online refresh skipped: provider API disabled") - } - return - } - - mfcOnlineRefreshMu.Lock() - if mfcOnlineRefreshInFlight { - mfcOnlineRefreshMu.Unlock() - return - } - mfcOnlineRefreshInFlight = true - mfcOnlineRefreshMu.Unlock() - - go func() { - defer func() { - mfcOnlineRefreshMu.Lock() - mfcOnlineRefreshInFlight = false - mfcOnlineRefreshMu.Unlock() - }() - - fetchedAt, count, err := refreshMyFreeCamsSnapshotNow(context.Background(), store) - - if err != nil { - msg := err.Error() - - if strings.Contains(msg, "usernameLookup pausiert") { - if verboseLogs() && msg != lastLoggedErr { - appLogln("⏸️ [myfreecams] online refresh skipped:", msg) - lastLoggedErr = msg - } - return - } - - if msg != lastLoggedErr { - appLogln("⚠️ [myfreecams] online refresh failed:", msg) - lastLoggedErr = msg - } - return - } - - if lastLoggedErr != "" { - appLogln("✅ [myfreecams] online refresh recovered") - lastLoggedErr = "" - } - - if verboseLogs() { - appLogln( - "✅ [myfreecams] checked models:", - count, - "snapshot", - fetchedAt.Format(time.RFC3339), - "trigger="+trigger, - ) - } - }() - } - - refreshOnce("startup") - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for range ticker.C { - refreshOnce("ticker") - } -} - type mfcOnlineResponse struct { Enabled bool `json:"enabled"` FetchedAt time.Time `json:"fetchedAt"` @@ -630,107 +551,16 @@ func myfreecamsOnlineHandler(w http.ResponseWriter, r *http.Request) { return } - wantRefresh := false - var users []string - - if r.Method == http.MethodPost { - r.Body = http.MaxBytesReader(w, r.Body, 1<<20) - var req struct { - Q []string `json:"q"` - Refresh bool `json:"refresh"` - } - _ = json.NewDecoder(r.Body).Decode(&req) - wantRefresh = req.Refresh - for _, u := range req.Q { - u = strings.ToLower(strings.TrimSpace(u)) - if u != "" { - users = append(users, u) - } - } - } else { - qRefresh := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("refresh"))) - wantRefresh = qRefresh == "1" || qRefresh == "true" || qRefresh == "yes" - - qUsers := strings.TrimSpace(r.URL.Query().Get("q")) - if qUsers != "" { - for _, u := range strings.Split(qUsers, ",") { - u = strings.ToLower(strings.TrimSpace(u)) - if u != "" { - users = append(users, u) - } - } - } - } - - store := mfcOnlineModelStore - if store == nil { - store = getModelStore() - } - - if wantRefresh && store != nil { - if !myFreeCamsProviderAPIEnabled() { - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-store") - _ = json.NewEncoder(w).Encode(mfcOnlineResponse{ - Enabled: false, - LastError: "MyFreeCams API ist in den Einstellungen deaktiviert", - Rooms: []MyFreeCamsOnlineRoomLite{}, - }) - return - } - - mfcOnlineRefreshMu.Lock() - if !mfcOnlineRefreshInFlight { - mfcOnlineRefreshInFlight = true - go func() { - defer func() { - mfcOnlineRefreshMu.Lock() - mfcOnlineRefreshInFlight = false - mfcOnlineRefreshMu.Unlock() - }() - - _, _, _ = refreshMyFreeCamsSnapshotNow(context.Background(), store) - }() - } - mfcOnlineRefreshMu.Unlock() - } - - mfcOnlineMu.RLock() - fetchedAt := mfcOnline.FetchedAt - lastAttempt := mfcOnline.LastAttempt - lastErr := mfcOnline.LastErr - liteByUser := mfcOnline.LiteByUser - mfcOnlineMu.RUnlock() - - total := 0 - if liteByUser != nil { - total = len(liteByUser) - } - - rooms := make([]MyFreeCamsOnlineRoomLite, 0) - - if len(users) > 0 { - for _, u := range users { - if rm, ok := liteByUser[u]; ok { - rooms = append(rooms, rm) - } - } - } - - if fetchedAt.IsZero() && lastErr == "" { - lastErr = "warming up" - } - w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-store") _ = json.NewEncoder(w).Encode(mfcOnlineResponse{ - Enabled: true, - FetchedAt: fetchedAt, - LastAttempt: lastAttempt, - Count: len(rooms), - Total: total, - LastError: lastErr, - Rooms: rooms, + Enabled: false, + FetchedAt: time.Time{}, + LastAttempt: time.Time{}, + Count: 0, + Total: 0, + LastError: "MyFreeCams Online-Prüfung deaktiviert; usernameLookup nur über biocontext bei Bedarf.", + Rooms: []MyFreeCamsOnlineRoomLite{}, }) } 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/backend/recorder_settings.json b/backend/recorder_settings.json index 5e26e59..ff8a4ee 100644 --- a/backend/recorder_settings.json +++ b/backend/recorder_settings.json @@ -13,7 +13,7 @@ "useMyFreeCamsApi": true, "autoDeleteSmallDownloads": true, "autoDeleteSmallDownloadsBelowMB": 300, - "autoDeleteSmallDownloadsKeepFavorites": true, + "autoDeleteSmallDownloadsKeepFavorites": false, "lowDiskPauseBelowGB": 5, "blurPreviews": false, "teaserPlayback": "all", diff --git a/backend/server.go b/backend/server.go index b8457fd..c649442 100644 --- a/backend/server.go +++ b/backend/server.go @@ -715,7 +715,6 @@ func main() { // Hintergrund-Worker go startDiskSpaceGuard() go startChaturbateOnlinePoller(store) - go startMyFreeCamsOnlinePoller(store) go startChaturbateAutoStartWorker(store) go startMyFreeCamsAutoStartWorker(store) 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(