diff --git a/cmd/democtl/gif/gif.go b/cmd/democtl/gif/gif.go new file mode 100644 index 0000000..ce557a2 --- /dev/null +++ b/cmd/democtl/gif/gif.go @@ -0,0 +1,111 @@ +package gif + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/wzshiming/democtl/pkg/renderer" + "github.com/wzshiming/democtl/pkg/renderer/video" +) + +func NewCommand() *cobra.Command { + var ( + input string + output string + ) + cmd := &cobra.Command{ + Use: "gif", + Short: "Convert terminal session to gif", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if input == "" { + return fmt.Errorf("no input file specified") + } + err := run(input, output) + if err != nil { + return err + } + return nil + }, + } + cmd.Flags().StringVarP(&input, "input", "i", input, "input filename") + cmd.Flags().StringVarP(&output, "output", "o", output, "output directory") + return cmd +} + +func run(inputPath, outputPath string) error { + input, err := os.OpenFile(inputPath, os.O_RDONLY, 0) + if err != nil { + return err + } + defer input.Close() + + if outputPath == "" { + inputExt := filepath.Ext(inputPath) + outputPath = inputPath[:len(inputPath)-len(inputExt)] + ".gif" + } + + rawDir := outputPath + ".raw" + + err = os.MkdirAll(rawDir, 0755) + if err != nil { + return err + } + + err = renderer.Render(context.Background(), video.NewCanvas(rawDir, false), input) + if err != nil { + return err + } + + stat, err := os.Stat(outputPath) + if err == nil { + if stat.IsDir() { + return fmt.Errorf("output directory already exists") + } else { + os.Remove(outputPath) + } + } + + ffmpegPath, err := exec.LookPath("ffmpeg") + if err != nil { + fmt.Printf(`# Next step: run the following command to generate the video +############################### +ffmpeg \ + -f concat \ + -safe 0 \ + -i %q \ + %q +rm -rf %q +############################### +`, filepath.Join(rawDir, "frames.txt"), outputPath, rawDir) + return nil + } + + args := []string{ + "-f", "concat", + "-safe", "0", + "-i", filepath.Join(rawDir, "frames.txt"), + outputPath, + } + + info, err := exec.Command(ffmpegPath, args...).CombinedOutput() + if err != nil { + return fmt.Errorf("ffmpeg failed: %w:%s", err, string(info)) + } + + _, err = os.Stat(outputPath) + if err != nil { + return err + } + + err = os.RemoveAll(rawDir) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/democtl/main.go b/cmd/democtl/main.go index 9af9d0b..f05e1cd 100644 --- a/cmd/democtl/main.go +++ b/cmd/democtl/main.go @@ -5,6 +5,7 @@ import ( "os" "github.com/spf13/cobra" + "github.com/wzshiming/democtl/cmd/democtl/gif" "github.com/wzshiming/democtl/cmd/democtl/mp4" "github.com/wzshiming/democtl/cmd/democtl/play" "github.com/wzshiming/democtl/cmd/democtl/record" @@ -25,6 +26,7 @@ func main() { play.NewCommand(), svg.NewCommand(), mp4.NewCommand(), + gif.NewCommand(), ) err := cmd.Execute()