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(),
|
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)
|
logAnalyzeError("write-meta-ai", file, outPath, err)
|
||||||
publishAnalysisError(analyzeStartedAtMs, file, "Speichern fehlgeschlagen", err)
|
publishAnalysisError(analyzeStartedAtMs, file, "Speichern fehlgeschlagen", err)
|
||||||
|
|
||||||
@ -1065,7 +1068,7 @@ func recordAnalyzeVideo(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
autoDeleteLowRatedDownloadAfterAnalysis(ctx, outPath, rating)
|
autoDeleteLowRatedDownloadAfterAnalysis(persistCtx, outPath, rating)
|
||||||
publishAnalyzePersistProgress(analyzeStartedAtMs, file, 1, "")
|
publishAnalyzePersistProgress(analyzeStartedAtMs, file, 1, "")
|
||||||
publishAnalysisFinished(analyzeStartedAtMs, analyzeProgressTotal, file, "Analyse abgeschlossen")
|
publishAnalysisFinished(analyzeStartedAtMs, analyzeProgressTotal, file, "Analyse abgeschlossen")
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,9 @@ const (
|
|||||||
analyzeVideoMAEClipWindowSeconds = 4.0
|
analyzeVideoMAEClipWindowSeconds = 4.0
|
||||||
analyzeVideoMAEClipStrideSeconds = 2.0
|
analyzeVideoMAEClipStrideSeconds = 2.0
|
||||||
analyzeVideoMAEMinScore = 0.34
|
analyzeVideoMAEMinScore = 0.34
|
||||||
analyzeVideoMAERequestBatchSize = 48
|
analyzeVideoMAERequestBatchSize = 8
|
||||||
|
analyzeVideoMAEMaxClips = 180
|
||||||
|
analyzeVideoMAERequestTimeout = 75 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type analyzeVideoMAEClipReqItem struct {
|
type analyzeVideoMAEClipReqItem struct {
|
||||||
@ -112,6 +114,31 @@ func buildAnalyzeVideoMAEClips(
|
|||||||
return clips
|
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(
|
func predictVideoMAEPositionClipsForAnalyze(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
clips []analyzeVideoMAEClipReqItem,
|
clips []analyzeVideoMAEClipReqItem,
|
||||||
@ -145,54 +172,66 @@ func predictVideoMAEPositionClipsForAnalyze(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsed, err := func() (analyzeVideoMAEClipPredictResp, error) {
|
||||||
|
var parsed analyzeVideoMAEClipPredictResp
|
||||||
|
|
||||||
|
reqCtx, cancel := context.WithTimeout(ctx, analyzeVideoMAERequestTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(
|
||||||
ctx,
|
reqCtx,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
analyzeAIServerURL()+"/predict-position-clips",
|
analyzeAIServerURL()+"/predict-position-clips",
|
||||||
bytes.NewReader(body),
|
bytes.NewReader(body),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return parsed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
addAIServerAuth(req)
|
addAIServerAuth(req)
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 180 * time.Second,
|
Timeout: analyzeVideoMAERequestTimeout + 5*time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
return nil, ctxErr
|
return parsed, ctxErr
|
||||||
}
|
}
|
||||||
return nil, err
|
return parsed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawBody, readErr := io.ReadAll(res.Body)
|
rawBody, readErr := io.ReadAll(res.Body)
|
||||||
|
statusCode := res.StatusCode
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
return nil, ctxErr
|
return parsed, ctxErr
|
||||||
}
|
}
|
||||||
return nil, readErr
|
return parsed, readErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsed analyzeVideoMAEClipPredictResp
|
|
||||||
if err := json.Unmarshal(rawBody, &parsed); err != nil {
|
if err := json.Unmarshal(rawBody, &parsed); err != nil {
|
||||||
if ctxErr := ctx.Err(); ctxErr != nil {
|
if ctxErr := ctx.Err(); ctxErr != nil {
|
||||||
return nil, ctxErr
|
return parsed, ctxErr
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("AI server VideoMAE JSON ungueltig: HTTP %d: %s", res.StatusCode, strings.TrimSpace(string(rawBody)))
|
return parsed, fmt.Errorf("AI server VideoMAE JSON ungueltig: HTTP %d: %s", statusCode, strings.TrimSpace(string(rawBody)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode < 200 || res.StatusCode >= 300 || !parsed.OK {
|
if statusCode < 200 || statusCode >= 300 || !parsed.OK {
|
||||||
msg := strings.TrimSpace(parsed.Error)
|
msg := strings.TrimSpace(parsed.Error)
|
||||||
if msg == "" {
|
if msg == "" {
|
||||||
msg = fmt.Sprintf("AI server VideoMAE HTTP %d", res.StatusCode)
|
msg = fmt.Sprintf("AI server VideoMAE HTTP %d", statusCode)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%s", msg)
|
return parsed, fmt.Errorf("%s", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed, nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !parsed.Available {
|
if !parsed.Available {
|
||||||
@ -227,6 +266,14 @@ func applyVideoMAEPositionClipsForAnalyze(
|
|||||||
if len(clips) == 0 {
|
if len(clips) == 0 {
|
||||||
return highlightHits, positionEvidence
|
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 {
|
if onProgress != nil {
|
||||||
onProgress(0, len(clips))
|
onProgress(0, len(clips))
|
||||||
|
|||||||
@ -196,7 +196,10 @@ func ensureAnalyzeAllGoalsForVideoCtxForce(
|
|||||||
AnalyzedAtUnix: time.Now().Unix(),
|
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)
|
publishAnalysisError(analyzeStartedAtMs, analyzeFile, "Speichern fehlgeschlagen", err)
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -210,7 +213,7 @@ func ensureAnalyzeAllGoalsForVideoCtxForce(
|
|||||||
|
|
||||||
// Direkt nach erfolgreicher Analyse löschen.
|
// Direkt nach erfolgreicher Analyse löschen.
|
||||||
// Wichtig: hier rating übergeben, nicht nil.
|
// Wichtig: hier rating übergeben, nicht nil.
|
||||||
autoDeleteLowRatedDownloadAfterAnalysis(actx, videoPath, rating)
|
autoDeleteLowRatedDownloadAfterAnalysis(persistCtx, videoPath, rating)
|
||||||
publishAnalyzePersistProgress(analyzeStartedAtMs, analyzeFile, 1, "")
|
publishAnalyzePersistProgress(analyzeStartedAtMs, analyzeFile, 1, "")
|
||||||
publishAnalysisFinished(analyzeStartedAtMs, analyzeProgressTotal, analyzeFile, "Analyse abgeschlossen")
|
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 {
|
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{
|
ai := &aiAnalysisMeta{
|
||||||
Goal: "highlights",
|
Goal: "highlights",
|
||||||
Mode: "video",
|
Mode: "video",
|
||||||
|
Completed: true,
|
||||||
Hits: hits,
|
Hits: hits,
|
||||||
Segments: segments,
|
Segments: segments,
|
||||||
Rating: rating,
|
Rating: rating,
|
||||||
AnalyzedAtUnix: time.Now().Unix(),
|
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)
|
publishAnalysisError(analyzeStartedAtMs2, analyzeFile2, "Speichern fehlgeschlagen", werr)
|
||||||
return out, nil
|
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
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(stepErr, context.Canceled) || errors.Is(stepErr, context.DeadlineExceeded) {
|
if errors.Is(stepErr, context.Canceled) {
|
||||||
// globaler Task wurde beendet
|
// globaler Task wurde beendet
|
||||||
if ctx.Err() != nil && !isAssetsJobSkipRequested(jobID, it.name) {
|
if ctx.Err() != nil && !isAssetsJobSkipRequested(jobID, it.name) {
|
||||||
finishFileControl()
|
finishFileControl()
|
||||||
@ -693,6 +693,18 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Datei wurde evtl. während des Verarbeitungsschritts gelöscht/verschoben
|
// 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) {
|
if isAssetsJobSkipRequested(jobID, it.name) || !fileExistsNonEmpty(it.path) {
|
||||||
return true, false
|
return true, false
|
||||||
}
|
}
|
||||||
@ -939,10 +951,12 @@ func runGenerateMissingAssetsJob(jobID string, ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if aerr != nil {
|
if aerr != nil {
|
||||||
|
appLogln("❌ assets task analyse fehlgeschlagen für", it.path+":", aerr)
|
||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
||||||
} else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady {
|
} else if assetsTaskTruthForVideo(id, it.path).AnalyzeReady {
|
||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse")
|
publishAssetsTaskPhase(it.name, "enrich", "analyze", "done", "Analyse")
|
||||||
} else {
|
} else {
|
||||||
|
appLogln("❌ assets task analyse fehlt nach Lauf:", it.path)
|
||||||
publishAssetsTaskPhase(it.name, "enrich", "analyze", "error", "Analyse fehlgeschlagen")
|
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) {
|
func TestTrainingResolveVideoMAEModelUsesConfigDir(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modelDir := filepath.Join(root, "videomae", "model")
|
modelDir := filepath.Join(root, "videomae", "model")
|
||||||
|
|||||||
@ -3189,7 +3189,7 @@ export default function App() {
|
|||||||
new CustomEvent('finished-downloads:postwork', {
|
new CustomEvent('finished-downloads:postwork', {
|
||||||
detail: {
|
detail: {
|
||||||
file,
|
file,
|
||||||
assetId: file.replace(/\.[^.]+$/, ''),
|
assetId: stripHotPrefix(file.replace(/\.[^.]+$/, '')),
|
||||||
queue: 'enrich',
|
queue: 'enrich',
|
||||||
phase: 'analyze',
|
phase: 'analyze',
|
||||||
state,
|
state,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user