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