This commit is contained in:
Linrador 2026-05-29 07:28:15 +02:00
parent 06b4ff2666
commit fd0bab67fb
5 changed files with 136 additions and 68 deletions

View File

@ -1,2 +1,2 @@
HTTPS_ENABLED=1 HTTPS_ENABLED=0
AUTH_RP_ORIGINS=https://l14pbbk95100006.tegdssd.de:9999,https://l14pbbk95100006.tegdssd.de:5173,https://localhost:9999,https://127.0.0.1:9999,https://10.0.1.25:9999,http://localhost:5173,http://127.0.0.1:5173,http://10.0.1.25:5173 AUTH_RP_ORIGINS=https://l14pbbk95100006.tegdssd.de:9999,https://l14pbbk95100006.tegdssd.de:5173,https://localhost:9999,https://127.0.0.1:9999,https://10.0.1.25:9999,http://localhost:5173,http://127.0.0.1:5173,http://10.0.1.25:5173

View File

@ -25,7 +25,7 @@ import (
"time" "time"
) )
const trainingUncertainCandidateCount = 5 const trainingUncertainCandidateCount = 10
type TrainingLabels struct { type TrainingLabels struct {
People []string `json:"people"` People []string `json:"people"`
@ -1078,7 +1078,7 @@ func trainingLabelsHandler(w http.ResponseWriter, r *http.Request) {
trainingWriteJSON(w, http.StatusOK, defaultTrainingLabelsFromJSON()) trainingWriteJSON(w, http.StatusOK, defaultTrainingLabelsFromJSON())
} }
const trainingImportVideoDefaultFrameCount = 8 const trainingImportVideoDefaultFrameCount = 10
const trainingImportVideoMaxFrameCount = 30 const trainingImportVideoMaxFrameCount = 30
type TrainingImportVideoRequest struct { type TrainingImportVideoRequest struct {
@ -3229,6 +3229,8 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64, requestID
missing = 1 missing = 1
} }
candidateWindowTotal := trainingUncertainCandidateCount
const stepsPerCandidate = 4 const stepsPerCandidate = 4
totalSteps := missing*stepsPerCandidate + 1 totalSteps := missing*stepsPerCandidate + 1
@ -3238,7 +3240,7 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64, requestID
attempt := i + 1 attempt := i + 1
stepStart := i*stepsPerCandidate + 1 stepStart := i*stepsPerCandidate + 1
prefix := fmt.Sprintf("Kandidat %d/%d: ", attempt, missing) prefix := fmt.Sprintf("Kandidat %d/%d: ", attempt, candidateWindowTotal)
candidate, err := trainingCreateUncertainCandidateWithProgress( candidate, err := trainingCreateUncertainCandidateWithProgress(
root, root,
@ -3258,7 +3260,7 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64, requestID
stepStart+stepsPerCandidate-1, stepStart+stepsPerCandidate-1,
totalSteps, totalSteps,
"", "",
fmt.Sprintf("Kandidat %d/%d fehlgeschlagen…", attempt, missing), fmt.Sprintf("Kandidat %d/%d fehlgeschlagen…", attempt, candidateWindowTotal),
) )
continue continue
@ -3275,7 +3277,7 @@ func trainingCreateUncertainNextSampleWithProgress(startedAtMs int64, requestID
fmt.Sprintf( fmt.Sprintf(
"Kandidat %d/%d bewertet · Unsicherheit %.0f%%", "Kandidat %d/%d bewertet · Unsicherheit %.0f%%",
attempt, attempt,
missing, candidateWindowTotal,
candidate.score*100, candidate.score*100,
), ),
) )

View File

@ -703,6 +703,56 @@ function firstNumber(obj: Record<string, unknown>, keys: string[]): number | und
return undefined return undefined
} }
function normalizeSegmentTimeValue(
key: string,
raw: unknown,
durationSeconds: number
): number | undefined {
const n = Number(raw)
if (!Number.isFinite(n) || n < 0) return undefined
const k = key.toLowerCase()
const duration = Number(durationSeconds)
const hasDuration = Number.isFinite(duration) && duration > 0
if (k.endsWith('ms') || k.includes('millisecond')) {
return n / 1000
}
if (hasDuration) {
// Einige Analyse-Backends schreiben Millisekunden in generische Felder
// wie `start`, `end`, `time` oder `timestamp`. Ohne diese Korrektur
// würde der Player z.B. 95500 als 95.500 Sekunden statt 95,5 Sekunden suchen.
const asMillis = n / 1000
if (n > duration + 1 && asMillis <= duration + 1) {
return asMillis
}
// Ebenfalls unterstützen: normalisierte Positionen 0..1, falls ein
// Backend explizit ratio/percent/pct im Feldnamen verwendet.
if (n >= 0 && n <= 1 && /(ratio|percent|pct)$/.test(k)) {
return n * duration
}
}
return n
}
function firstSegmentTimeNumber(
obj: Record<string, unknown>,
keys: string[],
durationSeconds: number
): number | undefined {
for (const key of keys) {
if (!(key in obj)) continue
const n = normalizeSegmentTimeValue(key, obj[key], durationSeconds)
if (typeof n === 'number') return n
}
return undefined
}
type PreviewSpriteMeta = { type PreviewSpriteMeta = {
path: string path: string
count: number count: number
@ -832,73 +882,81 @@ function readMetaDurationSeconds(metaRaw: unknown): number {
return Number.isFinite(n) && n > 0 ? n : 0 return Number.isFinite(n) && n > 0 ? n : 0
} }
function segmentPreviewTime(obj: Record<string, unknown>): number | undefined { const RATING_SEGMENT_OPEN_PREROLL_SECONDS = 3
const start = firstNumber(obj, [
function segmentAnchorTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
const start = firstSegmentTimeNumber(obj, [
'startSec', 'startSec',
'startSeconds', 'startSeconds',
'start_s', 'start_s',
'start', 'start',
'from', 'from',
'startMs', 'startMs',
]) 'startRatio',
'startPercent',
'startPct',
], durationSeconds)
const end = firstNumber(obj, [ const marker = firstSegmentTimeNumber(obj, [
'markerTime',
'markerSec',
'markerSeconds',
'time',
't',
'timestamp',
'at',
'timeMs',
'timestampMs',
'timeRatio',
'timestampRatio',
'timePercent',
'timestampPercent',
'timePct',
'timestampPct',
], durationSeconds)
if (typeof start === 'number') return start
if (typeof marker === 'number') return marker
return undefined
}
function segmentPreviewTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
const anchor = segmentAnchorTime(obj, durationSeconds)
if (typeof anchor === 'number') return anchor
const end = firstSegmentTimeNumber(obj, [
'endSec', 'endSec',
'endSeconds', 'endSeconds',
'end_s', 'end_s',
'end', 'end',
'to', 'to',
'endMs', 'endMs',
]) 'endRatio',
'endPercent',
'endPct',
], durationSeconds)
const marker = firstNumber(obj, [ return typeof end === 'number' ? end : undefined
'markerTime',
'markerSec',
'markerSeconds',
'time',
't',
'timestamp',
'at',
'timeMs',
'timestampMs',
])
if (typeof start === 'number' && typeof end === 'number' && end > start) {
return start + (end - start) / 2
}
if (typeof marker === 'number') return marker
if (typeof start === 'number') return start
return undefined
} }
function segmentOpenTime(obj: Record<string, unknown>): number | undefined { function segmentOpenTime(
const start = firstNumber(obj, [ obj: Record<string, unknown>,
'startSec', durationSeconds: number
'startSeconds', ): number | undefined {
'start_s', const anchor =
'start', segmentAnchorTime(obj, durationSeconds) ??
'from', segmentPreviewTime(obj, durationSeconds)
'startMs',
])
const marker = firstNumber(obj, [ if (typeof anchor !== 'number') return undefined
'markerTime',
'markerSec',
'markerSeconds',
'time',
't',
'timestamp',
'at',
'timeMs',
'timestampMs',
])
if (typeof start === 'number') return start return Math.max(0, anchor - RATING_SEGMENT_OPEN_PREROLL_SECONDS)
if (typeof marker === 'number') return marker
return segmentPreviewTime(obj)
} }
function spriteFrameIndexFromTime( function spriteFrameIndexFromTime(
@ -1009,8 +1067,8 @@ function segmentValueLabel(obj: Record<string, unknown>): string | undefined {
return score.toFixed(2).replace(/\.?0+$/, '') return score.toFixed(2).replace(/\.?0+$/, '')
} }
function segmentTimeLabel(obj: Record<string, unknown>): string | undefined { function segmentTimeLabel(obj: Record<string, unknown>, durationSeconds: number): string | undefined {
const start = firstNumber(obj, [ const start = firstSegmentTimeNumber(obj, [
'startSec', 'startSec',
'startSeconds', 'startSeconds',
'start_s', 'start_s',
@ -1020,16 +1078,22 @@ function segmentTimeLabel(obj: Record<string, unknown>): string | undefined {
'time', 'time',
'timestamp', 'timestamp',
'startMs', 'startMs',
]) 'startRatio',
'startPercent',
'startPct',
], durationSeconds)
const end = firstNumber(obj, [ const end = firstSegmentTimeNumber(obj, [
'endSec', 'endSec',
'endSeconds', 'endSeconds',
'end_s', 'end_s',
'end', 'end',
'to', 'to',
'endMs', 'endMs',
]) 'endRatio',
'endPercent',
'endPct',
], durationSeconds)
const duration = firstNumber(obj, [ const duration = firstNumber(obj, [
'durationSec', 'durationSec',
@ -1250,8 +1314,8 @@ export function readRatingSegments(metaRaw: unknown): RatingSegment[] {
.join(', ') .join(', ')
: undefined : undefined
const previewTime = segmentPreviewTime(raw) const previewTime = segmentPreviewTime(raw, durationSeconds)
const openAtSec = segmentOpenTime(raw) const openAtSec = segmentOpenTime(raw, durationSeconds)
const preview = const preview =
spriteMeta && typeof previewTime === 'number' spriteMeta && typeof previewTime === 'number'
@ -1263,7 +1327,7 @@ export function readRatingSegments(metaRaw: unknown): RatingSegment[] {
return { return {
key: String(raw.id ?? raw.key ?? `${index}`), key: String(raw.id ?? raw.key ?? `${index}`),
title, title,
timeLabel: segmentTimeLabel(raw), timeLabel: segmentTimeLabel(raw, durationSeconds),
valueLabel: segmentValueLabel(raw), valueLabel: segmentValueLabel(raw),
detail, detail,
tags: readSegmentTags(raw), tags: readSegmentTags(raw),

View File

@ -255,7 +255,7 @@ export default function RecordJobActions({
jobId: String((job as any)?.id ?? ''), jobId: String((job as any)?.id ?? ''),
output, output,
sourceFile: baseName(output), sourceFile: baseName(output),
count: 8, count: 10,
} }
// Falls TrainingTab gerade nicht gemountet ist. // Falls TrainingTab gerade nicht gemountet ist.

View File

@ -1,4 +1,4 @@
// frontend/src/components/ui/TrainingTab.tsx // frontend\src\components\ui\TrainingTab.tsx
'use client' 'use client'
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react' import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
@ -2797,14 +2797,16 @@ export default function TrainingTab(props: {
setLoadingPreviewCandidate('') setLoadingPreviewCandidate('')
const TRAINING_IMPORT_FRAME_COUNT = 10
const detail: PendingTrainingVideoImport = { const detail: PendingTrainingVideoImport = {
jobId: String(raw?.jobId || '').trim(), jobId: String(raw?.jobId || '').trim(),
output, output,
sourceFile: String(raw?.sourceFile || '').trim(), sourceFile: String(raw?.sourceFile || '').trim(),
count: Number(raw?.count || 8), count: Number(raw?.count || TRAINING_IMPORT_FRAME_COUNT),
} }
const importKey = `${detail.jobId || ''}|${detail.output}|${detail.count || 8}` const importKey = `${detail.jobId || ''}|${detail.output}|${detail.count || TRAINING_IMPORT_FRAME_COUNT}`
// Verhindert Doppelimport durch sessionStorage + CustomEvent. // Verhindert Doppelimport durch sessionStorage + CustomEvent.
if (videoImportInFlightKeyRef.current === importKey) { if (videoImportInFlightKeyRef.current === importKey) {
@ -2838,7 +2840,7 @@ export default function TrainingTab(props: {
body: JSON.stringify({ body: JSON.stringify({
jobId: detail.jobId, jobId: detail.jobId,
output: detail.output, output: detail.output,
count: detail.count || 8, count: detail.count || TRAINING_IMPORT_FRAME_COUNT,
analysisRequestId: requestId, analysisRequestId: requestId,
}), }),
}) })