Skip to content

Commit

Permalink
feat: webp support
Browse files Browse the repository at this point in the history
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
  • Loading branch information
caarlos0 committed Jul 28, 2023
1 parent d42375a commit 8993af4
Show file tree
Hide file tree
Showing 14 changed files with 80 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
**/*.gif filter=lfs diff=lfs merge=lfs -text
**/*.mp4 filter=lfs diff=lfs merge=lfs -text
**/*.webm filter=lfs diff=lfs merge=lfs -text
**/*.webp filter=lfs diff=lfs merge=lfs -text
**/*.png filter=lfs diff=lfs merge=lfs -text
*.tape linguist-language=elixir
themes*.json linguist-generated
Expand Down
56 changes: 27 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vim demo.tape
```

Tape files consist of a series of [commands](#vhs-command-reference). The commands are
instructions for VHS to perform on its virtual terminal. For a list of all
instructions for VHS to perform on its virtual terminal. For a list of all
possible commands see [the command reference](#vhs-command-reference).

```elixir
Expand Down Expand Up @@ -125,8 +125,8 @@ docker run --rm -v $PWD:/vhs ghcr.io/charmbracelet/vhs <cassette>.tape

Or, download it:

* [Packages][releases] are available in Debian and RPM formats
* [Binaries][releases] are available for Linux, macOS, and Windows
- [Packages][releases] are available in Debian and RPM formats
- [Binaries][releases] are available for Linux, macOS, and Windows

Or, just install it with `go`:

Expand Down Expand Up @@ -159,7 +159,7 @@ vhs cassette.tape
VHS allows you to publish your GIFs to our servers for easy sharing with your
friends and colleagues. Specify which file you want to share, then use the
`publish` sub-command to host it on `vhs.charm.sh`. The output will provide you
with links to share your GIF via browser, HTML, and Markdown.
with links to share your GIF via browser, HTML, and Markdown.

```bash
vhs publish demo.gif
Expand All @@ -180,16 +180,15 @@ vhs serve
<details>
<summary>Configuration Options</summary>

* `VHS_PORT`: The port to listen on (`1976`)
* `VHS_HOST`: The host to listen on (`localhost`)
* `VHS_GID`: The Group ID to run the server as (current user's GID)
* `VHS_UID`: The User ID to run the server as (current user's UID)
* `VHS_KEY_PATH`: The path to the SSH key to use (`.ssh/vhs_ed25519`)
* `VHS_AUTHORIZED_KEYS_PATH`: The path to the authorized keys file (empty, publicly accessible)
- `VHS_PORT`: The port to listen on (`1976`)
- `VHS_HOST`: The host to listen on (`localhost`)
- `VHS_GID`: The Group ID to run the server as (current user's GID)
- `VHS_UID`: The User ID to run the server as (current user's UID)
- `VHS_KEY_PATH`: The path to the SSH key to use (`.ssh/vhs_ed25519`)
- `VHS_AUTHORIZED_KEYS_PATH`: The path to the authorized keys file (empty, publicly accessible)

</details>


Then, simply access VHS from a different machine via `ssh`:

```sh
Expand All @@ -203,16 +202,16 @@ ssh vhs.example.com < demo.tape > demo.gif
There are a few basic types of VHS commands:

* [`Output <path>`](#output): specify file output
* [`Require <program>`](#require): specify required programs for tape file
* [`Set <Setting> Value`](#settings): set recording settings
* [`Type "<characters>"`](#type): emulate typing
* [`Left`](#arrow-keys) [`Right`](#arrow-keys) [`Up`](#arrow-keys) [`Down`](#arrow-keys): arrow keys
* [`Backspace`](#backspace) [`Enter`](#enter) [`Tab`](#tab) [`Space`](#space): special keys
* [`Ctrl[+Alt][+Shift]+<char>`](#ctrl): press control + key and/or modifier
* [`Sleep <time>`](#sleep): wait for a certain amount of time
* [`Hide`](#hide): hide commands from output
* [`Show`](#show): stop hiding commands from output
- [`Output <path>`](#output): specify file output
- [`Require <program>`](#require): specify required programs for tape file
- [`Set <Setting> Value`](#settings): set recording settings
- [`Type "<characters>"`](#type): emulate typing
- [`Left`](#arrow-keys) [`Right`](#arrow-keys) [`Up`](#arrow-keys) [`Down`](#arrow-keys): arrow keys
- [`Backspace`](#backspace) [`Enter`](#enter) [`Tab`](#tab) [`Space`](#space): special keys
- [`Ctrl[+Alt][+Shift]+<char>`](#ctrl): press control + key and/or modifier
- [`Sleep <time>`](#sleep): wait for a certain amount of time
- [`Hide`](#hide): hide commands from output
- [`Show`](#show): stop hiding commands from output

### Output

Expand All @@ -224,6 +223,7 @@ will render them to the respective locations.
Output out.gif
Output out.mp4
Output out.webm
Output out.webp
Output frames/ # a directory of frames as a PNG sequence
```

Expand Down Expand Up @@ -322,6 +322,7 @@ Set the height of the terminal with the `Set Height` command.
```elixir
Set Height 1000
```

<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/height.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/height.gif">
Expand Down Expand Up @@ -409,14 +410,12 @@ command.
Set Padding 0
```


<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://stuff.charm.sh/vhs/examples/padding.gif">
<source media="(prefers-color-scheme: light)" srcset="https://stuff.charm.sh/vhs/examples/padding.gif">
<img width="600" alt="Example of setting the padding" src="https://stuff.charm.sh/vhs/examples/padding.gif">
</picture>


#### Set Margin

Set the margin (in pixels) of the video with the `Set Margin` command.
Expand Down Expand Up @@ -463,7 +462,6 @@ Set BorderRadius 10
<img width="400" alt="Example of setting the margin" src="https://vhs.charm.sh/vhs-4nYoy6IsUKmleJANG7N1BH.gif">
</picture>


#### Set Framerate

Set the rate at which VHS captures frames with the `Set Framerate` command.
Expand Down Expand Up @@ -694,7 +692,7 @@ Type "You will see this being typed."
<img width="600" alt="Example of typing something while hidden" src="https://stuff.charm.sh/vhs/examples/hide.gif">
</picture>

***
---

## Continuous Integration

Expand Down Expand Up @@ -724,15 +722,15 @@ It works great with Neovim, Emacs, and so on!

We’d love to hear your thoughts on this project. Feel free to drop us a note!

* [Twitter](https://twitter.com/charmcli)
* [The Fediverse](https://mastodon.social/@charmcli)
* [Discord](https://charm.sh/chat)
- [Twitter](https://twitter.com/charmcli)
- [The Fediverse](https://mastodon.social/@charmcli)
- [Discord](https://charm.sh/chat)

## License

[MIT](https://github.com/charmbracelet/vhs/raw/main/LICENSE)

***
---

Part of [Charm](https://charm.sh).

Expand Down
2 changes: 2 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ func ExecuteOutput(c Command, v *VHS) {
v.Options.Video.Output.Frames = c.Args
case ".webm":
v.Options.Video.Output.WebM = c.Args
case ".webp":
v.Options.Video.Output.WebP = c.Args
default:
v.Options.Video.Output.GIF = c.Args
}
Expand Down
3 changes: 2 additions & 1 deletion examples/demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
# Output <path>.webp Create a WebP output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
Expand Down Expand Up @@ -63,4 +64,4 @@ Set Height 600
Type "echo 'Welcome to VHS!'" Sleep 500ms Enter

Sleep 5s
Sleep 1s
1 change: 1 addition & 0 deletions examples/fixtures/all.tape
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Output examples/fixtures/all.gif
Output examples/fixtures/all.mp4
Output examples/fixtures/all.webm
Output examples/fixtures/all.webp

# Settings:
Set Shell "fish"
Expand Down
1 change: 1 addition & 0 deletions examples/neofetch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Output examples/neofetch/neofetch.gif
Output examples/neofetch/neofetch.mp4
Output examples/neofetch/neofetch.webm
Output examples/neofetch/neofetch.webp
Output examples/neofetch/frames/
Set TypingSpeed 75ms
Expand Down
1 change: 1 addition & 0 deletions examples/neofetch/neofetch.tape
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Output examples/neofetch/neofetch.gif
Output examples/neofetch/neofetch.mp4
Output examples/neofetch/neofetch.webm
Output examples/neofetch/neofetch.webp
Output examples/neofetch/frames/

Set TypingSpeed 75ms
Expand Down
2 changes: 2 additions & 0 deletions lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ func TestLexTapeFile(t *testing.T) {
{STRING, "examples/fixtures/all.mp4"},
{OUTPUT, "Output"},
{STRING, "examples/fixtures/all.webm"},
{OUTPUT, "Output"},
{STRING, "examples/fixtures/all.webp"},
{COMMENT, " Settings:"},
{SET, "Set"},
{SHELL, "Shell"},
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ var (
v.Options.Video.Output.GIF = output
} else if strings.HasSuffix(output, webm) {
v.Options.Video.Output.WebM = output
} else if strings.HasSuffix(output, webp) {
v.Options.Video.Output.WebP = output
} else if strings.HasSuffix(output, mp4) {
v.Options.Video.Output.MP4 = output
}
Expand Down
4 changes: 2 additions & 2 deletions man.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ A tape file is a script made up of commands describing what actions to perform i
The following is a list of all possible commands in VHS:
* %Output% <path>.(gif|webm|mp4)
* %Output% <path>.(gif|webm|webp|mp4)
* %Require% <program>
* %Set% <setting> <value>
* %Sleep% <time>
Expand All @@ -48,7 +48,7 @@ The following is a list of all possible commands in VHS:
`

manOutput = `The Output command instructs VHS where to save the output of the recording.
File names with the extension %.gif%, %.webm%, %.mp4% will have the respective file types.
File names with the extension %.gif%, %.webm%, %.webp%, %.mp4% will have the respective file types.
`

manSettings = `The Set command allows VHS to adjust settings in the terminal, such as fonts, dimensions, and themes.
Expand Down
1 change: 1 addition & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func TestParseTapeFile(t *testing.T) {
{Type: OUTPUT, Options: ".gif", Args: "examples/fixtures/all.gif"},
{Type: OUTPUT, Options: ".mp4", Args: "examples/fixtures/all.mp4"},
{Type: OUTPUT, Options: ".webm", Args: "examples/fixtures/all.webm"},
{Type: OUTPUT, Options: ".webp", Args: "examples/fixtures/all.webp"},
{Type: SET, Options: "Shell", Args: "fish"},
{Type: SET, Options: "FontSize", Args: "22"},
{Type: SET, Options: "FontFamily", Args: "DejaVu Sans Mono"},
Expand Down
6 changes: 5 additions & 1 deletion serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,25 @@ var serveCmd = &cobra.Command{
tempFile := filepath.Join(os.TempDir(), fmt.Sprintf("vhs-%d", rand))
defer func() { _ = os.Remove(tempFile) }()
errs := Evaluate(s.Context(), b.String(), s.Stderr(), func(v *VHS) {
var gif, mp4, webm string
var gif, mp4, webm, webp string
switch {
case v.Options.Video.Output.MP4 != "":
tempFile += mp4
mp4 = tempFile
case v.Options.Video.Output.WebM != "":
tempFile += webm
webm = tempFile
case v.Options.Video.Output.WebP != "":
tempFile += webp
webp = tempFile
default:
tempFile += gif
gif = tempFile
}
v.Options.Video.Output.GIF = gif
v.Options.Video.Output.MP4 = mp4
v.Options.Video.Output.WebM = webm
v.Options.Video.Output.WebP = webp
})

if len(errs) > 0 {
Expand Down
1 change: 1 addition & 0 deletions vhs.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func (vhs *VHS) Render() error {
cmds = append(cmds, MakeGIF(vhs.Options.Video))
cmds = append(cmds, MakeMP4(vhs.Options.Video))
cmds = append(cmds, MakeWebM(vhs.Options.Video))
cmds = append(cmds, MakeWebP(vhs.Options.Video))

for _, cmd := range cmds {
if cmd == nil {
Expand Down
35 changes: 32 additions & 3 deletions video.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package vhs video.go spawns the ffmpeg process to convert the frames,
// collected by go-rod's screenshots into the input folder, to a GIF, WebM,
// MP4.
// WebP, MP4.
//
// MakeGIF takes several options to modify the behaviour of the ffmpeg process,
// which can be configured through the Set command.
Expand All @@ -25,6 +25,7 @@ const (
const (
mp4 = ".mp4"
webm = ".webm"
webp = ".webp"
gif = ".gif"
)

Expand All @@ -43,6 +44,7 @@ func randomDir() string {
type VideoOutputs struct {
GIF string
WebM string
WebP string
MP4 string
Frames string
}
Expand Down Expand Up @@ -91,7 +93,7 @@ func DefaultVideoOptions() VideoOptions {
Framerate: defaultFramerate,
Input: randomDir(),
MaxColors: defaultMaxColors,
Output: VideoOutputs{GIF: "", WebM: "", MP4: "", Frames: ""},
Output: VideoOutputs{GIF: "", WebM: "", WebP:"", MP4: "", Frames: ""},

Check failure on line 96 in video.go

View workflow job for this annotation

GitHub Actions / lint

File is not `goimports`-ed (goimports)

Check failure on line 96 in video.go

View workflow job for this annotation

GitHub Actions / lint

File is not `goimports`-ed (goimports)
Width: defaultWidth,
Height: defaultHeight,
Padding: defaultPadding,
Expand Down Expand Up @@ -307,6 +309,16 @@ func buildFFopts(opts VideoOptions, targetFile string) []string {
"-crf", "30",
"-b:v", "0",
)
} else if filepath.Ext(targetFile) == webp {
args = append(args,
"-vcodec", "libwebp",
"-pix_fmt", "yuv444p",
"-loop", "0",
"-lossless","0",
"-an",
"-crf", "30",
"-b:v", "0",
)
} else if filepath.Ext(targetFile) == mp4 {
args = append(args,
"-vcodec", "libx264",
Expand All @@ -329,7 +341,7 @@ func buildFFopts(opts VideoOptions, targetFile string) []string {
func MakeGIF(opts VideoOptions) *exec.Cmd {
targetFile := opts.Output.GIF

if opts.Output.GIF == "" && opts.Output.WebM == "" && opts.Output.MP4 == "" {
if opts.Output.GIF == "" && opts.Output.WebM == "" && opts.Output.WebP == "" && opts.Output.MP4 == "" {
targetFile = "out.gif"
} else if opts.Output.GIF == "" {
return nil
Expand Down Expand Up @@ -376,3 +388,20 @@ func MakeMP4(opts VideoOptions) *exec.Cmd {
buildFFopts(opts, opts.Output.MP4)...,
)
}


// MakeWebP takes a list of images (as frames) and converts them to an WebP.
func MakeWebP(opts VideoOptions) *exec.Cmd {
if opts.Output.WebP == "" {
return nil
}

log.Println(GrayStyle.Render("Creating " + opts.Output.WebP + "..."))
ensureDir(opts.Output.WebP)

//nolint:gosec
return exec.Command(
"ffmpeg",
buildFFopts(opts, opts.Output.WebP)...,
)
}

0 comments on commit 8993af4

Please sign in to comment.