diff --git a/backend/assets_generate.go b/backend/assets_generate.go index fd2ef8a..9e594e5 100644 --- a/backend/assets_generate.go +++ b/backend/assets_generate.go @@ -195,7 +195,16 @@ func ensureAnalyzeAllGoalsForVideoCtxForce( return false, err } - return hasAIAnalysisForAllOutputGoals(videoPath, requiredAnalyzeGoals()), nil + analysisReady := hasAIAnalysisForAllOutputGoals(videoPath, requiredAnalyzeGoals()) + if !analysisReady { + return false, nil + } + + // Direkt nach erfolgreicher Analyse löschen. + // Wichtig: hier rating übergeben, nicht nil. + autoDeleteLowRatedDownloadAfterAnalysis(actx, videoPath, rating) + + return true, nil } func assetsTruthForVideo(videoPath string) finishedPhaseTruth { @@ -897,10 +906,18 @@ func ensureAnalyzeForVideoCtx( return false, perr } - if _, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL); err != nil { + analyzed, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL) + if err != nil { return false, err } + if analyzed { + if _, statErr := os.Stat(videoPath); os.IsNotExist(statErr) { + // Analyse war erfolgreich, Datei wurde danach absichtlich durch AutoDelete gelöscht. + return true, nil + } + } + return hasAIAnalysisForOutputGoal(videoPath, goal), nil } @@ -926,10 +943,18 @@ func ensureDeferredAssetsAndAI(ctx context.Context, videoPath, sourceURL string, } // Analyse genau einmal für alle Standard-Ziele. - if _, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL); err != nil { + analyzed, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL) + if err != nil { return appErrorf("deferred analyze failed for %s: %w", videoPath, err) } + if analyzed { + if _, statErr := os.Stat(videoPath); os.IsNotExist(statErr) { + // Analyse war erfolgreich, Datei wurde danach absichtlich durch AutoDelete gelöscht. + return nil + } + } + goal = "highlights" if !hasAIAnalysisForOutputGoal(videoPath, goal) { diff --git a/backend/ml/detection_labels.json b/backend/ml/detection_labels.json index 6889c34..286ffe8 100644 --- a/backend/ml/detection_labels.json +++ b/backend/ml/detection_labels.json @@ -17,6 +17,7 @@ "facesitting", "handjob", "blowjob", + "boobjob", "toy_play", "fingering", "69" diff --git a/backend/rating.go b/backend/rating.go index 779839b..cf90c7c 100644 --- a/backend/rating.go +++ b/backend/rating.go @@ -125,6 +125,7 @@ func isKnownPositionLabel(label string) bool { "facesitting", "handjob", "blowjob", + "boobjob", "toy_play", "fingering", "69": @@ -144,7 +145,7 @@ func positionSeverityWeight(label string) float64 { return 0.98 case "missionary", "prone_bone": return 0.95 - case "blowjob", "cunnilingus", "69", "facesitting": + case "blowjob", "cunnilingus", "69", "facesitting", "boobjob": return 0.94 case "toy_play": return 0.88 diff --git a/backend/tasks_assets.go b/backend/tasks_assets.go index 8956379..5525592 100644 --- a/backend/tasks_assets.go +++ b/backend/tasks_assets.go @@ -942,9 +942,6 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) { publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen") } else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady { publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse") - - // Nach erfolgreicher Hintergrund-Analyse optional nach Rating löschen. - autoDeleteLowRatedDownloadAfterAnalysis(fileCtx, it.path, nil) } else { publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen") } diff --git a/backend/tasks_cleanup.go b/backend/tasks_cleanup.go index 6e8949f..9d0d1d1 100644 --- a/backend/tasks_cleanup.go +++ b/backend/tasks_cleanup.go @@ -739,6 +739,11 @@ func autoDeleteLowRatedDownloadAfterAnalysis(ctx context.Context, videoPath stri default: } + videoPath = strings.TrimSpace(videoPath) + if videoPath == "" { + return + } + s := getSettings() if !s.AutoDeleteLowRatedDownloads { return @@ -747,6 +752,7 @@ func autoDeleteLowRatedDownloadAfterAnalysis(ctx context.Context, videoPath stri maxStars := clampAutoDeleteRatingStars(s.AutoDeleteLowRatedDownloadsMaxStars) stars := 0 + if rating != nil { stars = rating.Stars if stars <= 0 && rating.Score > 0 { @@ -754,21 +760,34 @@ func autoDeleteLowRatedDownloadAfterAnalysis(ctx context.Context, videoPath stri } } - // Falls kein Rating direkt übergeben wurde, aus der gespeicherten meta.json lesen. - // Das brauchen wir für Asset-/Regenerate-Jobs, weil dort die Analyse erst geprüft - // und danach gelöscht werden soll. - if stars < 1 || stars > 5 { - metaStars, ok, err := ratingStarsForVideoPath(videoPath) - if err != nil { - appLogln("⚠️ [rating-delete] rating aus meta konnte nicht gelesen werden:", filepath.Base(videoPath), err) - return - } - if ok { + // Wichtig für Background-/Regenerate-Analyse: + // Dort wird rating teilweise als nil übergeben, obwohl es kurz vorher + // in meta.json gespeichert wurde. Dann lesen wir die Sterne aus der Meta. + if stars <= 0 { + if metaStars, ok, err := ratingStarsForVideoPath(videoPath); err != nil { + appLogln("⚠️ [rating-delete] rating lookup failed:", filepath.Base(videoPath), err) + } else if ok { stars = metaStars } } - if stars < 1 || stars > 5 || stars > maxStars { + if stars < 1 || stars > 5 { + appLogf( + "ℹ️ [rating-delete] übersprungen, kein gültiges Rating: %s stars=%d threshold<=%d", + filepath.Base(videoPath), + stars, + maxStars, + ) + return + } + + if stars > maxStars { + appLogf( + "ℹ️ [rating-delete] behalten: %s stars=%d threshold<=%d", + filepath.Base(videoPath), + stars, + maxStars, + ) return } diff --git a/frontend/src/aiLabels.ts b/frontend/src/aiLabels.ts index e1a0842..8d0b222 100644 --- a/frontend/src/aiLabels.ts +++ b/frontend/src/aiLabels.ts @@ -1,3 +1,5 @@ +// frontend\src\aiLabels.ts + export type AiLabelGroup = | 'people' | 'position' @@ -22,6 +24,7 @@ export const AI_POSITION_LABELS = [ 'facesitting', 'handjob', 'blowjob', + 'boobjob', 'toy_play', 'fingering', '69', @@ -122,6 +125,7 @@ export const AI_LABEL_FALLBACKS: Record = { facesitting: 'Facesitting', handjob: 'Handjob', blowjob: 'Blowjob', + boobjob: 'Boobjob', toy_play: 'Toy Play', fingering: 'Fingering', '69': '69', diff --git a/frontend/src/components/ui/Icons.tsx b/frontend/src/components/ui/Icons.tsx index 5185519..098edaf 100644 --- a/frontend/src/components/ui/Icons.tsx +++ b/frontend/src/components/ui/Icons.tsx @@ -63,6 +63,42 @@ export function FemaleBreastIcon(props: IconProps) { ) } +export function BoobjobIcon(props: IconProps) { + const { title, className, ...rest } = props + + return ( + + {title ? {title} : null} + + + + + + ) +} + export function ButtocksIcon(props: IconProps) { return (