main
1package ffmpeg
2
3import (
4 "fmt"
5 "io"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "syscall"
10 "time"
11)
12
13type Encoder struct {
14 *exec.Cmd
15 stdin io.WriteCloser
16}
17
18func ffmpegArgs(interval time.Duration) []string {
19 framerate := intervalToFramerate(interval)
20 return []string{
21 "-y", // Overwrite output files without asking.
22 "-loglevel", "info", // logs
23 "-f", "mjpeg", // jpeg source images
24 "-framerate", framerate, // framerate
25 "-i", "pipe:0", // input from pipe
26 "-vf", "setpts=PTS/120,scale=in_range=jpeg:out_range=tv,format=yuv420p", // compression 120x speed
27 "-r", "60",
28 "-c:v", "libx264", "-preset", "slow", "-crf", "25", // H.264 encoding settings
29 "-fps_mode", "cfr", //
30 "-movflags", "+faststart", // webplayback
31 "timelapse.mp4", // output file
32 }
33}
34
35func intervalToFramerate(interval time.Duration) string {
36 if interval < 1 {
37 return "1"
38 }
39 return fmt.Sprintf("%d/%d", time.Second, interval)
40}
41
42func NewEncoder(interval time.Duration) (*Encoder, error) {
43 enc := new(Encoder)
44
45 cmd := exec.Command("ffmpeg", ffmpegArgs(interval)...)
46 stdin, err := cmd.StdinPipe()
47 if err != nil {
48 err := fmt.Errorf("creating stdin pipe: %w", err)
49 return enc, err
50 }
51
52 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
53 enc.stdin = stdin
54 enc.Cmd = cmd
55
56 logDir := "log"
57 logPath := filepath.Join(logDir, "timelapse.log")
58
59 err = os.MkdirAll(logDir, 0o750)
60 if err != nil {
61 err := fmt.Errorf("creating log dir")
62 return enc, err
63 }
64
65 logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o640)
66 if err != nil {
67 err := fmt.Errorf("creating log file")
68 return enc, err
69 }
70 cmd.Stderr = logFile
71 cmd.Stdout = logFile
72
73 err = enc.Start()
74 if err != nil {
75 err := fmt.Errorf("starting encoder")
76 _ = logFile.Close()
77 return enc, err
78 }
79 return enc, nil
80}
81
82func (enc *Encoder) Write(p []byte) (int, error) {
83 return enc.stdin.Write(p)
84}
85
86func (enc *Encoder) Close() error {
87 return enc.stdin.Close()
88}