main
1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "log/slog"
9 "net/url"
10 "os"
11 "os/signal"
12 "path/filepath"
13 "time"
14
15 "christmas-cam/internal/ffmpeg"
16 "christmas-cam/internal/speco"
17
18 "golang.org/x/sync/errgroup"
19)
20
21func main() {
22
23 interval := 1600 * time.Millisecond
24
25 snapshotURLString := "http://admin:PASSWORD@192.168.58.58/snapshot.JPG"
26 snapshotURL, err := url.Parse(snapshotURLString)
27 if err != nil {
28 panic(err)
29 }
30 camera := speco.NewCamera(snapshotURL, 4*time.Second)
31 slog.SetLogLoggerLevel(slog.LevelDebug)
32
33 var (
34 ctx, stop = signal.NotifyContext(
35 context.Background(),
36 os.Interrupt,
37 )
38 captureGroup, cctx = errgroup.WithContext(ctx)
39 encodingGroup = new(errgroup.Group)
40 )
41 defer stop()
42
43 enc, err := ffmpeg.NewEncoder(interval)
44 if err != nil {
45 panic(err)
46 }
47 encodingGroup.Go(func() error {
48 err := enc.Wait()
49 if err != nil {
50 slog.Error("ffmpeg exit",
51 slog.String("error", err.Error()))
52 }
53 return err
54 })
55
56 go func() {
57 <-ctx.Done() // ctrl+c
58 slog.Info("shutdown requested")
59 }()
60
61 captureGroup.Go(func() error {
62 slog.Info("starting image captures")
63 next := time.Now()
64 for {
65
66 select {
67 case <-cctx.Done():
68 slog.Info("closing ffmpeg stdin")
69 _ = enc.Close()
70 return nil // normal shutdown
71 default:
72 }
73
74 time.Sleep(time.Until(next))
75 next = next.Add(interval)
76 start := time.Now()
77
78 filename := fmt.Sprintf("%x.jpg", start.Unix())
79 filepath := filepath.Join("img", filename)
80 slog.Debug(filepath)
81 snapshotFile, err := os.Create(filepath)
82 if err != nil {
83 err = fmt.Errorf("creating snapshot file: %w", err)
84 return err
85 }
86
87 mw := io.MultiWriter(snapshotFile, enc)
88 _, err = camera.Snapshot(mw)
89 if err != nil {
90 slog.Error("writing image to file/pipe",
91 slog.String("error", err.Error()))
92 continue
93 }
94 err = snapshotFile.Close()
95 if err != nil {
96 err = fmt.Errorf("closing snapshot file: %w", err)
97 return err
98 }
99
100 elapsed := time.Since(start)
101 if elapsed > interval {
102 slog.Warn("snapshot overran interval",
103 slog.String("elapsed", elapsed.String()))
104 }
105 }
106 })
107
108 err = captureGroup.Wait()
109 if err != nil {
110 if errors.Is(err, context.Canceled) {
111 slog.Error("ctx done")
112 return
113 }
114 panic(err)
115 }
116 err = encodingGroup.Wait()
117 if err != nil {
118 if errors.Is(err, context.Canceled) {
119 slog.Error("ctx done")
120 return
121 }
122 panic(err)
123 }
124}