DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to record a narrated product demo without screen recording software

How to Record a Narrated Product Demo Without Screen Recording Software

Loom requires someone to sit down and record. Scribe generates step-by-step screenshots but not video. Screen recorders can't run headlessly in CI. None of them let you regenerate the demo automatically when your UI changes.

Here's how to define your demo as code — navigate to a URL, click through the product, add AI-narrated commentary — and generate an MP4 you can embed anywhere. Runs headlessly, regenerates on every deploy.

Basic narrated demo

const res = await fetch("https://pagebolt.dev/api/v1/video", {
  method: "POST",
  headers: {
    "x-api-key": process.env.PAGEBOLT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    steps: [
      {
        action: "navigate",
        url: "https://yourapp.com",
        note: "Welcome to YourApp — the fastest way to capture web content.",
      },
      {
        action: "click",
        selector: "#get-started-btn",
        note: "Click Get Started to create a free account.",
      },
      {
        action: "wait_for",
        selector: "#dashboard",
      },
      {
        action: "screenshot",
        note: "Your dashboard loads instantly — no setup required.",
      },
      {
        action: "navigate",
        url: "https://yourapp.com/docs",
        note: "The API is a single POST request. Here's how it works.",
      },
    ],
    audioGuide: {
      enabled: true,
      script:
        "Welcome to YourApp. {{1}} {{2}} {{3}} Your dashboard is ready. {{4}} The API takes one call. That's it.",
    },
    pace: "cinematic",
    format: "mp4",
  }),
});

const video = Buffer.from(await res.arrayBuffer());
require("fs").writeFileSync("demo.mp4", video);
Enter fullscreen mode Exit fullscreen mode

Full product walkthrough with styled browser frame

const demoScript = {
  steps: [
    {
      action: "navigate",
      url: "https://yourapp.com",
      note: "YourApp — one API for screenshots, PDFs, and video.",
    },
    {
      action: "scroll",
      y: 400,
    },
    {
      action: "navigate",
      url: "https://yourapp.com/pricing",
      note: "Simple pricing. 100 free requests per month — no credit card.",
    },
    {
      action: "click",
      selector: "[data-plan='pro'] button",
      note: "Click any plan to get your API key instantly.",
    },
    {
      action: "wait_for",
      selector: "#signup-form",
    },
    {
      action: "fill",
      selector: "#email",
      value: "demo@example.com",
      note: "Enter your email and you're ready to go.",
    },
    {
      action: "navigate",
      url: "https://yourapp.com/docs",
      note: "The docs show every parameter. Here's a screenshot request.",
    },
    {
      action: "scroll",
      y: 600,
    },
  ],
  audioGuide: {
    enabled: true,
    voice: "nova",
    script: [
      "Welcome to YourApp.",
      "{{1}}",
      "{{2}}",
      "{{3}}",
      "{{4}}",
      "{{5}}",
      "{{6}}",
      "The full API reference is right here.",
      "{{7}}",
    ].join(" "),
  },
  pace: "dramatic",
  format: "mp4",
  frame: {
    enabled: true,
    style: "macos",
  },
  background: {
    enabled: true,
    type: "gradient",
    gradient: "ocean",
  },
  cursor: {
    style: "spotlight",
    persist: true,
  },
};
Enter fullscreen mode Exit fullscreen mode

Available voices

// Azure voices — natural, clear narration
const VOICES = [
  "ava",        // warm female
  "andrew",     // authoritative male
  "emma",       // crisp British female
  "brian",      // casual male
  "aria",       // conversational female
  "jenny",      // clear American female
];

// OpenAI voices — slightly more expressive
const OPENAI_VOICES = ["nova", "alloy", "echo", "fable", "onyx", "shimmer"];
Enter fullscreen mode Exit fullscreen mode

Auto-regenerate on deploy

# .github/workflows/demo-video.yml
name: Regenerate demo video

on:
  push:
    branches: [main]
    paths:
      - "src/pages/**"
      - "src/components/**"

jobs:
  record-demo:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Record demo video
        env:
          PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
        run: |
          node -e "
          const fs = require('fs');
          const steps = require('./scripts/demo-steps.js');

          fetch('https://pagebolt.dev/api/v1/video', {
            method: 'POST',
            headers: {'x-api-key': process.env.PAGEBOLT_API_KEY, 'Content-Type': 'application/json'},
            body: JSON.stringify(steps)
          })
          .then(r => r.arrayBuffer())
          .then(b => { fs.writeFileSync('demo.mp4', Buffer.from(b)); console.log('Video saved'); })
          .catch(console.error);
          "

      - name: Upload to release assets
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Upload to latest release or as a workflow artifact
          gh release upload "$(gh release list --limit 1 --json tagName -q '.[0].tagName')" \
            demo.mp4 --clobber || true

      - name: Upload as artifact
        uses: actions/upload-artifact@v4
        with:
          name: demo-video-${{ github.sha }}
          path: demo.mp4
          retention-days: 30
Enter fullscreen mode Exit fullscreen mode

Externalize demo steps for easy editing

// scripts/demo-steps.js
module.exports = {
  steps: [
    { action: "navigate", url: "https://yourapp.com", note: "Landing page" },
    { action: "click", selector: "#cta", note: "Primary CTA" },
    { action: "wait_for", selector: "#dashboard" },
    { action: "screenshot", note: "Dashboard loaded" },
  ],
  audioGuide: {
    enabled: true,
    voice: "nova",
    script: "{{1}} Click to get started. {{2}} {{3}} Your workspace is ready.",
  },
  pace: "dramatic",
  format: "mp4",
  frame: { enabled: true, style: "macos" },
  background: { enabled: true, type: "gradient", gradient: "ocean" },
  cursor: { style: "highlight", persist: true },
};
Enter fullscreen mode Exit fullscreen mode

Editing the demo means editing a JS object — no recording session, no video editor. The next CI run regenerates the MP4 automatically.

Embed in your README

[![Product Demo](https://img.shields.io/badge/Watch-Demo-blue)](https://github.com/yourorg/yourrepo/releases/latest/download/demo.mp4)
Enter fullscreen mode Exit fullscreen mode

Or embed directly in a README using GitHub's video support:

https://github.com/yourorg/yourrepo/assets/demo.mp4
Enter fullscreen mode Exit fullscreen mode

No screen recorder. No Loom subscription. No re-recording when the UI changes.


Try it free — 100 requests/month, no credit card. → Get started in 2 minutes

Top comments (0)