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