main
1// Package config defines the configuration for the upvs CLI.
2package config
3
4import (
5 "errors"
6 "fmt"
7 "net/url"
8 "os"
9 "strings"
10 "time"
11)
12
13// Default values for configuration.
14const (
15 DefaultWorkers = 4
16 DefaultRetries = 3
17 DefaultTargetSecs = 600 // 10 minutes
18 DefaultFPS = 30
19 DefaultCRF = 23
20 DefaultPreset = "medium"
21 DefaultMinSpeedMult = 1.0
22 DefaultMaxSpeedMult = 2000.0
23)
24
25// Config holds all configuration for a pipeline run.
26type Config struct {
27 // Connection settings
28 Host string
29 Username string
30 Password string
31 TLSInsecure bool
32 DirectAPI bool // Use /api instead of /proxy/protect/api
33
34 // Target selection
35 Camera string
36 Date time.Time
37
38 // Output settings
39 OutDir string
40 Verbose bool
41
42 // Fetch settings
43 Workers int
44 Retries int
45
46 // Render settings
47 TargetSecs int
48 FPS int
49 CRF int
50 Preset string
51 MinSpeedMult float64
52 MaxSpeedMult float64
53}
54
55// New creates a Config with default values.
56func New() *Config {
57 return &Config{
58 Workers: DefaultWorkers,
59 Retries: DefaultRetries,
60 TargetSecs: DefaultTargetSecs,
61 FPS: DefaultFPS,
62 CRF: DefaultCRF,
63 Preset: DefaultPreset,
64 MinSpeedMult: DefaultMinSpeedMult,
65 MaxSpeedMult: DefaultMaxSpeedMult,
66 }
67}
68
69// Sentinel errors for configuration validation.
70var (
71 ErrMissingHost = errors.New("host is required")
72 ErrMissingCredentials = errors.New("username and password are required")
73 ErrMissingCamera = errors.New("camera is required")
74 ErrMissingDate = errors.New("date is required")
75 ErrMissingOutDir = errors.New("out directory is required")
76 ErrInvalidHost = errors.New("invalid host URL")
77 ErrInvalidDate = errors.New("invalid date format (expected YYYY-MM-DD)")
78)
79
80// Validate checks that all required fields are set and valid.
81func (c *Config) Validate() error {
82 if c.Host == "" {
83 return ErrMissingHost
84 }
85 _, err := url.Parse(c.Host)
86 if err != nil {
87 return fmt.Errorf("%w: %v", ErrInvalidHost, err)
88 }
89 if c.Username == "" || c.Password == "" {
90 return ErrMissingCredentials
91 }
92 if c.Camera == "" {
93 return ErrMissingCamera
94 }
95 if c.Date.IsZero() {
96 return ErrMissingDate
97 }
98 if c.OutDir == "" {
99 return ErrMissingOutDir
100 }
101 return nil
102}
103
104// LoadPasswordFromFile reads the password from a file if Password is empty.
105func (c *Config) LoadPasswordFromFile(path string) error {
106 if path == "" {
107 return nil
108 }
109 data, err := os.ReadFile(path)
110 if err != nil {
111 return fmt.Errorf("reading password-file: %w", err)
112 }
113 c.Password = strings.TrimSpace(string(data))
114 return nil
115}
116
117// ParseDate parses a date string in YYYY-MM-DD format.
118func ParseDate(s string) (time.Time, error) {
119 t, err := time.Parse("2006-01-02", s)
120 if err != nil {
121 return time.Time{}, ErrInvalidDate
122 }
123 return t, nil
124}
125
126// DateString returns the date formatted as YYYY-MM-DD.
127func (c *Config) DateString() string {
128 return c.Date.Format("2006-01-02")
129}