main
1// Package output manages the output directory structure.
2package output
3
4import (
5 "fmt"
6 "os"
7 "path/filepath"
8
9 "github.com/crash/upvs/internal/sanitize"
10)
11
12// Layout manages paths within the output directory.
13type Layout struct {
14 root string
15 camera string
16 date string
17}
18
19// NewLayout creates a Layout for the given output directory, camera, and date.
20func NewLayout(outDir, camera, date string) *Layout {
21 return &Layout{
22 root: outDir,
23 camera: sanitize.Filename(camera),
24 date: date,
25 }
26}
27
28// EnsureDirs creates all required directories in the output structure.
29func (l *Layout) EnsureDirs() error {
30 dirs := []string{
31 l.MetadataDir(),
32 l.ClipsDir(),
33 l.ManifestsDir(),
34 l.TimelapseDir(),
35 l.LogsDir(),
36 }
37 for _, dir := range dirs {
38 err := os.MkdirAll(dir, 0755)
39 if err != nil {
40 return fmt.Errorf("creating directory %s: %w", dir, err)
41 }
42 }
43 return nil
44}
45
46// Root returns the root output directory.
47func (l *Layout) Root() string {
48 return l.root
49}
50
51// MetadataDir returns the path to the metadata directory.
52func (l *Layout) MetadataDir() string {
53 return filepath.Join(l.root, "metadata")
54}
55
56// CameraJSONPath returns the path to camera.json.
57func (l *Layout) CameraJSONPath() string {
58 return filepath.Join(l.MetadataDir(), "camera.json")
59}
60
61// DayJSONPath returns the path to day.json.
62func (l *Layout) DayJSONPath() string {
63 return filepath.Join(l.MetadataDir(), "day.json")
64}
65
66// ClipsDir returns the path to the clips directory for this camera/date.
67func (l *Layout) ClipsDir() string {
68 return filepath.Join(l.root, "clips", l.camera, l.date)
69}
70
71// ClipPath returns the path for a specific clip file.
72func (l *Layout) ClipPath(startMs, endMs int64, eventID string) string {
73 filename := fmt.Sprintf("clip_%d_%d_%s.mp4", startMs, endMs, sanitize.Filename(eventID))
74 return filepath.Join(l.ClipsDir(), filename)
75}
76
77// ClipPartialPath returns the path for a partial (in-progress) clip download.
78func (l *Layout) ClipPartialPath(startMs, endMs int64, eventID string) string {
79 return l.ClipPath(startMs, endMs, eventID) + ".partial"
80}
81
82// ManifestsDir returns the path to the manifests directory.
83func (l *Layout) ManifestsDir() string {
84 return filepath.Join(l.root, "manifests")
85}
86
87// ClipIndexPath returns the path to clip_index.json.
88func (l *Layout) ClipIndexPath() string {
89 return filepath.Join(l.ManifestsDir(), "clip_index.json")
90}
91
92// ConcatTxtPath returns the path to concat.txt for FFmpeg.
93func (l *Layout) ConcatTxtPath() string {
94 return filepath.Join(l.ManifestsDir(), "concat.txt")
95}
96
97// TimelapseDir returns the path to the timelapse output directory.
98func (l *Layout) TimelapseDir() string {
99 return filepath.Join(l.root, "timelapse")
100}
101
102// TimelapsePath returns the path to the final timelapse video.
103func (l *Layout) TimelapsePath() string {
104 filename := fmt.Sprintf("%s_%s_timelapse.mp4", l.camera, l.date)
105 return filepath.Join(l.TimelapseDir(), filename)
106}
107
108// LogsDir returns the path to the logs directory.
109func (l *Layout) LogsDir() string {
110 return filepath.Join(l.root, "logs")
111}
112
113// RunLogPath returns the path to run.log.
114func (l *Layout) RunLogPath() string {
115 return filepath.Join(l.LogsDir(), "run.log")
116}