main
Raw Download raw file
  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}