Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

VHS : générer des GIFs de terminal (CLI)

20 min de lecture
Améliorer cette page

VHS, créé par Charmbracelet, génère des GIFs et vidéos de sessions terminal à partir d'un fichier déclaratif .tape. Plutôt que de filmer votre écran, vous décrivez le scénario (commandes tapées, sorties, pauses, style), et VHS le rejoue pour produire un rendu propre, reproductible et versionnable.

Ce guide s'adresse à toute personne qui documente des outils en ligne de commande. Vous allez installer VHS et ses prérequis, écrire votre premier fichier .tape, générer un GIF, puis automatiser la régénération en CI.

  • Installer VHS et ses prérequis (ffmpeg, ttyd).
  • Écrire un fichier .tape (instructions Type, Enter, Sleep).
  • Générer un GIF, une vidéo MP4 ou WebM.
  • Personnaliser le rendu avec les directives Set.
  • Automatiser la régénération des GIFs en CI/CD.

Cette section commence par l'installation des prérequis système, puis explique plusieurs méthodes d'installation de VHS. L'objectif est que même un débutant en ligne de commande puisse exécuter une première tape en quelques minutes.

Avant d'installer vhs, assurez-vous que les composants indispensables sont disponibles :

  • ffmpeg : encode les images successives en GIF / MP4 / WebM
  • ttyd : fournit le terminal web que VHS pilote pour composer les images
  • Un shell : bash / zsh / fish (bash par défaut sur la plupart des distributions)

Les deux binaires sont obligatoires et doivent être présents dans le PATH, la documentation amont le précise explicitement. Sur Debian, Ubuntu, Fedora et RHEL, aucun paquet officiel de ttyd n'est tiré par l'installation de VHS : c'est une étape manuelle.

Commandes d'installation selon l'OS :

Fenêtre de terminal
# Debian / Ubuntu
sudo apt update && sudo apt install -y ffmpeg curl git
# Fedora / Rocky / Alma / RHEL
sudo dnf install -y ffmpeg curl git
# Arch Linux
sudo pacman -Syu --needed ffmpeg git curl
# macOS (Homebrew)
brew install ffmpeg
# Windows (PowerShell) - installation ffmpeg via winget
winget install Gyan.FFmpeg

Vérifiez ensuite :

Fenêtre de terminal
# Version (utile pour diagnostiquer un build manquant de codecs)
ffmpeg -version

Si ffmpeg est absent, l'encodage échouera (GIF/vidéo non produits).

Le projet ttyd publie des binaires statiques directement, sans archive : le fichier ttyd.x86_64 est l'exécutable lui-même. Chaque release fournit aussi un fichier SHA256SUMS, ce qui permet de vérifier le téléchargement avant de l'installer dans le PATH. Cette vérification n'est pas une formalité : elle détecte aussi bien une altération volontaire qu'un téléchargement tronqué par un proxy d'entreprise.

Fenêtre de terminal
# Exemple pour Linux x86_64
curl -fsSLO https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64
curl -fsSLO https://github.com/tsl0922/ttyd/releases/latest/download/SHA256SUMS
# Ne vérifier que la ligne correspondant à notre architecture
grep ' ttyd.x86_64$' SHA256SUMS | sha256sum --check -
sudo install -m 0755 ttyd.x86_64 /usr/local/bin/ttyd
ttyd --version

La commande sha256sum --check doit répondre ttyd.x86_64: Réussi (OK en anglais). Toute autre réponse impose de supprimer le fichier et de recommencer. ttyd --version affiche ensuite la version et le hash du commit, par exemple ttyd version 1.7.7-40e79c7.

Choisissez UNE méthode (la plus simple pour vous). Les résultats sont équivalents.

Homebrew gère la mise à jour et la désinstallation, c'est la voie la plus courte sur macOS. Sur Linux, elle suppose un Homebrew déjà en place, ce qui est rarement le cas sur un serveur.

Fenêtre de terminal
brew install vhs
vhs --version

C'est la méthode à privilégier sur un poste ou un serveur de long terme : le dépôt Charm est signé, les mises à jour arrivent avec celles du système, et la vérification de signature est faite par le gestionnaire de paquets à chaque installation.

Fenêtre de terminal
# Debian / Ubuntu : ajout du dépôt Charm signé
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" \
| sudo tee /etc/apt/sources.list.d/charm.list
sudo apt update && sudo apt install -y vhs ffmpeg

Sur Arch Linux, le paquet est directement dans les dépôts officiels :

Fenêtre de terminal
sudo pacman -Syu vhs

Dans les deux cas, ttyd reste à installer séparément (voir plus haut).

Les deux gestionnaires installent le binaire officiel et l'ajoutent au PATH. Comme sur Linux, ffmpeg et ttyd doivent être présents pour que le rendu aboutisse.

Fenêtre de terminal
# Winget
winget install charmbracelet.vhs
# Ou Scoop
scoop install vhs

La méthode la plus universelle, sans gestionnaire de paquets. Chaque release publie un fichier checksums.txt : téléchargez l'archive et ce fichier, puis vérifiez l'empreinte avant d'installer quoi que ce soit. Sans cette étape, vous placez dans votre PATH un binaire dont rien ne garantit l'origine.

Fenêtre de terminal
VHS_VERSION=0.11.0
BASE=https://github.com/charmbracelet/vhs/releases/download/v${VHS_VERSION}
curl -fsSLO "${BASE}/vhs_${VHS_VERSION}_Linux_x86_64.tar.gz"
curl -fsSLO "${BASE}/checksums.txt"
grep " vhs_${VHS_VERSION}_Linux_x86_64.tar.gz\$" checksums.txt | sha256sum --check -
tar -xf "vhs_${VHS_VERSION}_Linux_x86_64.tar.gz"
sudo install -m 0755 "vhs_${VHS_VERSION}_Linux_x86_64/vhs" /usr/local/bin/vhs
vhs --version

Adaptez VHS_VERSION et le nom de l'archive à votre OS et à votre architecture ; la liste complète est sur la page des releases du projet.

La méthode la plus flexible si vous avez Go installé.

Fenêtre de terminal
go install github.com/charmbracelet/vhs@latest
Fenêtre de terminal
# Afficher la version installée
vhs --version
# Générer une tape de test minimale et produire un GIF
cat > quick.tape <<'EOF'
Output quick.gif
Set TypingSpeed 120ms
Type "echo VHS OK"
Enter
Sleep 1s
EOF
vhs quick.tape && ls -lh quick.gif

Si quick.gif est créé sans erreur, l'environnement est opérationnel. Votre fichier .gif devrait ressembler à ceci :

Aperçu du GIF

Les quatre pannes ci-dessous couvrent la quasi-totalité des premiers essais. Elles ont un point commun : VHS échoue tard, au moment de l'encodage, et le message d'erreur ne pointe pas toujours la vraie cause. La directive Require est la parade la plus efficace, elle fait échouer la tape avant le rendu si une dépendance manque.

  • GIF vide ou non généré : vérifier ffmpeg -version
  • Erreur de dépendance : ajouter la directive Require ffmpeg en tête de la tape pour échouer tôt
  • Thème non appliqué : s'assurer que la directive Set Theme est placée avant toute commande Type
  • Commande lente imprévisible : remplacer pause fixe par Wait après la commande

Le cœur de VHS : un fichier texte avec extension .tape décrivant la session. Chaque ligne est une instruction.

Structure générale :

Output ./demo.gif
Set FontSize 16
Set TypingSpeed 120ms
Require ffmpeg
Type "echo 'Bonjour VHS'"
Enter
Sleep 500ms
Type "uname -s"
Enter
  • Output <chemin> : définit le fichier de sortie. Format selon extension (.gif, .mp4, .webm, .png -> séquence)
  • Set <Paramètre> <Valeur> : personnalisation (police, thème, vitesse, curseur, dimensions)
  • Type "texte" : simule la frappe humaine d'une chaîne
  • Enter : envoie la touche Entrée
  • Sleep <durée> : pause (ex: 500ms, 2s)
  • Wait : suspend la frappe jusqu'à ce qu'un motif apparaisse à l'écran. Sans argument, le motif par défaut est le retour du prompt (/>$/) et le délai maximal est de 15 secondes
  • Require <binaire> : vérifie la présence d'une dépendance avant exécution
  • Hide / Show : masquer temporairement une portion pour ne pas l'afficher
  • Source <fichier.tape> : inclure un sous-script (modularisation)

La première règle n'est pas une préférence de style, c'est une contrainte du langage : à l'exception de TypingSpeed, toute directive Set placée après une commande (Type, Enter, Sleep...) est silencieusement ignorée. C'est l'explication la plus fréquente d'un thème ou d'une taille de police qui « ne prend pas ». Même logique pour Require, qui doit précéder toute commande pour jouer son rôle de garde-fou.

  • Grouper les Set en début de fichier
  • Toujours définir explicitement Output
  • Utiliser Require pour des dépendances critiques (ffmpeg, git)
  • Préférer Sleep courts + Wait plutôt que des pauses longues arbitraires
  • Versionner le .tape, ignorer les rendus (.gitignore)

Si vous préférez partir d'une capture réelle, VHS propose :

Fenêtre de terminal
# Enregistrer une session interactive et générer un fichier .tape
vhs record > session.tape

Vous tapez vos commandes normalement, puis terminez avec Ctrl+D. Le fichier généré peut être nettoyé pour :

  • Retirer les fautes de frappe
  • Ajuster les temporisations (Sleep)
  • Ajouter des Set cohérents (police, thème, vitesse de frappe)

Cette approche hybride (record puis édition) combine vitesse et précision.

Une fois la tape prête :

Fenêtre de terminal
# Génère le média selon l'extension Output
vhs demo.tape

Formats supportés :

  • .gif : diffusion web / README
  • .mp4 : taille réduite, réseaux sociaux
  • .webm : optimisation web moderne
  • .png (dossier) : spritesheet potentielle / post‑traitement

Changez simplement l'extension dans l'instruction Output pour obtenir un autre format, sans modifier le reste du scénario.

Résumé des principaux réglages supportés (à placer avant toute commande non‑Set / non‑Output).

  • Set Shell <bash|zsh|fish|...> : shell utilisé pour exécuter les commandes
  • Set Width <px> / Set Height <px> : dimensions du terminal rendu
  • Set FontSize <px> : taille des caractères
  • Set FontFamily "Nom" : police monospacée (doit être installée côté système ou embarquée)
  • Set LetterSpacing <px> : espacement horizontal entre lettres
  • Set LineHeight <ratio> : interligne (ex: 1.4)
  • Set TypingSpeed <durée> : délai entre frappes (ex: 120ms, 0.1 secondes)
  • Set Theme { JSON } ou Set Theme "Nom" : thème 16 couleurs + foreground/background/cursor
  • Set Padding <px> : espace interne autour de la zone terminal
  • Set Margin <px> : marge externe du cadre vidéo
  • Set MarginFill "#RRGGBB" : couleur de fond de la marge
  • Set WindowBar <Colorful|ColorfulRight|Rings|RingsRight|None> : barre décorative
  • Set BorderRadius <px> : arrondi des coins (souvent avec marge + couleur)
  • Set Framerate <fps> : fréquence de capture (ex: 30, 60)
  • Set PlaybackSpeed <facteur> : accélération ou ralentissement de lecture finale (ex: 1.2)
  • Set LoopOffset <frames|%> : frame de début de boucle GIF (thumbnails plus dynamiques)
  • Set CursorBlink <true|false> : clignotement du curseur
  • Set WindowBarSize <px> : hauteur de la barre décorative
  • Set WaitTimeout <durée> / Set WaitPattern <regex> : réglages par défaut de la commande Wait

Astuce : regrouper tous les Set et Require en tête de fichier pour éviter qu'ils ne soient ignorés.

Ce bloc constitue un point de départ raisonnable pour de la documentation : 1000 x 600 pixels reste lisible une fois l'image réduite dans une page web, et une frappe à 120 ms laisse le temps de lire la commande. Copiez-le en tête de vos tapes, puis n'ajustez qu'une valeur à la fois, le rendu final se juge à l'oeil sur le GIF produit.

Set Width 1000
Set Height 600
Set FontSize 18
Set Theme Dracula
Set TypingSpeed 120ms
Set CursorBlink false

vhs themes liste les thèmes reconnus par votre version : passer un nom inconnu à Set Theme fait échouer la tape.

VHS propose un hébergement intégré :

Fenêtre de terminal
# Génération + upload immédiat si --publish est présent
vhs demo.tape --publish

Ou après génération :

Fenêtre de terminal
# Publication d'un fichier existant
vhs publish demo.gif

La commande retourne une URL partageable (https://vhs.charm.sh/...). Avantages :

  • Pas besoin de committer des fichiers lourds
  • Rendu optimisé (hébergement Charm)
  • Mise à jour automatisable (CI)

Alternative : héberger dans un dépôt GitHub (assets release) ou un CDN (Cloudflare R2 / S3) pour contrôle complet.

Automatiser la régénération garantit que vos GIFs restent synchronisés avec le code source. Exemple de workflow minimal :

.github/workflows/vhs.yml
name: Generate VHS assets
on:
push:
paths:
- "demo/*.tape"
workflow_dispatch:
permissions: {}
jobs:
build:
runs-on: ubuntu-24.04
permissions:
contents: read
env:
VHS_VERSION: "0.11.0"
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
- name: Install ttyd
run: |
curl -fsSLO https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64
curl -fsSLO https://github.com/tsl0922/ttyd/releases/latest/download/SHA256SUMS
grep ' ttyd.x86_64$' SHA256SUMS | sha256sum --check -
sudo install -m 0755 ttyd.x86_64 /usr/local/bin/ttyd
- name: Install VHS
run: |
base="https://github.com/charmbracelet/vhs/releases/download/v${VHS_VERSION}"
archive="vhs_${VHS_VERSION}_Linux_x86_64.tar.gz"
curl -fsSLO "${base}/${archive}"
curl -fsSLO "${base}/checksums.txt"
grep " ${archive}\$" checksums.txt | sha256sum --check -
tar -xf "${archive}"
sudo install -m 0755 "vhs_${VHS_VERSION}_Linux_x86_64/vhs" /usr/local/bin/vhs
- name: Generate GIFs
run: |
for f in demo/*.tape; do vhs "$f"; done
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vhs-gifs
path: demo/*.gif

Trois choix de ce workflow méritent une explication. Les actions sont épinglées par empreinte SHA parce qu'une étiquette de version peut être redéplacée vers un autre commit, ce qui suffirait à exécuter du code arbitraire sur le runner. permissions: {} retire tous les droits par défaut et le job ne récupère que contents: read, dont il a réellement besoin. Enfin, chaque binaire téléchargé est vérifié par son empreinte SHA-256 publiée par le projet, plutôt que d'être passé directement à tar : sans cette étape, une release compromise se propagerait silencieusement dans vos images.

Bonnes pratiques CI :

  • Ne pas stocker les GIFs générés dans Git (poids, diffs inutiles)
  • Versionner uniquement les .tape
  • Publier via --publish ou artifacts compressés
  • Ajouter un job périodique (schedule) si dépendances externes évoluent
  • Définir une vitesse de frappe modérée : Set TypingSpeed 90ms (lisible)
  • Utiliser Wait après des commandes réseau ou build
  • Réduire le prompt pour la lisibilité : VHS n'a pas de directive dédiée, on passe par un segment masqué (Hide / Type "export PS1='$ '" / Enter / Show)
  • Masquer secrets / tokens : segment sous Hide ... Show
  • Externaliser des blocs récurrents (Source ./fragments/init.tape)
  • Documenter les choix dans des commentaires en amont du fichier .tape (lignes commençant par #)
  • Contrôler la police : préférer une police monospacée lisible (JetBrains Mono, Fira Code)
  • Vérifier le poids final des GIFs (optimisation possible via gifsicle)

Réduire la taille des GIFs améliore les performances de chargement dans la documentation.

  • Limiter les dimensions : éviter > 1200px de largeur si non nécessaire
  • Réduire la durée : supprimer les pauses excessives (Sleep > 2s)
  • Ajuster Framerate : 30 fps suffisent souvent, 24 fps acceptable pour commandes lentes
  • Palette optimisée : certains thèmes très colorés augmentent la palette → préférer un thème sobre
  • Utiliser PlaybackSpeed : 1.1 ou 1.2 fluidifie sans rendre illisible
  • Post‑traitement : gifsicle -O3 demo.gif -o demo-opt.gif
  • Format alternatif : proposer aussi .webm ou .mp4 (nettement plus léger) et garder GIF comme fallback
  • Découper : plusieurs courtes démonstrations plutôt qu'un long scénario monolithique

Exemple pipeline local :

Fenêtre de terminal
# Optimisation après génération GIF
vhs demo.tape
gifsicle -O3 demo.gif -o demo-opt.gif

Le préfixe numérique n'est pas décoratif : il fixe l'ordre de lecture pour un humain et permet à une boucle for f in demo/*.tape de traiter les scénarios dans un ordre stable. Le sous-répertoire fragments/ regroupe ce qui n'est jamais rendu seul, uniquement inclus par Source.

demo/
00-intro.tape
10-installation.tape
20-flux-base.tape
fragments/
palette.tape
header.tape

Source insère le contenu d'une autre tape à l'endroit de l'appel. L'intérêt est de factoriser les réglages : la charte visuelle vit dans un seul fichier, et changer la police de toutes les démonstrations devient une modification à un seul endroit. Attention à l'ordre, le fichier inclus contient des Set, il doit donc rester avant la première commande Type.

# Fichier principal
Output ./workflow.gif
Source ./fragments/header.tape
Source ./fragments/palette.tape
Type "make build"
Enter
Wait
Type "./bin/outillage --help"
Enter
  • Éviter de montrer des chemins utilisateurs privés (/home/user) → utiliser comptes génériques
  • Purger l'historique de commandes sensibles
  • Indiquer dans la doc la commande pour régénérer : vhs chemin.tape
  • Ajouter un badge ou une phrase : "Ce GIF est généré automatiquement via CI"
  1. VHS génère des GIFs de terminal à partir d'un fichier déclaratif .tape, reproductible et versionnable.
  2. Les prérequis sont ffmpeg (encodage) et ttyd (terminal rendu).
  3. Une tape enchaîne des instructions : Output, Set, Type, Enter, Sleep, Require.
  4. vhs validate vérifie la syntaxe avant de lancer un rendu potentiellement long.
  5. En CI, on versionne les .tape et on régénère les GIFs à chaque changement.
  • Documentation et dépôt officiel : VHS
  • Publication hébergée (option --publish) : article Charm
  • Intégration CI : Action GitHub officielle vhs-action

Ces quatre questions sont celles qui reviennent avant l'adoption : ce qu'est réellement VHS, la commande minimale pour produire un GIF, la liste exacte des prérequis, et la faisabilité en CI/CD. Les réponses reprennent l'essentiel du guide sous une forme condensée.

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn