-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathgif.go
More file actions
117 lines (100 loc) · 2.84 KB
/
Copy pathgif.go
File metadata and controls
117 lines (100 loc) · 2.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func generateGIF(outputPath, theme, dir string, lines []string, cardCount int) error {
vhsPath, err := exec.LookPath("vhs")
if err != nil {
return fmt.Errorf("vhs is required for GIF output. Install: brew install vhs")
}
selfPath, err := os.Executable()
if err != nil {
return fmt.Errorf("cannot find executable path: %w", err)
}
selfPath, _ = filepath.Abs(selfPath)
var duration int
switch theme {
case "matrix":
duration = cardCount*5 + 2
default:
duration = len(lines)/8 + 3
if duration < 10 {
duration = 10
}
}
ffmpegPath, _ := exec.LookPath("ffmpeg")
absOutput, err := filepath.Abs(outputPath)
if err != nil {
return fmt.Errorf("invalid output path: %w", err)
}
var vhsOutput string
if ffmpegPath != "" {
vhsOutput = absOutput + ".mp4"
} else {
vhsOutput = absOutput
}
var tape strings.Builder
tape.WriteString(fmt.Sprintf("Output \"%s\"\n", vhsOutput))
tape.WriteString("Set Width 960\n")
tape.WriteString("Set Height 600\n")
tape.WriteString("Set Padding 0\n")
tape.WriteString("Set FontSize 16\n")
tape.WriteString("Set Theme \"Builtin Dark\"\n")
tape.WriteString("Set TypingSpeed 0\n")
cmdParts := []string{selfPath}
if theme != "default" {
cmdParts = append(cmdParts, "--theme", theme)
}
if dir != "" {
cmdParts = append(cmdParts, dir)
}
cmd := strings.Join(cmdParts, " ")
tape.WriteString(fmt.Sprintf("Type %q\n", cmd))
tape.WriteString("Enter\n")
tape.WriteString(fmt.Sprintf("Sleep %ds\n", duration))
tmpFile, err := os.CreateTemp("", "gitcredits-*.tape")
if err != nil {
return fmt.Errorf("cannot create temp file: %w", err)
}
tmpPath := tmpFile.Name()
defer os.Remove(tmpPath)
if _, err := tmpFile.WriteString(tape.String()); err != nil {
tmpFile.Close()
return fmt.Errorf("cannot write tape: %w", err)
}
tmpFile.Close()
vhsCmd := exec.Command(vhsPath, tmpPath)
if dir != "" {
vhsCmd.Dir = dir
} else {
cwd, _ := os.Getwd()
vhsCmd.Dir = cwd
}
vhsCmd.Env = append(os.Environ(), "TERM=xterm-256color")
vhsOut, err := vhsCmd.CombinedOutput()
if err != nil {
return fmt.Errorf("vhs failed: %s\n%s", err, string(vhsOut))
}
if ffmpegPath != "" {
defer os.Remove(vhsOutput)
palettePath := absOutput + ".palette.png"
defer os.Remove(palettePath)
p1 := exec.Command(ffmpegPath, "-y", "-i", vhsOutput,
"-vf", "fps=25,palettegen=max_colors=256:stats_mode=diff",
palettePath)
if out, err := p1.CombinedOutput(); err != nil {
return fmt.Errorf("ffmpeg palette failed: %s\n%s", err, string(out))
}
p2 := exec.Command(ffmpegPath, "-y", "-i", vhsOutput, "-i", palettePath,
"-filter_complex", "fps=25[v];[v][1:v]paletteuse=dither=floyd_steinberg",
absOutput)
if out, err := p2.CombinedOutput(); err != nil {
return fmt.Errorf("ffmpeg gif failed: %s\n%s", err, string(out))
}
}
return nil
}