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

View File

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

View File

@ -703,6 +703,56 @@ function firstNumber(obj: Record<string, unknown>, keys: string[]): number | und
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 = {
path: string
count: number
@ -832,73 +882,81 @@ function readMetaDurationSeconds(metaRaw: unknown): number {
return Number.isFinite(n) && n > 0 ? n : 0
}
function segmentPreviewTime(obj: Record<string, unknown>): number | undefined {
const start = firstNumber(obj, [
const RATING_SEGMENT_OPEN_PREROLL_SECONDS = 3
function segmentAnchorTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
const start = firstSegmentTimeNumber(obj, [
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'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',
'endSeconds',
'end_s',
'end',
'to',
'endMs',
])
'endRatio',
'endPercent',
'endPct',
], durationSeconds)
const marker = firstNumber(obj, [
'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
return typeof end === 'number' ? end : undefined
}
function segmentOpenTime(obj: Record<string, unknown>): number | undefined {
const start = firstNumber(obj, [
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'startMs',
])
function segmentOpenTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
const anchor =
segmentAnchorTime(obj, durationSeconds) ??
segmentPreviewTime(obj, durationSeconds)
const marker = firstNumber(obj, [
'markerTime',
'markerSec',
'markerSeconds',
'time',
't',
'timestamp',
'at',
'timeMs',
'timestampMs',
])
if (typeof anchor !== 'number') return undefined
if (typeof start === 'number') return start
if (typeof marker === 'number') return marker
return segmentPreviewTime(obj)
return Math.max(0, anchor - RATING_SEGMENT_OPEN_PREROLL_SECONDS)
}
function spriteFrameIndexFromTime(
@ -1009,8 +1067,8 @@ function segmentValueLabel(obj: Record<string, unknown>): string | undefined {
return score.toFixed(2).replace(/\.?0+$/, '')
}
function segmentTimeLabel(obj: Record<string, unknown>): string | undefined {
const start = firstNumber(obj, [
function segmentTimeLabel(obj: Record<string, unknown>, durationSeconds: number): string | undefined {
const start = firstSegmentTimeNumber(obj, [
'startSec',
'startSeconds',
'start_s',
@ -1020,16 +1078,22 @@ function segmentTimeLabel(obj: Record<string, unknown>): string | undefined {
'time',
'timestamp',
'startMs',
])
'startRatio',
'startPercent',
'startPct',
], durationSeconds)
const end = firstNumber(obj, [
const end = firstSegmentTimeNumber(obj, [
'endSec',
'endSeconds',
'end_s',
'end',
'to',
'endMs',
])
'endRatio',
'endPercent',
'endPct',
], durationSeconds)
const duration = firstNumber(obj, [
'durationSec',
@ -1250,8 +1314,8 @@ export function readRatingSegments(metaRaw: unknown): RatingSegment[] {
.join(', ')
: undefined
const previewTime = segmentPreviewTime(raw)
const openAtSec = segmentOpenTime(raw)
const previewTime = segmentPreviewTime(raw, durationSeconds)
const openAtSec = segmentOpenTime(raw, durationSeconds)
const preview =
spriteMeta && typeof previewTime === 'number'
@ -1263,7 +1327,7 @@ export function readRatingSegments(metaRaw: unknown): RatingSegment[] {
return {
key: String(raw.id ?? raw.key ?? `${index}`),
title,
timeLabel: segmentTimeLabel(raw),
timeLabel: segmentTimeLabel(raw, durationSeconds),
valueLabel: segmentValueLabel(raw),
detail,
tags: readSegmentTags(raw),

View File

@ -255,7 +255,7 @@ export default function RecordJobActions({
jobId: String((job as any)?.id ?? ''),
output,
sourceFile: baseName(output),
count: 8,
count: 10,
}
// 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'
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'
@ -2797,14 +2797,16 @@ export default function TrainingTab(props: {
setLoadingPreviewCandidate('')
const TRAINING_IMPORT_FRAME_COUNT = 10
const detail: PendingTrainingVideoImport = {
jobId: String(raw?.jobId || '').trim(),
output,
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.
if (videoImportInFlightKeyRef.current === importKey) {
@ -2838,7 +2840,7 @@ export default function TrainingTab(props: {
body: JSON.stringify({
jobId: detail.jobId,
output: detail.output,
count: detail.count || 8,
count: detail.count || TRAINING_IMPORT_FRAME_COUNT,
analysisRequestId: requestId,
}),
})