From 43449405e82525b03e8e2a994cfcfab07664678e Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:39:07 +0200 Subject: [PATCH] bugfixes --- Icons/boobjob.png | Bin 0 -> 4719 bytes Icons/penis.png | Bin 5152 -> 2623 bytes backend/ai_server.py | 2 +- backend/analyze.go | 2 +- backend/data/pending-autostart/admin.json | 3 - backend/embed.go | 6 +- backend/ml/detection_labels.json | 56 ---- backend/rating.go | 2 +- backend/server.go | 15 +- frontend/src/App.tsx | 18 +- frontend/src/aiLabels.ts | 2 - .../ui/FinishedDownloadsCardsView.tsx | 18 -- frontend/src/components/ui/Icons.tsx | 40 +-- frontend/src/components/ui/Player.tsx | 36 ++- frontend/src/components/ui/RatingOverlay.tsx | 1 - frontend/src/components/ui/SwipeCard.tsx | 270 ++++-------------- frontend/src/components/ui/TrainingTab.tsx | 24 +- .../src/components/ui/VideoSplitModal.tsx | 241 +++++++++++++++- 18 files changed, 376 insertions(+), 360 deletions(-) create mode 100644 Icons/boobjob.png delete mode 100644 backend/data/pending-autostart/admin.json delete mode 100644 backend/ml/detection_labels.json diff --git a/Icons/boobjob.png b/Icons/boobjob.png new file mode 100644 index 0000000000000000000000000000000000000000..8346c8b25e05cf8331a4466be74adab52886e075 GIT binary patch literal 4719 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 Ms_LoKDA`5-7knJMD*ylh literal 0 HcmV?d00001 diff --git a/Icons/penis.png b/Icons/penis.png index 3c4b4f49fe978a938a034323a46aa8b0482defb5..9b01da1ea980c77df45cab036f0414eb4ceea13e 100644 GIT binary patch literal 2623 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 diff --git a/backend/ai_server.py b/backend/ai_server.py index 1e27733..7e042e7 100644 --- a/backend/ai_server.py +++ b/backend/ai_server.py @@ -1,4 +1,4 @@ -# backend/ai_server.py +# backend\ai_server.py import json import os diff --git a/backend/analyze.go b/backend/analyze.go index b72c76c..6dc7d60 100644 --- a/backend/analyze.go +++ b/backend/analyze.go @@ -1509,7 +1509,7 @@ func buildHighlightHitsFromPrediction(pred TrainingPrediction, t float64) []anal continue } - // Schwache Positions-Kontexte wie standing/sitting nicht alleine als Segment anzeigen. + // Schwache Positions-Kontexte wie standing nicht alleine als Segment anzeigen. if sig.Group == "position" && segmentSeverityWeight(label) < 0.70 { continue } diff --git a/backend/data/pending-autostart/admin.json b/backend/data/pending-autostart/admin.json deleted file mode 100644 index fc69ce2..0000000 --- a/backend/data/pending-autostart/admin.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "items": [] -} \ No newline at end of file diff --git a/backend/embed.go b/backend/embed.go index 2885023..68c3a01 100644 --- a/backend/embed.go +++ b/backend/embed.go @@ -8,7 +8,7 @@ import ( "path/filepath" ) -//go:embed ml/*.py ml/*.json ai_server.py .env recorder-cert.pem recorder-key.pem +//go:embed ml/*.py ai_server.py .env recorder-cert.pem recorder-key.pem generated/training/detection_labels.json var embeddedMLFiles embed.FS func trainingEmbeddedMLDir() (string, error) { @@ -33,6 +33,10 @@ func trainingEmbeddedMLDir() (string, error) { for _, name := range append(files, optionalFiles...) { srcPath := filepath.ToSlash(filepath.Join("ml", name)) + if name == "detection_labels.json" { + srcPath = "generated/training/detection_labels.json" + } + b, err := embeddedMLFiles.ReadFile(srcPath) if err != nil { // Pflichtdateien müssen vorhanden sein. diff --git a/backend/ml/detection_labels.json b/backend/ml/detection_labels.json deleted file mode 100644 index 286ffe8..0000000 --- a/backend/ml/detection_labels.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "people": [ - "person_female", - "person_male" - ], - "sexPositions": [ - "unknown", - "missionary", - "doggy", - "cowgirl", - "reverse_cowgirl", - "cunnilingus", - "prone_bone", - "standing", - "standing_doggy", - "spooning", - "facesitting", - "handjob", - "blowjob", - "boobjob", - "toy_play", - "fingering", - "69" - ], - "bodyParts": [ - "anus", - "ass", - "breasts", - "penis", - "tongue", - "pussy" - ], - "objects": [ - "blindfold", - "buttplug", - "collar", - "dildo", - "handcuffs", - "shower", - "strapon", - "towel", - "vibrator" - ], - "clothing": [ - "bikini", - "bra", - "dress", - "heels", - "hotpants", - "lingerie", - "panties", - "skirt", - "stockings", - "croptop" - ] -} \ No newline at end of file diff --git a/backend/rating.go b/backend/rating.go index cf90c7c..c589ffb 100644 --- a/backend/rating.go +++ b/backend/rating.go @@ -451,7 +451,7 @@ func contextualSegmentSeverityWeight(label string) float64 { score += 0.015 } - // Standing/Sitting ohne Kontext bleibt schwach. + // Standing ohne Kontext bleibt schwach. if set.Position < 0.60 && !set.HasBody && !set.HasObject && !set.HasClothing { score = math.Min(score, 0.42) } diff --git a/backend/server.go b/backend/server.go index 1a3c6d4..f803a14 100644 --- a/backend/server.go +++ b/backend/server.go @@ -464,9 +464,22 @@ func startAIServer(ctx context.Context) (*aiServerProcess, error) { trainingRoot, appBaseDir := findTrainingRootDir(scriptDir) + mlDir, mlErr := trainingEmbeddedMLDir() + if mlErr != nil { + appLogln("⚠️ embedded ML-Dateien konnten nicht extrahiert werden:", mlErr) + } else { + appLogln("✅ embedded ML-Dateien extrahiert:", mlDir) + } + detectionLabelsPath := strings.TrimSpace(os.Getenv("DETECTION_LABELS_PATH")) if detectionLabelsPath == "" { - detectionLabelsPath = filepath.Join(trainingRoot, "detection_labels.json") + if mlErr == nil && strings.TrimSpace(mlDir) != "" { + // ai_server.py soll die entpackte embedded detection_labels.json verwenden. + detectionLabelsPath = filepath.Join(mlDir, "detection_labels.json") + } else { + // Fallback für Entwicklung / falls embedded extraction fehlschlägt. + detectionLabelsPath = filepath.Join(trainingRoot, "detection_labels.json") + } } else { detectionLabelsPath = filepath.Clean(detectionLabelsPath) } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index dccd439..8ee2c07 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4301,7 +4301,7 @@ export default function App() { setSplitModalOpen(false) setSplitJob(null) }} - onApply={({ job, splits, segments, deleteOriginal }) => { + onApply={async ({ job, splits, segments, deleteOriginal, onProgress }) => { const file = baseName(job.output || '') if (!file) { throw new Error('Kein Dateiname vorhanden.') @@ -4338,13 +4338,18 @@ export default function App() { return segments[pos - 1] ?? null } - const publish = ( - progress: Omit - ) => { - pushSplitProgress(file, progress) + type ModalSplitProgress = Parameters>[0] + + const publish = (progress: ModalSplitProgress) => { + pushSplitProgress( + file, + progress as Omit + ) + + onProgress?.(progress) } - void (async () => { + return await (async () => { let buffer = '' let finalData: any = null let total = totalFallback @@ -4562,6 +4567,7 @@ export default function App() { }) notify.error('Split fehlgeschlagen', `${file} — ${message}`) + throw err } })() }} diff --git a/frontend/src/aiLabels.ts b/frontend/src/aiLabels.ts index 8d0b222..90e8830 100644 --- a/frontend/src/aiLabels.ts +++ b/frontend/src/aiLabels.ts @@ -20,7 +20,6 @@ export const AI_POSITION_LABELS = [ 'standing', 'standing_doggy', 'spooning', - 'sitting', 'facesitting', 'handjob', 'blowjob', @@ -121,7 +120,6 @@ export const AI_LABEL_FALLBACKS: Record = { standing: 'Stehend', standing_doggy: 'Standing Doggy', spooning: 'Spooning', - sitting: 'Sitzend', facesitting: 'Facesitting', handjob: 'Handjob', blowjob: 'Blowjob', diff --git a/frontend/src/components/ui/FinishedDownloadsCardsView.tsx b/frontend/src/components/ui/FinishedDownloadsCardsView.tsx index a9252dd..7b580f8 100644 --- a/frontend/src/components/ui/FinishedDownloadsCardsView.tsx +++ b/frontend/src/components/ui/FinishedDownloadsCardsView.tsx @@ -1324,10 +1324,6 @@ function FinishedDownloadsCardsView({ doubleTapMaxMovePx={48} pressDelayMs={220} enablePressHold={canSpeedHold} - verticalThresholdPx={92} - verticalThresholdRatio={0.20} - upAction={{ label: 'Öffnen' }} - downAction={{ label: 'Weiter' }} onDoubleTap={async () => { if (isHot) return await onToggleHot?.(j) @@ -1361,16 +1357,6 @@ function FinishedDownloadsCardsView({ setMobileTopTeaserEnabled(false) return await keepVideo(j) }} - onSwipeUp={() => { - setMobileTopTeaserEnabled(false) - openPlayer(j) - return false - }} - onSwipeDown={() => { - setMobileTopTeaserEnabled(false) - skipTopMobileCard() - return true - }} onPressStart={(info) => { if (info.pointerType === 'mouse' && info.button !== 0) return if (!canSpeedHold) return @@ -1397,10 +1383,6 @@ function FinishedDownloadsCardsView({ {mobileTopRow ? (
-
- ← Löschen · → Behalten · ↑ Öffnen · ↓ Weiter · Doppeltipp HOT · Halten schneller -
- {mobileOrderedRows.length > 1 ? ( ) : null} - +
{!isDraft ? ( diff --git a/frontend/src/components/ui/VideoSplitModal.tsx b/frontend/src/components/ui/VideoSplitModal.tsx index 7d42ff3..c2870d7 100644 --- a/frontend/src/components/ui/VideoSplitModal.tsx +++ b/frontend/src/components/ui/VideoSplitModal.tsx @@ -860,6 +860,143 @@ function normalizeAiSegmentsFromHits( }) } +type SegmentVisualKind = 'position' | 'toy' | 'clothing' | 'body' | 'person' | 'default' + +function segmentVisualKindFromText(value: unknown): SegmentVisualKind { + const clean = String(value ?? '').trim().toLowerCase() + if (!clean) return 'default' + + if ( + clean.includes('person_male') || + clean.includes('male_person') || + clean.includes('person_female') || + clean.includes('female_person') || + clean === 'person' || + clean.startsWith('person:') + ) { + return 'person' + } + + if ( + clean.startsWith('position:') || + clean.includes('missionary') || + clean.includes('doggy') || + clean.includes('doggystyle') || + clean.includes('cowgirl') || + clean.includes('reverse cowgirl') || + clean.includes('reverse_cowgirl') || + clean.includes('cunnilingus') || + clean.includes('prone bone') || + clean.includes('prone_bone') || + clean.includes('standing') || + clean.includes('spooning') || + clean.includes('facesitting') || + clean.includes('handjob') || + clean.includes('blowjob') || + clean.includes('boobjob') || + clean.includes('boob job') || + clean.includes('boob_job') || + clean.includes('titjob') || + clean.includes('tit job') || + clean.includes('tit_job') || + clean.includes('fingering') || + clean.includes('toy play') || + clean.includes('toy_play') || + clean === '69' + ) { + return 'position' + } + + if ( + clean.includes('dildo') || + clean.includes('vibrator') || + clean.includes('buttplug') || + clean.includes('strapon') || + clean.includes('toy') + ) { + return 'toy' + } + + if ( + clean.includes('lingerie') || + clean.includes('bikini') || + clean.includes('bra') || + clean.includes('bh') || + clean.includes('panties') || + clean.includes('slip') || + clean.includes('stocking') || + clean.includes('strümpfe') || + clean.includes('skirt') || + clean.includes('rock') || + clean.includes('dress') || + clean.includes('kleid') || + clean.includes('heels') + ) { + return 'clothing' + } + + if ( + clean.includes('vagina') || + clean.includes('vulva') || + clean.includes('pussy') || + clean.includes('penis') || + clean.includes('anus') || + clean.includes('ass') || + clean.includes('hintern') || + clean.includes('breasts') || + clean.includes('brüste') || + clean.includes('buttocks') + ) { + return 'body' + } + + return 'default' +} + +function segmentVisualKind(seg: Segment): SegmentVisualKind { + const rawLabel = String(seg.label || '').trim() + const prettyLabel = getSegmentLabelText(rawLabel) + + const values = [rawLabel, prettyLabel] + + for (const value of values) { + const kind = segmentVisualKindFromText(value) + if (kind !== 'default') return kind + } + + return 'default' +} + +function reindexSegments(input: Segment[]): Segment[] { + return input.map((seg, idx) => ({ + ...seg, + index: idx + 1, + })) +} + +function filterSegmentsLikeRatingOverlay(input: Segment[]): Segment[] { + const withoutPeople = input.filter((seg) => segmentVisualKind(seg) !== 'person') + + const positionSegments = withoutPeople.filter((seg) => ( + segmentVisualKind(seg) === 'position' + )) + + if (positionSegments.length > 0) { + return reindexSegments(positionSegments) + } + + const bodyOrClothingSegments = withoutPeople.filter((seg) => { + const kind = segmentVisualKind(seg) + return kind === 'clothing' || kind === 'body' + }) + + if (bodyOrClothingSegments.length > 0) { + return reindexSegments(bodyOrClothingSegments) + } + + return reindexSegments(withoutPeople) +} + function shouldAutoSelectAiHit(hit: { label: string }): boolean { @@ -1085,7 +1222,7 @@ export default function VideoSplitModal({ mapAiTimes ) - const metaAiSegments = + const rawMetaAiSegments = metaAiSegmentsFromMeta.length > 0 ? metaAiSegmentsFromMeta : normalizeAiSegmentsFromHits( @@ -1094,6 +1231,8 @@ export default function VideoSplitModal({ mapAiTimes ) + const metaAiSegments = filterSegmentsLikeRatingOverlay(rawMetaAiSegments) + if (!cancelled) { setMediaDuration(metaDuration > 0 ? metaDuration : 0) setAiHits(metaAiHits) @@ -1610,6 +1749,31 @@ export default function VideoSplitModal({ ) }, [job?.output, anyJob?.startedAt, anyJob?.endedAt, anyJob?.createdAt, anyJob?.fileCreatedAt, anyJob?.ctime]) + const showSplitProgress = Boolean( + splitProgress && + ( + busy || + splitProgress.phase === 'done' || + splitProgress.phase === 'error' + ) + ) + + const splitOverallPercent = Math.round( + clamp(splitProgress?.overallProgress ?? 0, 0, 1) * 100 + ) + + const splitSegmentPercent = Math.round( + clamp(splitProgress?.segmentProgress ?? 0, 0, 1) * 100 + ) + + const splitCurrentLabel = splitProgress?.segment?.label + ? getSegmentLabelText(splitProgress.segment.label) + : '' + + const splitCurrentTime = splitProgress?.segment + ? `${formatTime(splitProgress.segment.start)} → ${formatTime(splitProgress.segment.end)}` + : '' + return ( - {segmentsToApply.length === 1 - ? '1 Datei erzeugen' - : `${segmentsToApply.length} Dateien erzeugen`} + {busy + ? 'Wird erzeugt…' + : segmentsToApply.length === 1 + ? '1 Datei erzeugen' + : `${segmentsToApply.length} Dateien erzeugen`} } @@ -2105,6 +2271,73 @@ export default function VideoSplitModal({ + {showSplitProgress ? ( +
+
+
+
+ {splitProgress?.phase === 'done' + ? 'Split abgeschlossen' + : splitProgress?.phase === 'error' + ? 'Split fehlgeschlagen' + : 'Split läuft…'} +
+ +
+ {splitProgress?.message || 'Bitte warten…'} +
+ + {splitCurrentLabel ? ( +
+ {splitCurrentLabel} + {splitCurrentTime ? ` · ${splitCurrentTime}` : ''} +
+ ) : null} +
+ +
+
+ {splitOverallPercent}% +
+ +
+ {splitProgress?.completed ?? 0}/{splitProgress?.total ?? segmentsToApply.length} +
+
+
+ +
+
+
+ + {splitProgress?.phase === 'splitting' ? ( +
+ Aktuelles Segment + {splitSegmentPercent}% +
+ ) : null} +
+ ) : null} +
{displaySegmentGroups.map((group, groupIdx) => { const firstSeg = group.segments[0]