bugfixes
This commit is contained in:
parent
d538a664cf
commit
caf3e8b56b
@ -1049,7 +1049,10 @@ func recordAnalyzeVideo(w http.ResponseWriter, r *http.Request) {
|
||||
AnalyzedAtUnix: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if err := writeVideoAIForFile(ctx, outPath, "", ai); err != nil {
|
||||
persistCtx, persistCancel := context.WithTimeout(ctx, 90*time.Second)
|
||||
defer persistCancel()
|
||||
|
||||
if err := writeVideoAIForFile(persistCtx, outPath, "", ai); err != nil {
|
||||
logAnalyzeError("write-meta-ai", file, outPath, err)
|
||||
publishAnalysisError(analyzeStartedAtMs, file, "Speichern fehlgeschlagen", err)
|
||||
|
||||
@ -1065,7 +1068,7 @@ func recordAnalyzeVideo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
autoDeleteLowRatedDownloadAfterAnalysis(ctx, outPath, rating)
|
||||
autoDeleteLowRatedDownloadAfterAnalysis(persistCtx, outPath, rating)
|
||||
publishAnalyzePersistProgress(analyzeStartedAtMs, file, 1, "")
|
||||
publishAnalysisFinished(analyzeStartedAtMs, analyzeProgressTotal, file, "Analyse abgeschlossen")
|
||||
|
||||
|
||||
@ -17,7 +17,9 @@ const (
|
||||
analyzeVideoMAEClipWindowSeconds = 4.0
|
||||
analyzeVideoMAEClipStrideSeconds = 2.0
|
||||
analyzeVideoMAEMinScore = 0.34
|
||||
analyzeVideoMAERequestBatchSize = 48
|
||||
analyzeVideoMAERequestBatchSize = 8
|
||||
analyzeVideoMAEMaxClips = 180
|
||||
analyzeVideoMAERequestTimeout = 75 * time.Second
|
||||
)
|
||||
|
||||
type analyzeVideoMAEClipReqItem struct {
|
||||
@ -112,6 +114,31 @@ func buildAnalyzeVideoMAEClips(
|
||||
return clips
|
||||
}
|
||||
|
||||
func limitAnalyzeVideoMAEClips(clips []analyzeVideoMAEClipReqItem, maxClips int) []analyzeVideoMAEClipReqItem {
|
||||
if maxClips <= 0 || len(clips) <= maxClips {
|
||||
return clips
|
||||
}
|
||||
if maxClips == 1 {
|
||||
return clips[:1]
|
||||
}
|
||||
|
||||
out := make([]analyzeVideoMAEClipReqItem, 0, maxClips)
|
||||
lastIdx := -1
|
||||
for i := 0; i < maxClips; i++ {
|
||||
idx := int(math.Round(float64(i) * float64(len(clips)-1) / float64(maxClips-1)))
|
||||
if idx <= lastIdx {
|
||||
idx = lastIdx + 1
|
||||
}
|
||||
if idx >= len(clips) {
|
||||
idx = len(clips) - 1
|
||||
}
|
||||
out = append(out, clips[idx])
|
||||
lastIdx = idx
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func predictVideoMAEPositionClipsForAnalyze(
|
||||
ctx context.Context,
|
||||
clips []analyzeVideoMAEClipReqItem,
|
||||
@ -145,56 +172,68 @@ func predictVideoMAEPositionClipsForAnalyze(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
analyzeAIServerURL()+"/predict-position-clips",
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
parsed, err := func() (analyzeVideoMAEClipPredictResp, error) {
|
||||
var parsed analyzeVideoMAEClipPredictResp
|
||||
|
||||
reqCtx, cancel := context.WithTimeout(ctx, analyzeVideoMAERequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
reqCtx,
|
||||
http.MethodPost,
|
||||
analyzeAIServerURL()+"/predict-position-clips",
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return parsed, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
addAIServerAuth(req)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: analyzeVideoMAERequestTimeout + 5*time.Second,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return parsed, ctxErr
|
||||
}
|
||||
return parsed, err
|
||||
}
|
||||
|
||||
rawBody, readErr := io.ReadAll(res.Body)
|
||||
statusCode := res.StatusCode
|
||||
_ = res.Body.Close()
|
||||
if readErr != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return parsed, ctxErr
|
||||
}
|
||||
return parsed, readErr
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(rawBody, &parsed); err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return parsed, ctxErr
|
||||
}
|
||||
return parsed, fmt.Errorf("AI server VideoMAE JSON ungueltig: HTTP %d: %s", statusCode, strings.TrimSpace(string(rawBody)))
|
||||
}
|
||||
|
||||
if statusCode < 200 || statusCode >= 300 || !parsed.OK {
|
||||
msg := strings.TrimSpace(parsed.Error)
|
||||
if msg == "" {
|
||||
msg = fmt.Sprintf("AI server VideoMAE HTTP %d", statusCode)
|
||||
}
|
||||
return parsed, fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
return parsed, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
addAIServerAuth(req)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 180 * time.Second,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawBody, readErr := io.ReadAll(res.Body)
|
||||
_ = res.Body.Close()
|
||||
if readErr != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
var parsed analyzeVideoMAEClipPredictResp
|
||||
if err := json.Unmarshal(rawBody, &parsed); err != nil {
|
||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
return nil, fmt.Errorf("AI server VideoMAE JSON ungueltig: HTTP %d: %s", res.StatusCode, strings.TrimSpace(string(rawBody)))
|
||||
}
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 || !parsed.OK {
|
||||
msg := strings.TrimSpace(parsed.Error)
|
||||
if msg == "" {
|
||||
msg = fmt.Sprintf("AI server VideoMAE HTTP %d", res.StatusCode)
|
||||
}
|
||||
return nil, fmt.Errorf("%s", msg)
|
||||
}
|
||||
|
||||
if !parsed.Available {
|
||||
if onProgress != nil {
|
||||
onProgress(len(clips), len(clips))
|
||||
@ -227,6 +266,14 @@ func applyVideoMAEPositionClipsForAnalyze(
|
||||
if len(clips) == 0 {
|
||||
return highlightHits, positionEvidence
|
||||
}
|
||||
if len(clips) > analyzeVideoMAEMaxClips {
|
||||
appLogf(
|
||||
"VideoMAE Clip-Analyse begrenzt: clips=%d max=%d",
|
||||
len(clips),
|
||||
analyzeVideoMAEMaxClips,
|
||||
)
|
||||
clips = limitAnalyzeVideoMAEClips(clips, analyzeVideoMAEMaxClips)
|
||||
}
|
||||
|
||||
if onProgress != nil {
|
||||
onProgress(0, len(clips))
|
||||
|
||||
@ -196,7 +196,10 @@ func ensureAnalyzeAllGoalsForVideoCtxForce(
|
||||
AnalyzedAtUnix: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if err := writeVideoAIForFile(actx, videoPath, meta.sourceURL, ai); err != nil {
|
||||
persistCtx, persistCancel := context.WithTimeout(ctx, 90*time.Second)
|
||||
defer persistCancel()
|
||||
|
||||
if err := writeVideoAIForFile(persistCtx, videoPath, meta.sourceURL, ai); err != nil {
|
||||
publishAnalysisError(analyzeStartedAtMs, analyzeFile, "Speichern fehlgeschlagen", err)
|
||||
return false, err
|
||||
}
|
||||
@ -210,7 +213,7 @@ func ensureAnalyzeAllGoalsForVideoCtxForce(
|
||||
|
||||
// Direkt nach erfolgreicher Analyse löschen.
|
||||
// Wichtig: hier rating übergeben, nicht nil.
|
||||
autoDeleteLowRatedDownloadAfterAnalysis(actx, videoPath, rating)
|
||||
autoDeleteLowRatedDownloadAfterAnalysis(persistCtx, videoPath, rating)
|
||||
publishAnalyzePersistProgress(analyzeStartedAtMs, analyzeFile, 1, "")
|
||||
publishAnalysisFinished(analyzeStartedAtMs, analyzeProgressTotal, analyzeFile, "Analyse abgeschlossen")
|
||||
|
||||
@ -928,7 +931,12 @@ func ensureAnalyzeForVideoCtx(
|
||||
}
|
||||
}
|
||||
|
||||
return hasAIAnalysisForOutputGoal(videoPath, goal), nil
|
||||
ready := hasAIAnalysisForOutputGoal(videoPath, goal)
|
||||
if analyzed && !ready {
|
||||
return false, appErrorf("analyse-meta konnte nach Analyse nicht verifiziert werden: %s", id)
|
||||
}
|
||||
|
||||
return ready, nil
|
||||
}
|
||||
|
||||
func ensureDeferredAssetsAndAI(ctx context.Context, videoPath, sourceURL string, goal string) error {
|
||||
@ -1611,13 +1619,17 @@ func prepareVideoForSplit(ctx context.Context, videoPath, sourceURL, goal string
|
||||
ai := &aiAnalysisMeta{
|
||||
Goal: "highlights",
|
||||
Mode: "video",
|
||||
Completed: true,
|
||||
Hits: hits,
|
||||
Segments: segments,
|
||||
Rating: rating,
|
||||
AnalyzedAtUnix: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if werr := writeVideoAIForFile(ctx, videoPath, sourceURL, ai); werr != nil {
|
||||
persistCtx, persistCancel := context.WithTimeout(ctx, 90*time.Second)
|
||||
defer persistCancel()
|
||||
|
||||
if werr := writeVideoAIForFile(persistCtx, videoPath, sourceURL, ai); werr != nil {
|
||||
publishAnalysisError(analyzeStartedAtMs2, analyzeFile2, "Speichern fehlgeschlagen", werr)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
BIN
backend/dist/nsfwapp-linux-amd64
vendored
BIN
backend/dist/nsfwapp-linux-amd64
vendored
Binary file not shown.
BIN
backend/dist/nsfwapp.exe
vendored
BIN
backend/dist/nsfwapp.exe
vendored
Binary file not shown.
@ -680,7 +680,7 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
||||
return false, false
|
||||
}
|
||||
|
||||
if errors.Is(stepErr, context.Canceled) || errors.Is(stepErr, context.DeadlineExceeded) {
|
||||
if errors.Is(stepErr, context.Canceled) {
|
||||
// globaler Task wurde beendet
|
||||
if ctx.Err() != nil && !isAssetsJobSkipRequested(jobID, it.name) {
|
||||
finishFileControl()
|
||||
@ -693,6 +693,18 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
||||
}
|
||||
|
||||
// Datei wurde evtl. während des Verarbeitungsschritts gelöscht/verschoben
|
||||
if errors.Is(stepErr, context.DeadlineExceeded) {
|
||||
if ctx.Err() != nil {
|
||||
finishFileControl()
|
||||
finishWithErr(context.Canceled)
|
||||
return false, true
|
||||
}
|
||||
if isAssetsJobSkipRequested(jobID, it.name) || !fileExistsNonEmpty(it.path) {
|
||||
return true, false
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
if isAssetsJobSkipRequested(jobID, it.name) || !fileExistsNonEmpty(it.path) {
|
||||
return true, false
|
||||
}
|
||||
@ -939,10 +951,12 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
||||
}
|
||||
|
||||
if aerr != nil {
|
||||
appLogln("❌ assets task analyse fehlgeschlagen für", it.path+":", aerr)
|
||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
||||
} else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady {
|
||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse")
|
||||
} else {
|
||||
appLogln("❌ assets task analyse fehlt nach Lauf:", it.path)
|
||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,26 @@ func TestBuildAnalyzeVideoMAEClipsUsesWindowAndStride(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitAnalyzeVideoMAEClipsKeepsEvenCoverage(t *testing.T) {
|
||||
clips := make([]analyzeVideoMAEClipReqItem, 10)
|
||||
for i := range clips {
|
||||
clips[i] = analyzeVideoMAEClipReqItem{Time: float64(i)}
|
||||
}
|
||||
|
||||
limited := limitAnalyzeVideoMAEClips(clips, 4)
|
||||
if len(limited) != 4 {
|
||||
t.Fatalf("clip count = %d, want 4", len(limited))
|
||||
}
|
||||
|
||||
got := []float64{limited[0].Time, limited[1].Time, limited[2].Time, limited[3].Time}
|
||||
want := []float64{0, 3, 6, 9}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("clip times = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrainingResolveVideoMAEModelUsesConfigDir(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
modelDir := filepath.Join(root, "videomae", "model")
|
||||
|
||||
@ -3189,7 +3189,7 @@ export default function App() {
|
||||
new CustomEvent('finished-downloads:postwork', {
|
||||
detail: {
|
||||
file,
|
||||
assetId: file.replace(/\.[^.]+$/, ''),
|
||||
assetId: stripHotPrefix(file.replace(/\.[^.]+$/, '')),
|
||||
queue: 'enrich',
|
||||
phase: 'analyze',
|
||||
state,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user