screen-recording

Par github · awesome-copilot

Créez des démos animées au format GIF annoté et des enregistrements d'écran pour les pull requests et la documentation. Couvre la capture d'images, le timing, la création de GIF avec imageio et les workflows d'annotation image par image.

npx skills add https://github.com/github/awesome-copilot --skill screen-recording

Enregistrement d'écran

Créez des démos animées en GIF montrant une fonctionnalité ou un workflow en action — avec annotations, timing variable et cadence appropriée. Utile pour les descriptions de PR, la documentation et les notes de version.

Quand utiliser cette compétence

Utilisez cette compétence quand vous avez besoin de :

  • Enregistrer une interaction UI multi-étapes en GIF animé
  • Créer une démo montrant un comportement avant/après
  • Construire des walkthroughs annotés pour la documentation ou les notes de version
  • Montrer la reproduction ou la correction d'un bug en action

Prérequis

pip install playwright Pillow imageio numpy scipy mss -q
playwright install chromium

Workflow principal

1. Capturer les frames

Utilisez Playwright pour progresser dans l'interaction et capturer chaque frame :

from playwright.async_api import async_playwright

async def record_frames(url, steps, width=1400, height=900):
    """
    steps: liste de dicts avec 'action' (callable async prenant page)
           et 'name' (nom du fichier du frame)
    """
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page(viewport={"width": width, "height": height})
        await page.goto(url, wait_until="networkidle")

        for step in steps:
            if step.get("action"):
                await step["action"](page)
                await page.wait_for_timeout(step.get("wait", 500))
            await page.screenshot(path=step["name"])

        await browser.close()

2. Assembler le GIF avec imageio

Utilisez imageio, pas PIL, pour l'écriture de GIF — l'encodeur GIF de PIL fusionne les frames visuellement similaires, ce qui tue les animations.

import imageio.v3 as iio
from PIL import Image
import numpy as np

frames = []
durations = []

for frame_path, duration_ms in frame_list:
    img = Image.open(frame_path)
    frames.append(np.array(img))
    durations.append(duration_ms)

iio.imwrite("demo.gif", frames, duration=durations, loop=0)

3. Timing variable des frames

Un timing uniforme rend tout soit trop rapide soit trop lent. Utilisez des durées variables :

Phase Durée Pourquoi
Action rapide (saisie, clic) 100ms Semble naturel, maintient l'énergie
Pause après action 600-800ms Laisser au spectateur le temps de traiter ce qui s'est passé
Message principal/final 500ms+ Le point clé a besoin de temps pour s'enregistrer

4. Annoter les frames

Appliquez des annotations à des frames spécifiques en utilisant la compétence image-annotations :

from PIL import Image, ImageDraw, ImageFont

def annotate_frame(frame_path, annotations, out_path):
    img = Image.open(frame_path)
    draw = ImageDraw.Draw(img)

    for ann in annotations:
        # Appliquer annotation (rect, flèche, label, etc.)
        pass

    img.save(out_path)

5. Annotations avec fondu

Pour une apparition fluide des annotations :

def apply_fade(base_frame, annotation_layer, alpha):
    """Fusionner annotation sur frame à alpha donné (0.0 à 1.0)"""
    blended = Image.blend(
        base_frame.convert("RGBA"),
        annotation_layer.convert("RGBA"),
        alpha
    )
    return blended.convert("RGB")

# Pop-in 2-frame à 10fps : 50% puis 100%
faded_frames = [
    apply_fade(base, annotations, 0.5),  # frame 1: opacité 50%
    apply_fade(base, annotations, 1.0),  # frame 2: opacité 100%
]

À 10fps, utilisez 2 frames de fondu (0,2s au total). À 30fps, utilisez 3-4 frames. Les courbes d'easing ont mauvaise allure en basse résolution FPS — un simple pop-in est plus percutant et lisible.

Construire comme un script

La logique d'annotation devient complexe au-delà des démos triviales. Écrivez un script dédié (par ex. annotate_gif.py) avec des fonctions au lieu du code inline. Vous itérerez sur le timing et le positionnement.

Tester les animations

Toujours tester isolément d'abord — ne reconstruisez pas la démo complète pour tester un ajustement de fondu :

# Petit GIF de test : 10 frames bruts → frames de fondu → 15 frames de maintien
# Ajouter un overlay compteur de frames pour le débogage :
draw.text((10, height - 30), f"F{i}/{total} a={alpha:.0%} FADE",
          fill="white", font=small_font)

Enregistrement d'écran de bureau (mss)

Pour enregistrer des apps de bureau, des terminaux ou n'importe quoi en dehors d'un navigateur. Utilise mss pour une capture d'écran rapide.

import mss
from PIL import Image
import time

def record_gif(output_path, region=None, duration=5, fps=8):
    """Enregistrer région d'écran en GIF. region = {left, top, width, height} ou None pour écran complet."""
    with mss.mss() as sct:
        if region is None:
            region = sct.monitors[1]  # moniteur principal

        frames = []
        t_end = time.time() + duration
        while time.time() < t_end:
            t0 = time.time()
            shot = sct.grab(region)
            frames.append(Image.frombytes('RGB', shot.size, shot.rgb))
            time.sleep(max(0, 1 / fps - (time.time() - t0)))

    frames[0].save(output_path, save_all=True, append_images=frames[1:],
                   duration=int(1000 / fps), loop=0, optimize=True)
    return len(frames)

record_gif('demo.gif', region={'left': 0, 'top': 0, 'width': 800, 'height': 500}, duration=3)

Testé : 3s à 8fps → 24 frames, ~31KB. Gardez fps ≤ 10 pour des tailles de fichier raisonnables.

Note : PIL.save(save_all=True) fonctionne pour les enregistrements simples mais fusionne les frames visuellement similaires. Pour les GIFs annotés avec effets de fondu, utilisez imageio.v3.imwrite à la place.

Combiner avec capture de fenêtre

# Trouver rect de fenêtre, puis l'enregistrer comme GIF
# Réutiliser find_window() de la compétence ui-screenshots
import ctypes
from ctypes import c_int, Structure, byref, windll

class RECT(Structure):
    _fields_ = [('left', c_int), ('top', c_int), ('right', c_int), ('bottom', c_int)]

hwnd = find_window('My App')[0][0]
rect = RECT()
windll.user32.GetWindowRect(hwnd, byref(rect))
region = {'left': rect.left, 'top': rect.top,
          'width': rect.right - rect.left, 'height': rect.bottom - rect.top}
record_gif('app-demo.gif', region=region, duration=5, fps=8)

Détection de clusters basée sur les différences

Trouvez par programme les régions modifiées entre frames pour décider quoi annoter :

import numpy as np
from scipy import ndimage

def find_changed_clusters(frame_a, frame_b, threshold=30, min_pixels=300, dilate=5):
    """Trouver les boîtes englobantes des régions modifiées entre deux frames."""
    diff = np.abs(frame_b.astype(float) - frame_a.astype(float)).max(axis=2)
    mask = diff > threshold
    dilated = ndimage.binary_dilation(mask, iterations=dilate)
    labeled, n = ndimage.label(dilated)
    clusters = []
    for i in range(1, n + 1):
        ys, xs = np.where(labeled == i)
        if len(ys) < min_pixels:
            continue
        clusters.append((xs.min(), ys.min(), xs.max(), ys.max(), len(ys)))
    return sorted(clusters, key=lambda c: -c[4])  # plus grand d'abord

Compatibilité des formats

Format Aperçu VS Code GitHub Navigateur
GIF ✅ Anime
WebP ⚠️ Statique uniquement
MP4 ❌ Cassé ⚠️

GIF est le seul format animé universellement supporté sur l'aperçu VS Code, le markdown GitHub et les navigateurs.

Directives

  1. Saisie → pause → annotation — durant l'action rapide, NE PAS afficher d'annotation. D'abord faire la pause, puis annoter
  2. Le message principal a la plus grosse police — 64pt+ pour le point clé, 38pt pour les détails
  3. La palette GIF ne tue PAS les dégradés — 20 étapes alpha distinctes survivent à une palette de 256 couleurs
  4. Minimum 10fps pour la saisie/interaction — plus bas semble saccadé
  5. Construire itérativement — corriger d'abord la séquence de frames, ajouter les annotations ensuite, affiner le timing en dernier

Limitations

  • GIF est limité à 256 couleurs par frame — acceptable pour les captures d'écran UI, peut montrer de la posterisation sur contenu photographique
  • Les grands GIFs (50+ frames en haute résolution) peuvent faire plusieurs MB — envisagez de recadrer la zone pertinente
  • Pas de support audio en GIF — utilisez MP4 pour les démos narratives (mais perdez le support d'aperçu VS Code)

Skills similaires