This commit is contained in:
Linrador 2026-05-05 15:35:33 +02:00
parent cf5888d9e5
commit ec4fcea9b1
3 changed files with 230 additions and 12 deletions

View File

@ -373,7 +373,7 @@ func trainingPredictionToHighlightResults(pred TrainingPrediction) []NsfwFrameRe
} }
comboScore := math.Min(positionScore, score) comboScore := math.Min(positionScore, score)
addHighlightResult(best, "combo:"+sexPosition+"+"+prefix+":"+label, comboScore) addHighlightResult(best, "combo:position:"+sexPosition+"+"+prefix+":"+label, comboScore)
} }
} }
@ -1504,8 +1504,21 @@ func buildCombinedHighlightHitFromPrediction(pred TrainingPrediction, t float64)
best := map[string]highlightSignal{} best := map[string]highlightSignal{}
sexPosition := strings.ToLower(strings.TrimSpace(pred.SexPosition)) sexPosition := strings.ToLower(strings.TrimSpace(pred.SexPosition))
if sexPosition != "" && sexPosition != "unknown" {
addHighlightSignal(best, "position:"+sexPosition, pred.SexPositionScore) if sexPosition != "" && sexPosition != "unknown" && isKnownPositionLabel(sexPosition) {
positionScore := pred.SexPositionScore
if positionScore <= 0 {
positionScore = 0.35
}
// Position soll als Kontext in Kombis bleiben.
// Sie erzeugt weiterhin kein Segment alleine, weil unten mindestens
// ein Nicht-Positionssignal verlangt wird.
best["position:"+sexPosition] = highlightSignal{
Label: "position:" + sexPosition,
Score: math.Max(positionScore, 0.20),
Group: "position",
}
} }
addHighlightSignalsFromScoredLabels(best, "body", pred.BodyPartsPresent) addHighlightSignalsFromScoredLabels(best, "body", pred.BodyPartsPresent)
@ -2297,6 +2310,19 @@ func preferAnalyzeSegmentLabel(a, b string) string {
return a return a
} }
if strings.HasPrefix(a, "combo:") && strings.HasPrefix(b, "combo:") {
if strings.Contains(a, "position:") && !strings.Contains(b, "position:") {
return a
}
if strings.Contains(b, "position:") && !strings.Contains(a, "position:") {
return b
}
if len(b) > len(a) {
return b
}
return a
}
// body: ist meist semantisch sauberer als detector: // body: ist meist semantisch sauberer als detector:
if strings.HasPrefix(a, "body:") && !strings.HasPrefix(b, "body:") { if strings.HasPrefix(a, "body:") && !strings.HasPrefix(b, "body:") {
return a return a
@ -2328,6 +2354,91 @@ func preferAnalyzeSegmentLabel(a, b string) string {
return a return a
} }
func segmentLabelParts(label string) []string {
label = strings.ToLower(strings.TrimSpace(label))
if label == "" {
return nil
}
if strings.HasPrefix(label, "combo:") {
raw := strings.TrimPrefix(label, "combo:")
parts := strings.Split(raw, "+")
out := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.ToLower(strings.TrimSpace(part))
if part != "" {
out = append(out, part)
}
}
return out
}
return []string{label}
}
func segmentPositionFromAnalyzeLabel(label string) string {
for _, part := range segmentLabelParts(label) {
part = strings.TrimSpace(part)
part = strings.TrimPrefix(part, "position:")
if isKnownPositionLabel(part) {
return part
}
}
return ""
}
func segmentTagsFromAnalyzeLabel(label string) []string {
parts := segmentLabelParts(label)
if len(parts) == 0 {
return nil
}
out := make([]string, 0, len(parts))
seen := map[string]bool{}
for _, part := range parts {
part = strings.ToLower(strings.TrimSpace(part))
if part == "" {
continue
}
// Person nicht als Segment-Tag speichern.
if isPersonSegmentLabel(part) {
continue
}
if strings.HasPrefix(part, "position:") {
pos := strings.TrimPrefix(part, "position:")
if pos == "" || !isKnownPositionLabel(pos) {
continue
}
tag := "position:" + pos
if !seen[tag] {
seen[tag] = true
out = append(out, tag)
}
continue
}
if !seen[part] {
seen[part] = true
out = append(out, part)
}
}
if len(out) == 0 {
return nil
}
return out
}
func mergeAnalyzeHits(in []analyzeHit) []analyzeHit { func mergeAnalyzeHits(in []analyzeHit) []analyzeHit {
if len(in) == 0 { if len(in) == 0 {
return []analyzeHit{} return []analyzeHit{}
@ -2576,13 +2687,17 @@ func buildSegmentsFromAnalyzeHits(hits []analyzeHit, duration float64) []aiSegme
continue continue
} }
label := strings.ToLower(strings.TrimSpace(hit.Label))
out = append(out, aiSegmentMeta{ out = append(out, aiSegmentMeta{
Label: strings.ToLower(strings.TrimSpace(hit.Label)), Label: label,
StartSeconds: start, StartSeconds: start,
EndSeconds: end, EndSeconds: end,
DurationSeconds: end - start, DurationSeconds: end - start,
Score: hit.Score, Score: hit.Score,
AutoSelected: true, AutoSelected: true,
Position: segmentPositionFromAnalyzeLabel(label),
Tags: segmentTagsFromAnalyzeLabel(label),
}) })
} }
@ -2696,6 +2811,8 @@ func buildHighlightSegmentsFromAnalyzeHits(hits []analyzeHit, duration float64)
DurationSeconds: end - start, DurationSeconds: end - start,
Score: hit.Score, Score: hit.Score,
AutoSelected: true, AutoSelected: true,
Position: segmentPositionFromAnalyzeLabel(label),
Tags: segmentTagsFromAnalyzeLabel(label),
}) })
} }

View File

@ -104,12 +104,14 @@ type videoMeta struct {
} }
type aiSegmentMeta struct { type aiSegmentMeta struct {
Label string `json:"label"` Label string `json:"label"`
StartSeconds float64 `json:"startSeconds"` StartSeconds float64 `json:"startSeconds"`
EndSeconds float64 `json:"endSeconds"` EndSeconds float64 `json:"endSeconds"`
DurationSeconds float64 `json:"durationSeconds"` DurationSeconds float64 `json:"durationSeconds"`
Score float64 `json:"score,omitempty"` Score float64 `json:"score,omitempty"`
AutoSelected bool `json:"autoSelected,omitempty"` AutoSelected bool `json:"autoSelected,omitempty"`
Position string `json:"position,omitempty"`
Tags []string `json:"tags,omitempty"`
} }
type aiAnalysisMeta struct { type aiAnalysisMeta struct {

View File

@ -289,12 +289,111 @@ function prettyAiLabel(value: unknown): string {
return isAiLikeLabel(raw) ? titleCaseLabel(raw) : raw return isAiLikeLabel(raw) ? titleCaseLabel(raw) : raw
} }
function rawSegmentLabelParts(value: unknown): string[] {
const raw = String(value ?? '').trim().toLowerCase()
if (!raw) return []
if (raw.startsWith('combo:')) {
return raw
.slice('combo:'.length)
.split('+')
.map((part) => part.trim())
.filter(Boolean)
}
return [raw]
}
function tagsFromSegmentLabel(value: unknown): string[] {
const out: string[] = []
for (const part of rawSegmentLabelParts(value)) {
const clean = part.trim()
if (!clean) continue
if (clean.startsWith('position:')) {
const label = prettyAiLabel(clean)
if (label) out.push(`Position: ${label}`)
continue
}
if (clean.startsWith('body:')) {
const label = prettyAiLabel(clean)
if (label) out.push(label)
continue
}
if (clean.startsWith('object:')) {
const label = prettyAiLabel(clean)
if (label) out.push(label)
continue
}
if (clean.startsWith('clothing:')) {
const label = prettyAiLabel(clean)
if (label) out.push(label)
continue
}
if (clean.startsWith('detector:')) {
const label = prettyAiLabel(clean)
if (label) out.push(label)
continue
}
if (isKnownFrontendPositionLabel(clean)) {
const label = prettyAiLabel(clean)
if (label) out.push(`Position: ${label}`)
continue
}
const label = prettyAiLabel(clean)
if (label) out.push(label)
}
return out
}
function isKnownFrontendPositionLabel(value: unknown): boolean {
const label = String(value ?? '').trim().toLowerCase()
return [
'missionary',
'doggy',
'doggystyle',
'cowgirl',
'reverse_cowgirl',
'cunnilingus',
'prone_bone',
'standing',
'standing_doggy',
'spooning',
'sitting',
'facesitting',
'handjob',
'blowjob',
'toy_play',
'fingering',
'69',
'other',
].includes(label)
}
function segmentTitleFromRawLabel(raw: string): string { function segmentTitleFromRawLabel(raw: string): string {
return prettyAiLabel(raw) return prettyAiLabel(raw)
} }
function readSegmentTags(obj: Record<string, unknown>): string[] { function readSegmentTags(obj: Record<string, unknown>): string[] {
const tags = readTags(obj) const position = typeof obj.position === 'string' && obj.position.trim()
? `Position: ${prettyAiLabel(obj.position)}`
: ''
const tags = [
position,
...tagsFromSegmentLabel(obj.label ?? obj.title ?? obj.category ?? obj.type ?? obj.kind ?? obj.name),
...readTags(obj),
]
const out: string[] = [] const out: string[] = []
for (const tag of tags) { for (const tag of tags) {
@ -303,7 +402,7 @@ function readSegmentTags(obj: Record<string, unknown>): string[] {
out.push(tag) out.push(tag)
} }
return out.slice(0, 6) return out.slice(0, 8)
} }
function prettyMaybeAiValue(key: string, value: string): string { function prettyMaybeAiValue(key: string, value: string): string {