main
Raw Download raw file
 1package pipeline
 2
 3import (
 4	"context"
 5	"fmt"
 6	"log/slog"
 7
 8	"github.com/crash/upvs/internal/ffmpeg"
 9	"github.com/crash/upvs/internal/manifest"
10	"github.com/crash/upvs/internal/output"
11)
12
13// ManifestResult holds the output of the manifest phase.
14type ManifestResult struct {
15	ValidClips    int
16	TotalDuration float64 // seconds
17}
18
19// BuildManifest filters valid clips and generates the FFmpeg concat file.
20func BuildManifest(ctx context.Context, layout *output.Layout) (*ManifestResult, error) {
21	clipIndex, err := manifest.ReadClipIndex(layout.ClipIndexPath())
22	if err != nil {
23		return nil, fmt.Errorf("reading clip index: %w", err)
24	}
25
26	slog.Info("building manifest", slog.Int("total_clips", len(clipIndex.Clips)))
27
28	var validPaths []string
29	var totalDurationMs int64
30
31	for _, clip := range clipIndex.Clips {
32		if clip.Status != manifest.StatusComplete {
33			continue
34		}
35
36		// Verify file exists and is valid
37		if !fileExists(clip.FilePath) {
38			slog.Warn("clip file missing",
39				slog.String("event_id", clip.EventID),
40				slog.String("path", clip.FilePath))
41			continue
42		}
43
44		// Optionally verify with ffprobe
45		err := ffmpeg.Probe(ctx, clip.FilePath)
46		if err != nil {
47			slog.Warn("clip invalid",
48				slog.String("event_id", clip.EventID),
49				slog.String("error", err.Error()))
50			clip.Status = manifest.StatusFailed
51			clip.Error = "invalid video file"
52			continue
53		}
54
55		validPaths = append(validPaths, clip.FilePath)
56		totalDurationMs += clip.DurationMs
57	}
58
59	if len(validPaths) == 0 {
60		return nil, fmt.Errorf("no valid clips to render")
61	}
62
63	err = ffmpeg.GenerateConcatFile(layout.ConcatTxtPath(), validPaths)
64	if err != nil {
65		return nil, err
66	}
67
68	// Update clip index with any status changes
69	err = clipIndex.Write(layout.ClipIndexPath())
70	if err != nil {
71		return nil, err
72	}
73
74	totalDuration := float64(totalDurationMs) / 1000.0
75
76	slog.Info("manifest built",
77		slog.Int("valid_clips", len(validPaths)),
78		slog.Float64("total_duration_secs", totalDuration),
79		slog.String("concat_path", layout.ConcatTxtPath()))
80
81	return &ManifestResult{
82		ValidClips:    len(validPaths),
83		TotalDuration: totalDuration,
84	}, nil
85}