added boobjob position
This commit is contained in:
parent
d55c6b5854
commit
98e7bc70f2
@ -195,7 +195,16 @@ func ensureAnalyzeAllGoalsForVideoCtxForce(
|
|||||||
return false, err
|
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 {
|
func assetsTruthForVideo(videoPath string) finishedPhaseTruth {
|
||||||
@ -897,10 +906,18 @@ func ensureAnalyzeForVideoCtx(
|
|||||||
return false, perr
|
return false, perr
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL); err != nil {
|
analyzed, err := ensureAnalyzeAllGoalsForVideoCtx(ctx, videoPath, sourceURL)
|
||||||
|
if err != nil {
|
||||||
return false, err
|
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
|
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.
|
// 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)
|
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"
|
goal = "highlights"
|
||||||
|
|
||||||
if !hasAIAnalysisForOutputGoal(videoPath, goal) {
|
if !hasAIAnalysisForOutputGoal(videoPath, goal) {
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"facesitting",
|
"facesitting",
|
||||||
"handjob",
|
"handjob",
|
||||||
"blowjob",
|
"blowjob",
|
||||||
|
"boobjob",
|
||||||
"toy_play",
|
"toy_play",
|
||||||
"fingering",
|
"fingering",
|
||||||
"69"
|
"69"
|
||||||
|
|||||||
@ -125,6 +125,7 @@ func isKnownPositionLabel(label string) bool {
|
|||||||
"facesitting",
|
"facesitting",
|
||||||
"handjob",
|
"handjob",
|
||||||
"blowjob",
|
"blowjob",
|
||||||
|
"boobjob",
|
||||||
"toy_play",
|
"toy_play",
|
||||||
"fingering",
|
"fingering",
|
||||||
"69":
|
"69":
|
||||||
@ -144,7 +145,7 @@ func positionSeverityWeight(label string) float64 {
|
|||||||
return 0.98
|
return 0.98
|
||||||
case "missionary", "prone_bone":
|
case "missionary", "prone_bone":
|
||||||
return 0.95
|
return 0.95
|
||||||
case "blowjob", "cunnilingus", "69", "facesitting":
|
case "blowjob", "cunnilingus", "69", "facesitting", "boobjob":
|
||||||
return 0.94
|
return 0.94
|
||||||
case "toy_play":
|
case "toy_play":
|
||||||
return 0.88
|
return 0.88
|
||||||
|
|||||||
@ -942,9 +942,6 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
|||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
||||||
} else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady {
|
} else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady {
|
||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse")
|
publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse")
|
||||||
|
|
||||||
// Nach erfolgreicher Hintergrund-Analyse optional nach Rating löschen.
|
|
||||||
autoDeleteLowRatedDownloadAfterAnalysis(fileCtx, it.path, nil)
|
|
||||||
} else {
|
} else {
|
||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -739,6 +739,11 @@ func autoDeleteLowRatedDownloadAfterAnalysis(ctx context.Context, videoPath stri
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoPath = strings.TrimSpace(videoPath)
|
||||||
|
if videoPath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
s := getSettings()
|
s := getSettings()
|
||||||
if !s.AutoDeleteLowRatedDownloads {
|
if !s.AutoDeleteLowRatedDownloads {
|
||||||
return
|
return
|
||||||
@ -747,6 +752,7 @@ func autoDeleteLowRatedDownloadAfterAnalysis(ctx context.Context, videoPath stri
|
|||||||
maxStars := clampAutoDeleteRatingStars(s.AutoDeleteLowRatedDownloadsMaxStars)
|
maxStars := clampAutoDeleteRatingStars(s.AutoDeleteLowRatedDownloadsMaxStars)
|
||||||
|
|
||||||
stars := 0
|
stars := 0
|
||||||
|
|
||||||
if rating != nil {
|
if rating != nil {
|
||||||
stars = rating.Stars
|
stars = rating.Stars
|
||||||
if stars <= 0 && rating.Score > 0 {
|
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.
|
// Wichtig für Background-/Regenerate-Analyse:
|
||||||
// Das brauchen wir für Asset-/Regenerate-Jobs, weil dort die Analyse erst geprüft
|
// Dort wird rating teilweise als nil übergeben, obwohl es kurz vorher
|
||||||
// und danach gelöscht werden soll.
|
// in meta.json gespeichert wurde. Dann lesen wir die Sterne aus der Meta.
|
||||||
if stars < 1 || stars > 5 {
|
if stars <= 0 {
|
||||||
metaStars, ok, err := ratingStarsForVideoPath(videoPath)
|
if metaStars, ok, err := ratingStarsForVideoPath(videoPath); err != nil {
|
||||||
if err != nil {
|
appLogln("⚠️ [rating-delete] rating lookup failed:", filepath.Base(videoPath), err)
|
||||||
appLogln("⚠️ [rating-delete] rating aus meta konnte nicht gelesen werden:", filepath.Base(videoPath), err)
|
} else if ok {
|
||||||
return
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
stars = metaStars
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
// frontend\src\aiLabels.ts
|
||||||
|
|
||||||
export type AiLabelGroup =
|
export type AiLabelGroup =
|
||||||
| 'people'
|
| 'people'
|
||||||
| 'position'
|
| 'position'
|
||||||
@ -22,6 +24,7 @@ export const AI_POSITION_LABELS = [
|
|||||||
'facesitting',
|
'facesitting',
|
||||||
'handjob',
|
'handjob',
|
||||||
'blowjob',
|
'blowjob',
|
||||||
|
'boobjob',
|
||||||
'toy_play',
|
'toy_play',
|
||||||
'fingering',
|
'fingering',
|
||||||
'69',
|
'69',
|
||||||
@ -122,6 +125,7 @@ export const AI_LABEL_FALLBACKS: Record<string, string> = {
|
|||||||
facesitting: 'Facesitting',
|
facesitting: 'Facesitting',
|
||||||
handjob: 'Handjob',
|
handjob: 'Handjob',
|
||||||
blowjob: 'Blowjob',
|
blowjob: 'Blowjob',
|
||||||
|
boobjob: 'Boobjob',
|
||||||
toy_play: 'Toy Play',
|
toy_play: 'Toy Play',
|
||||||
fingering: 'Fingering',
|
fingering: 'Fingering',
|
||||||
'69': '69',
|
'69': '69',
|
||||||
|
|||||||
@ -63,6 +63,42 @@ export function FemaleBreastIcon(props: IconProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BoobjobIcon(props: IconProps) {
|
||||||
|
const { title, className, ...rest } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={iconClassName(className, 'origin-center scale-[1.12]')}
|
||||||
|
aria-hidden={title ? undefined : true}
|
||||||
|
role={title ? 'img' : undefined}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{title ? <title>{title}</title> : null}
|
||||||
|
|
||||||
|
<FemaleBreastIcon
|
||||||
|
x="3"
|
||||||
|
y="20"
|
||||||
|
width="94"
|
||||||
|
height="58"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="opacity-100"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MaleGenitaliaIcon
|
||||||
|
x="35"
|
||||||
|
y="25"
|
||||||
|
width="30"
|
||||||
|
height="54"
|
||||||
|
aria-hidden="true"
|
||||||
|
className="opacity-100"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ButtocksIcon(props: IconProps) {
|
export function ButtocksIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<IconBase
|
<IconBase
|
||||||
@ -1010,6 +1046,24 @@ const SEGMENT_LABEL_META: SegmentLabelMeta[] = [
|
|||||||
text: 'Blowjob',
|
text: 'Blowjob',
|
||||||
icon: BlowjobIcon,
|
icon: BlowjobIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
match: [
|
||||||
|
'boobjob',
|
||||||
|
'boob_job',
|
||||||
|
'boob-job',
|
||||||
|
'boob job',
|
||||||
|
'titjob',
|
||||||
|
'tit_job',
|
||||||
|
'tit-job',
|
||||||
|
'tit job',
|
||||||
|
'titfuck',
|
||||||
|
'tit_fuck',
|
||||||
|
'tit-fuck',
|
||||||
|
'tit fuck',
|
||||||
|
],
|
||||||
|
text: 'Boobjob',
|
||||||
|
icon: BoobjobIcon,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
match: ['cunnilingus'],
|
match: ['cunnilingus'],
|
||||||
text: 'Cunnilingus',
|
text: 'Cunnilingus',
|
||||||
|
|||||||
@ -1076,6 +1076,12 @@ function segmentKindFromText(value: unknown): SegmentVisualKind {
|
|||||||
clean.includes('facesitting') ||
|
clean.includes('facesitting') ||
|
||||||
clean.includes('handjob') ||
|
clean.includes('handjob') ||
|
||||||
clean.includes('blowjob') ||
|
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('fingering') ||
|
||||||
clean.includes('toy play') ||
|
clean.includes('toy play') ||
|
||||||
clean.includes('toy_play') ||
|
clean.includes('toy_play') ||
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user