@biggora/claude-plugins 1.1.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/README.md +24 -17
  3. package/package.json +1 -1
  4. package/registry/registry.json +319 -244
  5. package/specs/coding.md +24 -0
  6. package/specs/pod.md +2 -0
  7. package/src/skills/captcha/README.md +221 -0
  8. package/src/skills/captcha/SKILL.md +355 -0
  9. package/src/skills/captcha/references/captcha-types.md +254 -0
  10. package/src/skills/captcha/references/services.md +172 -0
  11. package/src/skills/captcha/references/stealth.md +238 -0
  12. package/src/skills/captcha/scripts/solve_captcha.py +323 -0
  13. package/src/skills/captcha/scripts/solve_image_grid.py +350 -0
  14. package/src/skills/codex-cli/SKILL.md +21 -11
  15. package/src/skills/gemini-cli/SKILL.md +27 -13
  16. package/src/skills/gemini-cli/references/commands.md +21 -14
  17. package/src/skills/gemini-cli/references/configuration.md +23 -18
  18. package/src/skills/gemini-cli/references/headless-and-scripting.md +7 -17
  19. package/src/skills/gemini-cli/references/mcp-and-extensions.md +12 -6
  20. package/src/skills/google-merchant-api/SKILL.md +581 -0
  21. package/src/skills/google-merchant-api/references/accounts.md +247 -0
  22. package/src/skills/google-merchant-api/references/content-api-legacy.md +216 -0
  23. package/src/skills/google-merchant-api/references/datasources.md +233 -0
  24. package/src/skills/google-merchant-api/references/inventories.md +201 -0
  25. package/src/skills/google-merchant-api/references/migration.md +267 -0
  26. package/src/skills/google-merchant-api/references/products.md +316 -0
  27. package/src/skills/google-merchant-api/references/promotions.md +201 -0
  28. package/src/skills/google-merchant-api/references/reports.md +240 -0
  29. package/src/skills/lv-aggregators-api/SKILL.md +113 -0
  30. package/src/skills/lv-aggregators-api/references/integration-guide.md +368 -0
  31. package/src/skills/lv-aggregators-api/references/kurpirkt.md +103 -0
  32. package/src/skills/lv-aggregators-api/references/salidzini.md +122 -0
  33. package/src/skills/notebook-lm/SKILL.md +1 -1
  34. package/src/skills/screen-recording/SKILL.md +243 -213
  35. package/src/skills/screen-recording/references/design-patterns.md +4 -2
  36. package/src/skills/screen-recording/references/ffmpeg-recording.md +473 -0
  37. package/src/skills/screen-recording/references/{approach1-programmatic.md → programmatic-generation.md} +45 -22
  38. package/src/skills/screen-recording/references/python-fallback.md +222 -0
  39. package/src/skills/tailwindcss-best-practices/SKILL.md +180 -0
  40. package/src/skills/tailwindcss-best-practices/references/best-practices-utility-patterns.md +87 -0
  41. package/src/skills/tailwindcss-best-practices/references/core-installation.md +109 -0
  42. package/src/skills/tailwindcss-best-practices/references/core-preflight.md +200 -0
  43. package/src/skills/tailwindcss-best-practices/references/core-responsive.md +163 -0
  44. package/src/skills/tailwindcss-best-practices/references/core-source-detection.md +114 -0
  45. package/src/skills/tailwindcss-best-practices/references/core-theme.md +108 -0
  46. package/src/skills/tailwindcss-best-practices/references/core-utility-classes.md +59 -0
  47. package/src/skills/tailwindcss-best-practices/references/core-variants.md +204 -0
  48. package/src/skills/tailwindcss-best-practices/references/effects-form-controls.md +76 -0
  49. package/src/skills/tailwindcss-best-practices/references/effects-mask.md +91 -0
  50. package/src/skills/tailwindcss-best-practices/references/effects-scroll-snap.md +59 -0
  51. package/src/skills/tailwindcss-best-practices/references/effects-text-shadow.md +78 -0
  52. package/src/skills/tailwindcss-best-practices/references/effects-transition-animation.md +80 -0
  53. package/src/skills/tailwindcss-best-practices/references/effects-visibility-interactivity.md +82 -0
  54. package/src/skills/tailwindcss-best-practices/references/features-content-detection.md +175 -0
  55. package/src/skills/tailwindcss-best-practices/references/features-custom-styles.md +203 -0
  56. package/src/skills/tailwindcss-best-practices/references/features-dark-mode.md +137 -0
  57. package/src/skills/tailwindcss-best-practices/references/features-functions-directives.md +241 -0
  58. package/src/skills/tailwindcss-best-practices/references/features-upgrade.md +160 -0
  59. package/src/skills/tailwindcss-best-practices/references/layout-aspect-ratio.md +39 -0
  60. package/src/skills/tailwindcss-best-practices/references/layout-columns.md +80 -0
  61. package/src/skills/tailwindcss-best-practices/references/layout-display.md +110 -0
  62. package/src/skills/tailwindcss-best-practices/references/layout-flexbox.md +112 -0
  63. package/src/skills/tailwindcss-best-practices/references/layout-grid.md +87 -0
  64. package/src/skills/tailwindcss-best-practices/references/layout-height.md +97 -0
  65. package/src/skills/tailwindcss-best-practices/references/layout-inset.md +103 -0
  66. package/src/skills/tailwindcss-best-practices/references/layout-logical-properties.md +92 -0
  67. package/src/skills/tailwindcss-best-practices/references/layout-margin.md +126 -0
  68. package/src/skills/tailwindcss-best-practices/references/layout-min-max-sizing.md +63 -0
  69. package/src/skills/tailwindcss-best-practices/references/layout-object-fit-position.md +64 -0
  70. package/src/skills/tailwindcss-best-practices/references/layout-overflow.md +57 -0
  71. package/src/skills/tailwindcss-best-practices/references/layout-padding.md +77 -0
  72. package/src/skills/tailwindcss-best-practices/references/layout-position.md +85 -0
  73. package/src/skills/tailwindcss-best-practices/references/layout-tables.md +67 -0
  74. package/src/skills/tailwindcss-best-practices/references/layout-width.md +102 -0
  75. package/src/skills/tailwindcss-best-practices/references/transform-base.md +68 -0
  76. package/src/skills/tailwindcss-best-practices/references/transform-rotate.md +70 -0
  77. package/src/skills/tailwindcss-best-practices/references/transform-scale.md +83 -0
  78. package/src/skills/tailwindcss-best-practices/references/transform-skew.md +62 -0
  79. package/src/skills/tailwindcss-best-practices/references/transform-translate.md +77 -0
  80. package/src/skills/tailwindcss-best-practices/references/typography-font-text.md +142 -0
  81. package/src/skills/tailwindcss-best-practices/references/typography-list-style.md +65 -0
  82. package/src/skills/tailwindcss-best-practices/references/typography-text-align.md +60 -0
  83. package/src/skills/tailwindcss-best-practices/references/visual-background.md +76 -0
  84. package/src/skills/tailwindcss-best-practices/references/visual-border.md +108 -0
  85. package/src/skills/tailwindcss-best-practices/references/visual-effects.md +111 -0
  86. package/src/skills/tailwindcss-best-practices/references/visual-svg.md +82 -0
  87. package/src/skills/test-mobile-app/SKILL.md +11 -6
  88. package/src/skills/test-mobile-app/scripts/analyze_apk.py +15 -4
  89. package/src/skills/test-mobile-app/scripts/check_environment.py +5 -5
  90. package/src/skills/test-mobile-app/scripts/run_tests.py +1 -1
  91. package/src/skills/test-web-ui/SKILL.md +264 -84
  92. package/src/skills/test-web-ui/scripts/discover.py +25 -12
  93. package/src/skills/test-web-ui/scripts/run_tests.py +3 -2
  94. package/src/skills/tm-search/SKILL.md +242 -106
  95. package/src/skills/tm-search/references/scraping-fallback.md +60 -95
  96. package/src/skills/tm-search/scripts/tm_search.py +453 -375
  97. package/src/skills/vite-best-practices/SKILL.md +115 -0
  98. package/src/skills/vite-best-practices/references/build-and-ssr.md +255 -0
  99. package/src/skills/vite-best-practices/references/core-config.md +231 -0
  100. package/src/skills/vite-best-practices/references/core-features.md +222 -0
  101. package/src/skills/vite-best-practices/references/core-plugin-api.md +294 -0
  102. package/src/skills/vite-best-practices/references/environment-api.md +108 -0
  103. package/src/skills/vite-best-practices/references/rolldown-migration.md +242 -0
  104. package/src/skills/screen-recording/references/approach2-xvfb.md +0 -232
@@ -0,0 +1,473 @@
1
+ # FFmpeg Screen Recording — Platform Reference
2
+
3
+ Complete command reference for recording the actual screen with FFmpeg.
4
+
5
+ ## Table of Contents
6
+ - [Platform Detection](#platform-detection)
7
+ - [Windows (gdigrab)](#windows-gdigrab)
8
+ - [macOS (avfoundation)](#macos-avfoundation)
9
+ - [Linux X11 (x11grab)](#linux-x11-x11grab)
10
+ - [Linux Wayland](#linux-wayland)
11
+ - [Audio Capture](#audio-capture)
12
+ - [Window Capture](#window-capture)
13
+ - [Quality & Performance Tuning](#quality--performance-tuning)
14
+ - [Full Cross-Platform Script](#full-cross-platform-script)
15
+
16
+ ---
17
+
18
+ ## Platform Detection
19
+
20
+ ```python
21
+ import platform, shutil, os
22
+
23
+ def detect_recording_backend():
24
+ """Detect the best FFmpeg input format for this platform."""
25
+ system = platform.system()
26
+ has_ffmpeg = shutil.which("ffmpeg") is not None
27
+
28
+ if not has_ffmpeg:
29
+ return "python_fallback"
30
+
31
+ if system == "Windows":
32
+ return "gdigrab"
33
+ elif system == "Darwin":
34
+ return "avfoundation"
35
+ else: # Linux
36
+ if os.environ.get("WAYLAND_DISPLAY"):
37
+ if shutil.which("wf-recorder"):
38
+ return "wf-recorder"
39
+ return "x11grab" # may not work on Wayland, but try
40
+ return "x11grab"
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Windows (gdigrab)
46
+
47
+ ### Full screen
48
+ ```bash
49
+ ffmpeg -f gdigrab -framerate 30 -draw_mouse 1 -i desktop ^
50
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ^
51
+ recording.mp4 -y
52
+ ```
53
+
54
+ ### Specific region (x=100, y=200, 800x600)
55
+ ```bash
56
+ ffmpeg -f gdigrab -framerate 30 -draw_mouse 1 ^
57
+ -offset_x 100 -offset_y 200 -video_size 800x600 ^
58
+ -i desktop -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ^
59
+ recording.mp4 -y
60
+ ```
61
+
62
+ ### Specific window by title
63
+ ```bash
64
+ ffmpeg -f gdigrab -framerate 30 -draw_mouse 1 ^
65
+ -i title="Untitled - Notepad" ^
66
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ^
67
+ recording.mp4 -y
68
+ ```
69
+
70
+ ### With audio (system sound)
71
+ ```bash
72
+ ffmpeg -f dshow -i audio="Stereo Mix" ^
73
+ -f gdigrab -framerate 30 -draw_mouse 1 -i desktop ^
74
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ^
75
+ -c:a aac -b:a 128k ^
76
+ recording.mp4 -y
77
+ ```
78
+
79
+ ### List available audio devices
80
+ ```bash
81
+ ffmpeg -list_devices true -f dshow -i dummy 2>&1 | findstr "audio"
82
+ ```
83
+
84
+ > **Note**: "Stereo Mix" must be enabled in Windows Sound settings → Recording tab → right-click → Show Disabled Devices → Enable "Stereo Mix". If unavailable, use virtual audio cable software.
85
+
86
+ ### With fixed duration (30 seconds)
87
+ ```bash
88
+ ffmpeg -f gdigrab -framerate 30 -draw_mouse 1 -t 30 -i desktop ^
89
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p ^
90
+ recording.mp4 -y
91
+ ```
92
+
93
+ ---
94
+
95
+ ## macOS (avfoundation)
96
+
97
+ ### List available devices
98
+ ```bash
99
+ ffmpeg -f avfoundation -list_devices true -i "" 2>&1
100
+ ```
101
+ Output shows numbered screen and audio devices. Typically: `[0]` = screen, `[1]` = microphone.
102
+
103
+ ### Full screen (no audio)
104
+ ```bash
105
+ ffmpeg -f avfoundation -framerate 30 -i "0" \
106
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
107
+ recording.mp4 -y
108
+ ```
109
+
110
+ ### Full screen with audio
111
+ ```bash
112
+ ffmpeg -f avfoundation -framerate 30 -i "0:1" \
113
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
114
+ -c:a aac -b:a 128k \
115
+ recording.mp4 -y
116
+ ```
117
+
118
+ ### Region capture (crop filter)
119
+ ```bash
120
+ # Record full screen then crop to 800x600 at offset (100, 200)
121
+ ffmpeg -f avfoundation -framerate 30 -i "0" \
122
+ -vf "crop=800:600:100:200" \
123
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
124
+ recording.mp4 -y
125
+ ```
126
+
127
+ > **Note**: avfoundation doesn't support offset/region natively. Use `-vf crop=w:h:x:y` to crop after capture. This captures the full screen and discards the rest — slightly less efficient but works.
128
+
129
+ ### Cursor
130
+ macOS avfoundation captures the cursor by default. To hide it, use `-capture_cursor 0`.
131
+
132
+ ---
133
+
134
+ ## Linux X11 (x11grab)
135
+
136
+ ### Full screen
137
+ ```bash
138
+ ffmpeg -f x11grab -framerate 30 -draw_mouse 1 \
139
+ -i :0.0 \
140
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
141
+ recording.mp4 -y
142
+ ```
143
+
144
+ ### Specific region (800x600 at offset 100,200)
145
+ ```bash
146
+ ffmpeg -f x11grab -framerate 30 -draw_mouse 1 \
147
+ -video_size 800x600 -i :0.0+100,200 \
148
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
149
+ recording.mp4 -y
150
+ ```
151
+
152
+ ### With audio (PulseAudio)
153
+ ```bash
154
+ ffmpeg -f pulse -i default \
155
+ -f x11grab -framerate 30 -draw_mouse 1 -i :0.0 \
156
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
157
+ -c:a aac -b:a 128k \
158
+ recording.mp4 -y
159
+ ```
160
+
161
+ ### With audio (ALSA)
162
+ ```bash
163
+ ffmpeg -f alsa -i default \
164
+ -f x11grab -framerate 30 -draw_mouse 1 -i :0.0 \
165
+ -c:v libx264 -preset ultrafast -crf 23 -pix_fmt yuv420p \
166
+ -c:a aac -b:a 128k \
167
+ recording.mp4 -y
168
+ ```
169
+
170
+ ### Get screen resolution
171
+ ```bash
172
+ xdpyinfo | grep dimensions
173
+ # or
174
+ xrandr | grep '*'
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Linux Wayland
180
+
181
+ FFmpeg's x11grab does **not** work on Wayland. Use `wf-recorder` instead:
182
+
183
+ ```bash
184
+ # Full screen
185
+ wf-recorder -f recording.mp4
186
+
187
+ # With audio
188
+ wf-recorder -a -f recording.mp4
189
+
190
+ # Specific region (interactive selection)
191
+ wf-recorder -g "$(slurp)" -f recording.mp4
192
+
193
+ # Fixed region
194
+ wf-recorder -g "100,200 800x600" -f recording.mp4
195
+ ```
196
+
197
+ **Install**: `sudo apt install wf-recorder` (Ubuntu) or `sudo pacman -S wf-recorder` (Arch)
198
+
199
+ > **Detection**: Check `echo $WAYLAND_DISPLAY` — if set, you're on Wayland.
200
+
201
+ ---
202
+
203
+ ## Audio Capture
204
+
205
+ ### Detect available audio devices
206
+
207
+ **Windows:**
208
+ ```python
209
+ import subprocess
210
+ result = subprocess.run(
211
+ ["ffmpeg", "-list_devices", "true", "-f", "dshow", "-i", "dummy"],
212
+ capture_output=True, text=True
213
+ )
214
+ # Parse stderr for audio device names
215
+ for line in result.stderr.splitlines():
216
+ if "audio" in line.lower():
217
+ print(line.strip())
218
+ ```
219
+
220
+ **macOS:**
221
+ ```python
222
+ result = subprocess.run(
223
+ ["ffmpeg", "-f", "avfoundation", "-list_devices", "true", "-i", ""],
224
+ capture_output=True, text=True
225
+ )
226
+ for line in result.stderr.splitlines():
227
+ print(line.strip())
228
+ ```
229
+
230
+ **Linux:**
231
+ ```bash
232
+ # PulseAudio
233
+ pactl list short sources
234
+
235
+ # ALSA
236
+ arecord -L
237
+ ```
238
+
239
+ ### Audio device selection in Python
240
+ ```python
241
+ def get_audio_device():
242
+ system = platform.system()
243
+ if system == "Windows":
244
+ return "Stereo Mix" # Common default; detect with dshow list
245
+ elif system == "Darwin":
246
+ return "1" # Audio device index from avfoundation list
247
+ else:
248
+ return "default" # PulseAudio default source
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Window Capture
254
+
255
+ ### Windows — by window title
256
+ ```python
257
+ # FFmpeg gdigrab supports -i title="Window Title"
258
+ cmd = ["ffmpeg", "-f", "gdigrab", "-framerate", "30",
259
+ "-i", f'title={window_title}',
260
+ "-c:v", "libx264", "-preset", "ultrafast", output, "-y"]
261
+ ```
262
+
263
+ ### macOS — by window (not directly supported)
264
+ macOS avfoundation captures screens, not windows. Workaround:
265
+ 1. Get window bounds with `osascript` (AppleScript)
266
+ 2. Record full screen with crop filter
267
+
268
+ ```python
269
+ import subprocess, json
270
+
271
+ def get_macos_window_bounds(app_name):
272
+ script = f'''
273
+ tell application "System Events"
274
+ tell process "{app_name}"
275
+ set pos to position of front window
276
+ set sz to size of front window
277
+ return (item 1 of pos) & "," & (item 2 of pos) & "," & (item 1 of sz) & "," & (item 2 of sz)
278
+ end tell
279
+ end tell
280
+ '''
281
+ result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True)
282
+ x, y, w, h = result.stdout.strip().split(",")
283
+ return int(x), int(y), int(w), int(h)
284
+ ```
285
+
286
+ ### Linux X11 — by window ID
287
+ ```bash
288
+ # Get window ID interactively (click on window)
289
+ xdotool selectwindow
290
+
291
+ # Get window geometry by ID
292
+ xdotool getwindowgeometry --shell WINDOW_ID
293
+
294
+ # Get by name
295
+ xdotool search --name "Firefox" | head -1
296
+ ```
297
+
298
+ ```python
299
+ import subprocess
300
+
301
+ def get_x11_window_geometry(window_name):
302
+ wid = subprocess.run(
303
+ ["xdotool", "search", "--name", window_name],
304
+ capture_output=True, text=True
305
+ ).stdout.strip().split('\n')[0]
306
+
307
+ geo = subprocess.run(
308
+ ["xdotool", "getwindowgeometry", "--shell", wid],
309
+ capture_output=True, text=True
310
+ ).stdout
311
+ # Parse X, Y, WIDTH, HEIGHT from output
312
+ vals = {}
313
+ for line in geo.strip().split('\n'):
314
+ k, v = line.split('=')
315
+ vals[k] = int(v)
316
+ return vals['X'], vals['Y'], vals['WIDTH'], vals['HEIGHT']
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Quality & Performance Tuning
322
+
323
+ ### Encoding presets (speed vs size)
324
+ | Preset | Speed | File Size | Use Case |
325
+ |--------|-------|-----------|----------|
326
+ | `ultrafast` | Fastest | Largest | Live recording (recommended) |
327
+ | `superfast` | Very fast | Large | Recording with modest CPU |
328
+ | `fast` | Fast | Medium | Post-recording re-encode |
329
+ | `medium` | Moderate | Smaller | Final output for sharing |
330
+ | `slow` | Slow | Smallest | Archival quality |
331
+
332
+ ### CRF values (quality vs size)
333
+ | CRF | Quality | Use Case |
334
+ |-----|---------|----------|
335
+ | 18 | Visually lossless | Maximum quality |
336
+ | 23 | High (default) | Good balance |
337
+ | 28 | Medium | Smaller files, acceptable quality |
338
+ | 35 | Low | Minimum usable quality |
339
+
340
+ ### Recommended settings
341
+ ```bash
342
+ # During recording (prioritize speed)
343
+ -preset ultrafast -crf 23
344
+
345
+ # Re-encode after recording (prioritize size)
346
+ ffmpeg -i recording.mp4 -c:v libx264 -preset slow -crf 23 compressed.mp4
347
+ ```
348
+
349
+ ### FPS recommendations
350
+ - `30` — Standard, smooth motion (default)
351
+ - `24` — Cinematic feel, smaller files
352
+ - `15` — Acceptable for static content (presentations, text-heavy screens)
353
+ - `60` — Smooth for fast-moving content (games, animations)
354
+
355
+ ---
356
+
357
+ ## Full Cross-Platform Script
358
+
359
+ ```python
360
+ #!/usr/bin/env python3
361
+ """
362
+ Cross-platform screen recorder using FFmpeg.
363
+ Usage: python3 record.py [output.mp4] [--duration 30] [--fps 30] [--audio] [--region x,y,w,h]
364
+ """
365
+
366
+ import platform, subprocess, shutil, signal, os, sys
367
+
368
+ def build_ffmpeg_cmd(output, duration=None, fps=30, region=None, audio=False, window=None):
369
+ system = platform.system()
370
+ if not shutil.which("ffmpeg"):
371
+ raise RuntimeError("FFmpeg not found. See: https://ffmpeg.org/download.html")
372
+
373
+ cmd = ["ffmpeg"]
374
+
375
+ if system == "Windows":
376
+ if audio:
377
+ cmd += ["-f", "dshow", "-i", "audio=Stereo Mix"]
378
+ cmd += ["-f", "gdigrab", "-framerate", str(fps), "-draw_mouse", "1"]
379
+ if window:
380
+ cmd += ["-i", f"title={window}"]
381
+ elif region:
382
+ x, y, w, h = region
383
+ cmd += ["-offset_x", str(x), "-offset_y", str(y),
384
+ "-video_size", f"{w}x{h}", "-i", "desktop"]
385
+ else:
386
+ cmd += ["-i", "desktop"]
387
+
388
+ elif system == "Darwin":
389
+ devices = "0" if not audio else "0:1"
390
+ cmd += ["-f", "avfoundation", "-framerate", str(fps),
391
+ "-capture_cursor", "1", "-i", devices]
392
+ if region:
393
+ x, y, w, h = region
394
+ cmd += ["-vf", f"crop={w}:{h}:{x}:{y}"]
395
+
396
+ else: # Linux
397
+ if audio:
398
+ cmd += ["-f", "pulse", "-i", "default"]
399
+ display = os.environ.get("DISPLAY", ":0.0")
400
+ cmd += ["-f", "x11grab", "-framerate", str(fps), "-draw_mouse", "1"]
401
+ if region:
402
+ x, y, w, h = region
403
+ cmd += ["-video_size", f"{w}x{h}", "-i", f"{display}+{x},{y}"]
404
+ else:
405
+ cmd += ["-i", display]
406
+
407
+ cmd += ["-c:v", "libx264", "-preset", "ultrafast",
408
+ "-crf", "23", "-pix_fmt", "yuv420p"]
409
+ if audio:
410
+ cmd += ["-c:a", "aac", "-b:a", "128k"]
411
+ if duration:
412
+ cmd += ["-t", str(duration)]
413
+ cmd += [output, "-y"]
414
+ return cmd
415
+
416
+
417
+ def record_screen(output="recording.mp4", duration=None, fps=30,
418
+ region=None, audio=False, window=None):
419
+ """Record the screen to an MP4 file."""
420
+ system = platform.system()
421
+
422
+ # Wayland special case
423
+ if system == "Linux" and os.environ.get("WAYLAND_DISPLAY"):
424
+ if shutil.which("wf-recorder"):
425
+ cmd = ["wf-recorder", "-f", output]
426
+ if audio:
427
+ cmd.append("-a")
428
+ if region:
429
+ x, y, w, h = region
430
+ cmd += ["-g", f"{x},{y} {w}x{h}"]
431
+ if duration:
432
+ # wf-recorder doesn't have -t; use timeout
433
+ cmd = ["timeout", str(duration)] + cmd
434
+ print(f"Recording (wf-recorder) → {output}")
435
+ proc = subprocess.Popen(cmd)
436
+ try:
437
+ proc.wait()
438
+ except KeyboardInterrupt:
439
+ proc.send_signal(signal.SIGINT)
440
+ proc.wait()
441
+ print(f"Saved: {output} ({os.path.getsize(output):,} bytes)")
442
+ return output
443
+
444
+ cmd = build_ffmpeg_cmd(output, duration, fps, region, audio, window)
445
+ stop_msg = f" for {duration}s" if duration else " (Ctrl+C to stop)"
446
+ print(f"Recording{stop_msg} → {output}")
447
+ proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
448
+ try:
449
+ proc.wait()
450
+ except KeyboardInterrupt:
451
+ proc.send_signal(signal.SIGINT)
452
+ proc.wait()
453
+ size = os.path.getsize(output) if os.path.exists(output) else 0
454
+ print(f"Saved: {output} ({size:,} bytes)")
455
+ return output
456
+
457
+
458
+ if __name__ == "__main__":
459
+ import argparse
460
+ parser = argparse.ArgumentParser(description="Record screen to MP4")
461
+ parser.add_argument("output", nargs="?", default="recording.mp4")
462
+ parser.add_argument("--duration", "-t", type=int, default=None)
463
+ parser.add_argument("--fps", type=int, default=30)
464
+ parser.add_argument("--audio", "-a", action="store_true")
465
+ parser.add_argument("--region", "-r", type=str, default=None,
466
+ help="x,y,w,h (e.g. 100,200,800,600)")
467
+ parser.add_argument("--window", "-w", type=str, default=None,
468
+ help="Window title (Windows only)")
469
+ args = parser.parse_args()
470
+
471
+ region = tuple(map(int, args.region.split(","))) if args.region else None
472
+ record_screen(args.output, args.duration, args.fps, region, args.audio, args.window)
473
+ ```
@@ -9,19 +9,19 @@ Full offline pipeline. No browser, no display server needed.
9
9
  """
10
10
  Autonomous Product Demo Video Generator
11
11
  Usage: python3 generate_demo.py
12
- Output: /mnt/user-data/outputs/demo.mp4
12
+ Output: ./demo.mp4 (current working directory)
13
13
  """
14
14
 
15
15
  from moviepy import VideoClip, AudioFileClip
16
16
  import numpy as np
17
17
  from PIL import Image, ImageDraw, ImageFont
18
- import pyttsx3, subprocess, os, shutil
18
+ import pyttsx3, subprocess, os, tempfile
19
19
 
20
20
  # ── CONFIG ─────────────────────────────────────────────────────────────────────
21
21
  WIDTH, HEIGHT = 1280, 720
22
22
  FPS = 24
23
- OUTPUT_PATH = "/home/claude/demo.mp4"
24
- FINAL_OUTPUT = "/mnt/user-data/outputs/demo.mp4"
23
+ TMP = tempfile.gettempdir()
24
+ OUTPUT_PATH = os.path.join(os.getcwd(), "demo.mp4")
25
25
 
26
26
  # Color palette (dark tech theme)
27
27
  C = {
@@ -36,6 +36,20 @@ C = {
36
36
  "card": (25, 28, 60),
37
37
  }
38
38
 
39
+ # Font handling (falls back to Pillow default if no TTF found)
40
+ def _load_font(size=24):
41
+ for p in ["/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
42
+ "/usr/share/fonts/TTF/DejaVuSans.ttf",
43
+ "/System/Library/Fonts/Helvetica.ttc",
44
+ "C:/Windows/Fonts/arial.ttf"]:
45
+ if os.path.exists(p):
46
+ return ImageFont.truetype(p, size)
47
+ return ImageFont.load_default()
48
+
49
+ FONT_LG = _load_font(36)
50
+ FONT_MD = _load_font(24)
51
+ FONT_SM = _load_font(16)
52
+
39
53
  # ── SCENES ─────────────────────────────────────────────────────────────────────
40
54
  SCENES = [
41
55
  {
@@ -70,9 +84,9 @@ def ease(t, d):
70
84
 
71
85
  def draw_header(draw, title, subtitle=""):
72
86
  draw.rectangle([0, 0, WIDTH, 72], fill=C["header"])
73
- draw.text((32, 16), title, fill=C["text"])
87
+ draw.text((32, 16), title, fill=C["text"], font=FONT_MD)
74
88
  if subtitle:
75
- draw.text((32, 46), subtitle, fill=C["subtext"])
89
+ draw.text((32, 46), subtitle, fill=C["subtext"], font=FONT_SM)
76
90
  # Header accent line
77
91
  draw.rectangle([0, 72, WIDTH, 75], fill=C["accent"])
78
92
 
@@ -105,7 +119,8 @@ def draw_intro(draw, t, d):
105
119
  x = int(WIDTH/2 - len(title)*12)
106
120
  y = int(200 + (1 - alpha_title) * 60)
107
121
  draw.text((x, y), title, fill=(
108
- int(255 * alpha_title), int(255 * alpha_title), int(255 * alpha_title)))
122
+ int(255 * alpha_title), int(255 * alpha_title), int(255 * alpha_title)),
123
+ font=FONT_LG)
109
124
 
110
125
  # Subtitle
111
126
  sub = "Transforming workflows with AI automation"
@@ -223,15 +238,24 @@ DRAW_FUNCTIONS = {
223
238
 
224
239
  # ── MAIN PIPELINE ──────────────────────────────────────────────────────────────
225
240
  def generate_video():
241
+ # NOTE: Single TTS call for all scenes. For per-scene timing, generate audio per scene
242
+ # (see SKILL.md "Scene Structure Pattern" section for the per-scene alternative).
226
243
  print("📝 Step 1: Generating TTS narration...")
227
244
  narration_text = " ".join(s["narration"] for s in SCENES)
228
- engine = pyttsx3.init()
229
- engine.setProperty('rate', 140)
230
- engine.save_to_file(narration_text, '/tmp/narration.wav')
231
- engine.runAndWait()
232
- subprocess.run(['ffmpeg', '-i', '/tmp/narration.wav', '-c:a', 'libmp3lame',
233
- '-b:a', '128k', '/tmp/narration.mp3', '-y', '-loglevel', 'quiet'])
234
- print(f" ✅ Narration: {os.path.getsize('/tmp/narration.mp3'):,} bytes")
245
+ wav_path = os.path.join(TMP, 'narration.wav')
246
+ mp3_path = os.path.join(TMP, 'narration.mp3')
247
+ audio_available = False
248
+ try:
249
+ engine = pyttsx3.init()
250
+ engine.setProperty('rate', 140)
251
+ engine.save_to_file(narration_text, wav_path)
252
+ engine.runAndWait()
253
+ subprocess.run(['ffmpeg', '-i', wav_path, '-c:a', 'libmp3lame',
254
+ '-b:a', '128k', mp3_path, '-y', '-loglevel', 'quiet'])
255
+ audio_available = True
256
+ print(f" ✅ Narration: {os.path.getsize(mp3_path):,} bytes")
257
+ except Exception as e:
258
+ print(f" ⚠️ TTS unavailable ({e}). Generating silent video.")
235
259
 
236
260
  print("🎬 Step 2: Rendering video frames...")
237
261
  # Build timeline
@@ -254,18 +278,17 @@ def generate_video():
254
278
  print(f" ✅ Clip: {TOTAL_DURATION}s at {FPS}fps")
255
279
 
256
280
  print("🎵 Step 3: Combining video + audio...")
257
- audio = AudioFileClip('/tmp/narration.mp3').with_duration(TOTAL_DURATION)
258
- final = clip.with_audio(audio)
281
+ if audio_available:
282
+ audio = AudioFileClip(mp3_path).with_duration(TOTAL_DURATION)
283
+ final = clip.with_audio(audio)
284
+ else:
285
+ final = clip
259
286
  final.write_videofile(OUTPUT_PATH, fps=FPS, logger=None)
260
287
  size = os.path.getsize(OUTPUT_PATH)
261
288
  print(f" ✅ Video: {size:,} bytes ({size//1024} KB)")
289
+ print(f" ✅ Saved to: {OUTPUT_PATH}")
262
290
 
263
- print("📦 Step 4: Copying to outputs...")
264
- os.makedirs(os.path.dirname(FINAL_OUTPUT), exist_ok=True)
265
- shutil.copy(OUTPUT_PATH, FINAL_OUTPUT)
266
- print(f" ✅ Saved to: {FINAL_OUTPUT}")
267
-
268
- return FINAL_OUTPUT
291
+ return OUTPUT_PATH
269
292
 
270
293
  if __name__ == "__main__":
271
294
  result = generate_video()