PM-Driven & Region-Aware REAL Demo Video Generation
🆕 V3.2 MANDATORY INTERACTIONS (ENFORCED):
- [MANDATORY #1] Recording Region Selection MUST prompt user for explicit confirmation
- [MANDATORY #2] Scenario Preview & Validation MUST wait for user approval before recording
- [MANDATORY #3] PRD-Driven Scenario Priority - If bmad-create-prd PRD exists, MUST use PRD scenarios
🆕 V3.1 NEW FEATURES:
- Recording Region Selection - Clip to specific UI area (e.g., content area only, exclude sidebar/header)
- PM-Driven Workflow Integration - Auto-detect features → Generate PRD → Decompose scenarios → Record per-scenario
- Scenario-Based Narration - TTS audio generated from PRD scenarios for professional demos
Rule: You MUST prompt user to select recording region before starting. ❌ FORBIDDEN: Using full-screen as default or silently using auto-detected region.
Why:
Region Types:
text
┌─────────────────────────────────────────────────────────────┐
│ BROWSER CHROME (exclude this) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HEADER/TABS (optional) │ │
│ │ ┌──────────┬────────────────────────────────────┐ │ │
│ │ │ SIDEBAR │ CONTENT AREA │ │ │
│ │ │ (menu) │ (query form + table) │ │ │
│ │ │ │ │ │ │
│ │ │ │ │ │ │
│ │ └──────────┴────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ SYSTEM TASKBAR (exclude this) │
└─────────────────────────────────────────────────────────────┘
Type A: FULL PAGE WITH SIDEBAR (recommended for navigation demos)
→ Includes sidebar menu + content area
→ Excludes browser chrome & taskbar
Type B: CONTENT ONLY (for feature-focused demos)
→ Content area only, no sidebar
→ Cleaner look for specific functionality
Type C: FULL SCREEN (NOT recommended)
→ Everything including browser chrome
→ Unprofessional for product demos
Implementation Requirements (Universal IDE Support):
python
# ✅ CORRECT: ALWAYS ask user to select region via AskUserQuestion
async def select_recording_region(self, page) -> Dict:
"""
[MANDATORY] Force user to select recording region.
CRITICAL RULES:
- ❌ NEVER default to full-screen
- ❌ NEVER skip user selection
- ✅ MUST use AskUserQuestion tool
- ✅ MUST provide visual region options
"""
response = await AskUserQuestion({
"questions": [{
"header": "录制区域",
"question": "请选择要录制的区域(当前为全屏模式):",
"multiSelect": False,
"options": [
{
"label": "📺 页面+侧边栏(推荐)",
"description": "包含左侧导航菜单+主内容区,不含浏览器边框。适合演示带菜单操作的功能"
},
{
"label": "📋 仅内容区",
"description": "只录制主内容区域,不含左侧菜单。适合聚焦功能本身"
},
{
"label": "🖥️ 全屏",
"description": "包含浏览器边框、任务栏等所有元素(不推荐)"
}
]
}]
})
choice = response['questions'][0]['answer']
if choice == "📺 页面+侧边栏(推荐)":
return {"x": 0, "y": 80, "width": 1920, "height": 1000}
elif choice == "📋 仅内容区":
return {"x": 250, "y": 80, "width": 1670, "height": 1000}
else:
return None # Full screen (no cropping)
Playwright Implementation:
python
# Use record_video_size parameter to crop video to selected region
ctx = await browser.new_context(
viewport={'width': 1920, 'height': 1080},
record_video_dir=str(video_dir),
record_video_size={
'width': selected_region['width'], # e.g., 1920 or 1670
'height': selected_region['height'] # e.g., 1000
},
locale='zh-CN'
)
❌ FORBIDDEN BEHAVIORS:
✅ REQUIRED WORKFLOW:
AskUserQuestion with region optionsrecord_video_size to crop videoRule: After generating scenarios from PM analysis, you MUST display all scenarios to the user and wait for their explicit approval before starting recording.
Why:
Implementation Requirements (Universal IDE Support):
python
# ✅ CORRECT: Use AskUserQuestion tool (works in ALL IDEs: TRAE, Cursor, WorkBuddy, etc.)
async def preview_and_validate_scenarios(self, scenarios: List[Scenario]) -> List[Scenario]:
"""
[MANDATORY] Show scenario list to user for review and confirmation.
CRITICAL: Must NOT start recording until user explicitly confirms.
Use AskUserQuestion tool for universal IDE compatibility.
"""
# Build scenario summary text
scenario_summary = "\n".join([
f" [{s.id}] {s.name} - {s.description} (~{s.duration_estimate}s)"
for s in scenarios
])
# [MANDATORY] Use AskUserQuestion - works in ALL IDEs!
response = await AskUserQuestion({
"questions": [{
"header": "Scenarios",
"question": f"Generated {len(scenarios)} recording scenarios. Approve to start recording?\n{scenario_summary}",
"multiSelect": False,
"options": [
{
"label": "✅ Approve & Start Recording",
"description": "Accept all scenarios and start video recording immediately"
},
{
"label": "✏️ Modify Scenarios",
"description": "Remove/reorder/edit specific scenarios"
},
{
"label": "❌ Cancel",
"description": "Abort recording process"
}
]
}]
})
choice = response['questions'][0]['answer']
if "Approve" in choice:
return scenarios
elif choice == "2":
return await self._modify_scenarios(scenarios) # Edit mode
elif choice == "3":
return await self._add_custom_scenario(scenarios) # Add custom
else:
raise KeyboardInterrupt("User cancelled")
User Actions Available:
ActionCommandDescriptionApproveChoice 1Accept all scenarios and start recordingRemove ScenarioREMOVE S5Delete scenario S5 from listSwap OrderSWAP S2 S6Exchange positions of S2 and S6Edit NarrationEDIT S3Change narration text for S3Add CustomChoice 3Create new scenario manuallyCancelChoice 4Abort entire process
❌ FORBIDDEN: Starting recording without showing this preview screen.
Rule: When a PRD has been generated by the bmad-create-prd skill, you MUST extract scenarios from that PRD instead of auto-generating them. Only auto-generate scenarios when no PRD exists.
Why:
Implementation Requirements (Universal IDE Support):
python
# ✅ CORRECT: Check for existing PRD before generating scenarios
async def resolve_scenarios(self, target_feature: str) -> List[Scenario]:
"""
[MANDATORY] Resolve recording scenarios with PRD priority.
Priority:
1. If bmad-create-prd PRD exists → Extract scenarios from PRD (MANDATORY)
2. If no PRD exists → Auto-generate scenarios from page analysis
"""
# Step 1: Check if bmad-create-prd PRD exists
prd_file = await self._find_prd_file(target_feature)
if prd_file:
# [MANDATORY] Use PRD scenarios - DO NOT auto-generate
scenarios = await self._extract_scenarios_from_prd(prd_file)
return scenarios
else:
# Fallback: Auto-generate scenarios from page analysis
scenarios = await self._auto_generate_scenarios()
return scenarios
async def _find_prd_file(self, feature_name: str) -> Optional[Path]:
"""Search for PRD files generated by bmad-create-prd skill."""
search_paths = [
Path("./_bmad-output/prd"),
Path("./docs/prd"),
Path("./prd"),
]
for search_dir in search_paths:
if not search_dir.exists():
continue
for prd_file in search_dir.rglob("*.md"):
content = prd_file.read_text(encoding='utf-8')
if feature_name in content or '场景' in content:
return prd_file
return None
async def _extract_scenarios_from_prd(self, prd_file: Path) -> List[Scenario]:
"""Extract recording scenarios from bmad-create-prd PRD."""
content = prd_file.read_text(encoding='utf-8')
# Parse PRD sections: user stories, scenarios, feature descriptions
scenarios = []
# Extract from PRD structure:
# - ## 场景 / ## Scenario sections
# - ### 用户故事 / ### User Story sections
# - Feature descriptions with step-by-step flows
# ... parsing logic ...
return scenarios
Decision Flow:
text
User requests video recording
↓
Check: Does bmad-create-prd PRD exist?
↓
YES → [MANDATORY] Extract scenarios from PRD
│ ↓
│ Show PRD scenarios to user via AskUserQuestion
│ ↓
│ User approves → Start recording with PRD scenarios
│
NO → Auto-generate scenarios from page analysis
↓
Show auto scenarios to user via AskUserQuestion
↓
User approves → Start recording with auto scenarios
❌ FORBIDDEN: Ignoring an existing bmad-create-prd PRD and auto-generating scenarios instead.
text
COMPLETE V3.2 WORKFLOW:
=======================
[Step 1] Launch Browser + Enable Recording
↓
[Step 2] Navigate to Target URL (+ Login if needed)
↓
[Step 3] ⚠️ [MANDATORY #1] DETECT REGION + PROMPT USER
↓ Auto-detect content area
↓ SHOW interactive menu (ALWAYS!)
↓ Wait for user choice: FULL / CONTENT / CUSTOM
↓ User EXPLICITLY selects region
↓
[Step 4] PM Analysis (Auto-detect components + Generate PRD)
↓ Detect: trees, forms, buttons, tables, etc.
↓ Generate scenarios: S1, S2, S3, ...
↓ Save PRD to JSON file
↓
[Step 4.5] ⚠️ [MANDATORY #2] PREVIEW SCENARIOS + WAIT FOR APPROVAL
↓ Display ALL scenarios in detail table
↓ Show narration previews
↓ Wait for user action:
Option 1: ✅ Approve → Continue to Step 5
Option 2: ✏️ Modify → Remove/Swap/Edit scenarios
Option 3: ➕ Add Custom → Create new scenario
Option 4: ❌ Cancel → Abort
↓ User EXPLICITLY approves scenarios
↓
[Step 5] EXECUTE RECORDING (Finally!)
↓ For each approved scenario:
Execute real interactions (mouse, keyboard, scroll)
All actions recorded to .webm file
↓
[Step 6] Finalize Video (Close browser context)
↓ Raw video saved
↓
[Step 7] Generate Audio + Merge
↓ TTS narration for each scenario
↓ FFmpeg merge video + audio
↓ Final MP4 output
TOTAL: 7 Steps + 2 MANDATORY User Interactions
Problem: Full-page recording includes unnecessary elements (sidebar, header, navigation).
Solution: Let users specify which area to record.
python
# Define recording region (content area only)
region = RecordingRegion(
x=250, # Start X (after sidebar)
y=80, # Start Y (below header)
width=1670, # Content width
height=1000 # Content height
)
# Use region during recording
await recorder.record_with_region_and_scenarios(
url="https://example.com/dashboard",
output_file="./demo.mp4",
options={'region': region}
)
Auto-Detection Strategies:
.ant-layout-content or [role="main"]Integration with bmad-agent-pm Skill:
text
User Request: "Record the comprehensive query feature"
↓
[Step 1] Page Analysis (Auto-detect components)
↓ Detects: trees, forms, buttons, tables, date pickers
[Step 2] PRD Generation (Simulated bmad-create-prd)
↓ Outputs: Feature name, component list, user stories
[Step 3] Scenario Decomposition
↓ Creates: S1-S7 scenarios (Overview → Selection → Query → Results)
[Step 4] Per-Scenario Recording
↓ Executes: Real interactions per scenario
[Step 5] Audio Generation
↓ Generates: TTS narration based on each scenario
[Step 6] Merge & Output
↓ Produces: Final MP4 with professional narration
Example PRD Output:
json
{
"feature_name": "综合查询 · 单车成本核算系统",
"components_detected": {
"trees": ["查询项目选择树"],
"forms": ["请输入关键字进行过滤"],
"buttons": ["查询", "重置", "导出", "打印"],
"tables": ["Table 1"],
"date_pickers": ["DatePicker 1"]
},
"scenarios": [
{
"id": "S1",
"name": "界面概览",
"description": "展示整体布局和主要功能区",
"narration": "欢迎观看综合查询功能演示..."
},
{
"id": "S2",
"name": "查询项目选择",
"description": "在树形结构中选择要查询的项目类型",
"narration": "在查询项目选择区域..."
},
// ... more scenarios
]
}
bash
# Basic usage (auto-detect region + PM analysis)
python record_v3_smart.py --target "http://localhost:8090/#/dashboardIndex/UnityQuery"
# With custom output
python record_v3_smart.py -t "URL" -o "./my-demo.mp4"
# Disable region selection (full page)
python record_v3_smart.py --no-region
Python API:
python
from record_v3_smart import SmartVideoRecorderV31
recorder = SmartVideoRecorderV31(verbose=True)
result = await recorder.record_with_region_and_scenarios(
url="https://example.com/feature-page",
output_file="./feature-demo.mp4",
options={
'auto_select_region': True,
'generate_prd': True,
'voice': 'zh-CN-YunxiNeural'
}
)
print(f"✅ Video generated: {result['output_path']}")
print(f" Scenarios: {result['scenarios_count']}")
print(f" Region: {result['region']}")
print(f" PRD saved to: {result['prd_file']}")
The following approaches are NOT ALLOWED under any circumstances:
ALL video generation MUST use this exact approach:
python
# ✅ CORRECT: Playwright Native Recording
from playwright.async_api import async_playwright
pw = await async_playwright().start()
# CRITICAL LINE: Enable video recording in browser context
context = await pw.chromium.launch_persistent_context(
"",
headless=False,
viewport={'width': 1920, 'height': 1080},
record_video_dir="./videos", # ← THIS IS MANDATORY
record_video_size={'width': 1920, 'height': 1080}
)
page = context.pages[0]
# Navigate to target page
await page.goto("https://example.com")
# Execute REAL interactions (these get RECORDED automatically)
await page.mouse.move(500, 300, steps=20) # Real mouse movement
await page.mouse.click(500, 300) # Real click
await page.keyboard.type("Hello") # Real typing
await page.mouse.wheel(0, 500) # Real scroll
# Close context to finalize recording
await context.close()
await pw.stop()
# Result: A .webm file with REAL interactions recorded!
Every implementation MUST include these parameters:
python
context = await browser.new_context(
# ... other params ...
# MANDATORY: Video recording directory
record_video_dir=str(video_output_path),
# MANDATORY: Video resolution (must match viewport)
record_video_size={
'width': viewport_width,
'height': viewport_height
},
# Recommended: Full HD by default
viewport={
'width': 1920, # ← Full HD width
'height': 1080 # ← Full HD height
}
)
Mouse Interactions:
python
# Smooth mouse movement (REQUIRED for professional look)
await page.mouse.move(x, y, steps=20) # steps=20 makes it smooth
# Click with visual feedback
await page.mouse.click(x, y)
# Scroll with animation
await page.mouse.wheel(delta_x, delta_y)
Keyboard Interactions:
python
# Type text character by character (realistic)
await page.keyboard.type("search query", delay=100) # delay=100ms per char
# Press special keys
await page.keyboard.press("Enter")
await page.keyboard.press("Tab")
Element Highlighting (Optional but Recommended):
python
# Before click - highlight element
await element.evaluate("""
el => {
el.style.transition = 'outline 0.2s';
el.style.outline = '3px solid #1890ff';
el.style.outlineOffset = '2px';
}
""")
await asyncio.sleep(0.3)
# Perform click
await page.mouse.click(x, y)
# Remove highlight after click
await element.evaluate("""
el => {
el.style.outline = '';
el.style.outlineOffset = '';
}
""")
Recording Process:
Playwright records .webm format automatically
File saved to record_video_dir when context closes
Merge TTS audio using FFmpeg: bash
ffmpeg -i input.webm -i audio.mp3 -c:v libx264 -c:a aac output.mp4
Before considering a video generation complete, verify:
record_video_dir parameter?page.mouse.move/click/wheel calls executed?page.keyboard.type/press calls used if needed?If ANY of these fail, the implementation is INVALID and must be fixed.
This skill provides REAL automated video generation capabilities:
Use this skill when the user wants to:
python
import asyncio
from auto_video_generator import VideoGenerator
async def main():
gen = VideoGenerator(verbose=True)
result = await gen.generate(
source="https://example.com/dashboard",
output="./demo.mp4",
options={
"viewport_width": 1920, # Full HD
"viewport_height": 1080,
"voice": "zh-CN-YunxiNeural", # Chinese voice
"headless": False, # Show browser window
"show_cursor": True, # Show mouse cursor
"highlight_clicks": True, # Highlight clicked elements
}
)
print(f"✅ REAL Video generated!")
print(f" Duration: {result.duration_seconds:.1f}s")
print(f" Size: {result.file_size_mb:.2f} MB")
print(f" Resolution: {result.resolution}")
asyncio.run(main())
text
1. Launch Chrome with recording enabled (record_video_dir)
2. Navigate to target URL
3. Analyze page structure (detect forms, buttons, tables)
4. Execute REAL interactions:
- Mouse moves smoothly to elements
- Clicks buttons with highlight effects
- Types in input fields
- Scrolls to show content
5. All actions automatically recorded as .webm
6. Generate TTS audio narration
7. Merge video + audio → final MP4
8. Return result with metadata
OptionTypeDefaultDescriptionviewport_widthint1920Video width (min: 1440)viewport_heightint1080Video height (min: 900)voicestringzh-CN-YunxiNeuralEdge TTS voice nameratestring"-5%"Speech rate adjustmentheadlessboolFalseHide browser windowshow_cursorboolTrueShow mouse cursor in videohighlight_clicksboolTrueHighlight elements before clickingfpsint4Target frames per secondqualitystring"high"Video quality (low/medium/high)
Every generated video MUST meet these standards:
python
# DON'T DO THIS!
for i in range(100):
await page.screenshot(path=f"frame_{i}.png") # ❌ Screenshots!
await asyncio.sleep(0.1)
# Then use ffmpeg to combine images... # ❌ Fake video!
python
# DO THIS INSTEAD!
context = await browser.new_context(
record_video_dir="./videos" # ✅ Enable recording!
)
page = await context.new_page()
await page.goto("https://example.com")
# Real interactions that get recorded automatically
await page.mouse.move(500, 300, steps=20)
await page.mouse.click(500, 300)
await context.close() # ✅ Finalizes recording
record_video_dir requirementAll contributions MUST follow the V3.2 standard:
record_video_dir)MIT License
Version: 3.2.0 (Mandatory User Interactions) Last Updated: 2026-05-30 Status: PRODUCTION READY ✅ Maintained by: AVG Team Integration: bmad-agent-pm (PM workflow)