main
1package ffmpeg
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "strconv"
8)
9
10// RenderConfig holds FFmpeg rendering parameters.
11type RenderConfig struct {
12 ConcatFile string
13 OutputPath string
14 SpeedFactor float64
15 FPS int
16 CRF int
17 Preset string
18}
19
20// Render executes FFmpeg to create the timelapse.
21func Render(ctx context.Context, cfg RenderConfig) error {
22 // Build setpts filter for speed adjustment
23 // setpts=PTS/N makes video N times faster
24 setptsFilter := fmt.Sprintf("setpts=PTS/%s", strconv.FormatFloat(cfg.SpeedFactor, 'f', 6, 64))
25
26 args := []string{
27 "-f", "concat",
28 "-safe", "0",
29 "-i", cfg.ConcatFile,
30 "-vf", setptsFilter,
31 "-r", strconv.Itoa(cfg.FPS),
32 "-c:v", "libx264",
33 "-crf", strconv.Itoa(cfg.CRF),
34 "-preset", cfg.Preset,
35 "-pix_fmt", "yuv420p",
36 "-an", // No audio
37 "-y", // Overwrite output
38 cfg.OutputPath,
39 }
40
41 cmd := exec.CommandContext(ctx, "ffmpeg", args...)
42
43 output, err := cmd.CombinedOutput()
44 if err != nil {
45 return fmt.Errorf("ffmpeg failed: %w\noutput: %s", err, string(output))
46 }
47
48 return nil
49}
50
51// Probe checks if a video file is valid using ffprobe.
52func Probe(ctx context.Context, path string) error {
53 args := []string{
54 "-v", "error",
55 "-show_entries", "format=duration",
56 "-of", "default=noprint_wrappers=1:nokey=1",
57 path,
58 }
59
60 cmd := exec.CommandContext(ctx, "ffprobe", args...)
61 output, err := cmd.CombinedOutput()
62 if err != nil {
63 return fmt.Errorf("ffprobe failed: %w\noutput: %s", err, string(output))
64 }
65
66 return nil
67}
68
69// GetDuration returns the duration of a video file in seconds.
70func GetDuration(ctx context.Context, path string) (float64, error) {
71 args := []string{
72 "-v", "error",
73 "-show_entries", "format=duration",
74 "-of", "default=noprint_wrappers=1:nokey=1",
75 path,
76 }
77
78 cmd := exec.CommandContext(ctx, "ffprobe", args...)
79 output, err := cmd.CombinedOutput()
80 if err != nil {
81 return 0, fmt.Errorf("ffprobe failed: %w", err)
82 }
83
84 duration, err := strconv.ParseFloat(string(output[:len(output)-1]), 64)
85 if err != nil {
86 return 0, fmt.Errorf("parsing duration: %w", err)
87 }
88
89 return duration, nil
90}