From c9f2086b79be5a6ddb410afca3c1df5babc25cc0 Mon Sep 17 00:00:00 2001 From: Linrador <68631622+Linrador@users.noreply.github.com> Date: Fri, 22 May 2026 14:11:00 +0200 Subject: [PATCH] added sprites check --- backend/assets_generate.go | 6 +++ backend/recorder.go | 32 ++++++++++-- backend/tasks_assets.go | 13 +++++ backend/tasks_check_video.go | 78 ++++++++++++++++++++++++++++++ backend/tasks_regenerate_assets.go | 20 +++++++- 5 files changed, 143 insertions(+), 6 deletions(-) diff --git a/backend/assets_generate.go b/backend/assets_generate.go index 7e7ebf1..15d78b8 100644 --- a/backend/assets_generate.go +++ b/backend/assets_generate.go @@ -166,6 +166,12 @@ func ensureAnalyzeAllGoalsForVideoCtxForce( return false, appErrorf("videolänge konnte nicht bestimmt werden") } + if skip, reason, err := checkPreviewSpriteBeforeAnalysis(actx, videoPath); err != nil { + return false, err + } else if skip { + return false, skipAnalysisBecauseBadSpriteError(reason) + } + hits, err := analyzeVideoFromFrames(actx, videoPath) if err != nil { return false, err diff --git a/backend/recorder.go b/backend/recorder.go index d82859c..103d67f 100644 --- a/backend/recorder.go +++ b/backend/recorder.go @@ -1619,10 +1619,18 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st // Ohne Sprite kann keine Sprite-basierte Analyse laufen. if !truth.SpritesReady { - if firstErr == nil { - firstErr = appErrorf("Analysen übersprungen: preview-sprite.jpg fehlt") - } - return firstErr + err := skipAnalysisBecauseBadSpriteError("preview-sprite.jpg fehlt") + publishDeferredPhase( + fileName, + assetID, + "enrich", + "analyze", + "error", + "Analyse übersprungen", + err, + ) + + return nil } // ------------------------------------------------- @@ -1643,6 +1651,22 @@ func runDeferredEnrichPipeline(ctx context.Context, outPath string, sourceURL st return cerr } + if analysisSkippedByBadSprite(err) { + publishDeferredPhase( + fileName, + assetID, + "enrich", + "analyze", + "error", + "Analyse übersprungen", + err, + ) + + // Wichtig: nicht als Queue-Fehler zurückgeben, + // sonst wird die enrichQ unnötig weiter belastet/retryt. + return nil + } + analyzeReady = hasAIResultsForAllOutputGoals(outPath, requiredGoals) if err != nil || !analyzeReady { diff --git a/backend/tasks_assets.go b/backend/tasks_assets.go index b0e2098..a2a7e93 100644 --- a/backend/tasks_assets.go +++ b/backend/tasks_assets.go @@ -916,6 +916,19 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) { publishAssetsTaskPhase(it.name, "enrich", "analyze", "running", "Analyse") _, aerr := ensureAnalyzeForVideoCtx(fileCtx, it.path, sourceURL, "highlights") + if analysisSkippedByBadSprite(aerr) { + publishFinishedPostworkPhase( + it.name, + id, + "enrich", + "analyze", + "error", + "Analyse übersprungen", + aerr.Error(), + ) + continue + } + if skipFile, stopAll := handleFileAbort(aerr); stopAll { return } else if skipFile { diff --git a/backend/tasks_check_video.go b/backend/tasks_check_video.go index beb71e9..1708ba9 100644 --- a/backend/tasks_check_video.go +++ b/backend/tasks_check_video.go @@ -703,3 +703,81 @@ func runTailBlackoutCheck(ctx context.Context, videoPath string) (*tailBlackoutR return res, nil } + +var errAnalysisSkippedByBadSprite = errors.New("analysis skipped because preview-sprite.jpg is invalid") + +func analysisSkippedByBadSprite(err error) bool { + return errors.Is(err, errAnalysisSkippedByBadSprite) +} + +func skipAnalysisBecauseBadSpriteError(reason string) error { + reason = strings.TrimSpace(reason) + if reason == "" { + reason = "preview-sprite.jpg ist fehlerhaft" + } + + return fmt.Errorf("%w: %s", errAnalysisSkippedByBadSprite, reason) +} + +func persistBadPreviewSpriteReason(videoPath string, reason string) { + reason = strings.TrimSpace(reason) + if reason == "" { + reason = "preview-sprite.jpg ist fehlerhaft" + } + + err := persistTailBlackoutResult(videoPath, &tailBlackoutResult{ + CheckedAt: time.Now().Format(time.RFC3339Nano), + IsCorrupt: true, + Reason: reason, + }) + if err != nil { + appLogln("⚠️ preview-sprite validation persist failed:", videoPath, err) + } +} + +func checkPreviewSpriteBeforeAnalysis( + ctx context.Context, + videoPath string, +) (skip bool, reason string, err error) { + videoPath = strings.TrimSpace(videoPath) + if videoPath == "" { + return true, "videoPath ist leer", nil + } + + if err := ctx.Err(); err != nil { + return false, "", err + } + + assetID := assetIDFromVideoPath(videoPath) + if assetID == "" { + return true, "asset id fehlt", nil + } + + _, _, _, spritePath, _, perr := assetPathsForID(assetID) + if perr != nil { + return true, perr.Error(), nil + } + + if !fileExistsNonEmpty(spritePath) { + reason := "preview-sprite.jpg fehlt oder ist leer" + persistBadPreviewSpriteReason(videoPath, reason) + return true, reason, nil + } + + res, checkErr := runTailBlackoutCheck(ctx, videoPath) + if checkErr != nil { + reason := "preview-sprite.jpg konnte nicht geprüft werden: " + checkErr.Error() + persistBadPreviewSpriteReason(videoPath, reason) + return true, reason, nil + } + + if res != nil && res.IsCorrupt { + reason := strings.TrimSpace(res.Reason) + if reason == "" { + reason = "preview-sprite.jpg deutet auf beschädigtes Videoende hin" + } + return true, reason, nil + } + + return false, "", nil +} diff --git a/backend/tasks_regenerate_assets.go b/backend/tasks_regenerate_assets.go index 2fbff31..f976b81 100644 --- a/backend/tasks_regenerate_assets.go +++ b/backend/tasks_regenerate_assets.go @@ -466,8 +466,6 @@ func runRegenerateAssetsJob(job *regenerateAssetsJob) { publishFinishedPostworkPhase(file, id, "postwork", "sprites", "done", "Sprites", "") } - publishFinishedPostworkPhase(file, id, "postwork", "sprites", "done", "Sprites", "") - // 5) Analyse publishFinishedPostworkPhase(file, id, "enrich", "analyze", "running", "Analyse", "") @@ -476,6 +474,24 @@ func runRegenerateAssetsJob(job *regenerateAssetsJob) { cancelJob() return } + + if analysisSkippedByBadSprite(err) { + publishFinishedPostworkPhase( + file, + id, + "enrich", + "analyze", + "error", + "Analyse übersprungen", + err.Error(), + ) + + finishRegenerateAssetsJob(file, "done", "") + setRegenerateAssetsTaskDone(file, "Fertig, Analyse übersprungen") + removeRegenerateAssetsJobLater(file, 3*time.Second) + return + } + failJob("enrich", "analyze", err.Error()) return }