screenkit 0.0.0

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +4 -0
  3. data/.github/FUNDING.yml +4 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  5. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  6. data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
  8. data/.github/dependabot.yml +15 -0
  9. data/.github/workflows/ruby-tests.yml +51 -0
  10. data/.gitignore +12 -0
  11. data/.rubocop.yml +14 -0
  12. data/CHANGELOG.md +16 -0
  13. data/CODE_OF_CONDUCT.md +74 -0
  14. data/CONTRIBUTING.md +80 -0
  15. data/DOCUMENTATION.md +972 -0
  16. data/Gemfile +5 -0
  17. data/LICENSE.md +20 -0
  18. data/README.md +49 -0
  19. data/Rakefile +15 -0
  20. data/bin/console +16 -0
  21. data/bin/setup +10 -0
  22. data/exe/screenkit +5 -0
  23. data/lib/screen_kit.rb +79 -0
  24. data/lib/screenkit/anchor.rb +19 -0
  25. data/lib/screenkit/animation_filters.rb +114 -0
  26. data/lib/screenkit/banner.rb +46 -0
  27. data/lib/screenkit/callout/styles/base.rb +101 -0
  28. data/lib/screenkit/callout/styles/default.rb +144 -0
  29. data/lib/screenkit/callout/styles/inline_block.rb +123 -0
  30. data/lib/screenkit/callout/text_style.rb +44 -0
  31. data/lib/screenkit/callout.rb +98 -0
  32. data/lib/screenkit/cli/base.rb +24 -0
  33. data/lib/screenkit/cli/episode.rb +78 -0
  34. data/lib/screenkit/cli/root.rb +73 -0
  35. data/lib/screenkit/cli.rb +9 -0
  36. data/lib/screenkit/config/base.rb +51 -0
  37. data/lib/screenkit/config/episode.rb +42 -0
  38. data/lib/screenkit/config/project.rb +47 -0
  39. data/lib/screenkit/content_type.rb +12 -0
  40. data/lib/screenkit/core_ext/json.rb +47 -0
  41. data/lib/screenkit/core_ext/string.rb +28 -0
  42. data/lib/screenkit/exporter/demotape.rb +26 -0
  43. data/lib/screenkit/exporter/episode.rb +565 -0
  44. data/lib/screenkit/exporter/image.rb +31 -0
  45. data/lib/screenkit/exporter/intro.rb +261 -0
  46. data/lib/screenkit/exporter/outro.rb +183 -0
  47. data/lib/screenkit/exporter/segment.rb +258 -0
  48. data/lib/screenkit/exporter/video.rb +33 -0
  49. data/lib/screenkit/generators/episode/config.yml.erb +106 -0
  50. data/lib/screenkit/generators/episode/content/001.tape +4 -0
  51. data/lib/screenkit/generators/episode/scripts/001.txt +1 -0
  52. data/lib/screenkit/generators/episode.rb +31 -0
  53. data/lib/screenkit/generators/project/Gemfile.erb +7 -0
  54. data/lib/screenkit/generators/project/resources/backtracks/default.aac +0 -0
  55. data/lib/screenkit/generators/project/resources/fonts/opensans/OFL.txt +93 -0
  56. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Bold.ttf +0 -0
  57. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-BoldItalic.ttf +0 -0
  58. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBold.ttf +0 -0
  59. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBoldItalic.ttf +0 -0
  60. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Italic.ttf +0 -0
  61. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Light.ttf +0 -0
  62. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-LightItalic.ttf +0 -0
  63. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Medium.ttf +0 -0
  64. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-MediumItalic.ttf +0 -0
  65. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Regular.ttf +0 -0
  66. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-SemiBold.ttf +0 -0
  67. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-SemiBoldItalic.ttf +0 -0
  68. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Bold.ttf +0 -0
  69. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-BoldItalic.ttf +0 -0
  70. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBold.ttf +0 -0
  71. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  72. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Italic.ttf +0 -0
  73. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Light.ttf +0 -0
  74. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-LightItalic.ttf +0 -0
  75. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Medium.ttf +0 -0
  76. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-MediumItalic.ttf +0 -0
  77. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Regular.ttf +0 -0
  78. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBold.ttf +0 -0
  79. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  80. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Bold.ttf +0 -0
  81. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  82. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  83. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  84. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Italic.ttf +0 -0
  85. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Light.ttf +0 -0
  86. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  87. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Medium.ttf +0 -0
  88. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  89. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Regular.ttf +0 -0
  90. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  91. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  92. data/lib/screenkit/generators/project/resources/fonts/opensans/README.txt +100 -0
  93. data/lib/screenkit/generators/project/resources/images/logo.png +0 -0
  94. data/lib/screenkit/generators/project/resources/images/watermark.png +0 -0
  95. data/lib/screenkit/generators/project/resources/sounds/chime.mp3 +0 -0
  96. data/lib/screenkit/generators/project/resources/sounds/pop.mp3 +0 -0
  97. data/lib/screenkit/generators/project/resources/sounds/whoosh.mp3 +0 -0
  98. data/lib/screenkit/generators/project/screenkit.yml +189 -0
  99. data/lib/screenkit/generators/project.rb +34 -0
  100. data/lib/screenkit/logfile.rb +33 -0
  101. data/lib/screenkit/parallel_processor.rb +50 -0
  102. data/lib/screenkit/path_lookup.rb +27 -0
  103. data/lib/screenkit/resources/mute.mp3 +0 -0
  104. data/lib/screenkit/resources/transparent.png +0 -0
  105. data/lib/screenkit/schema_validator.rb +14 -0
  106. data/lib/screenkit/schemas/callouts/default.json +44 -0
  107. data/lib/screenkit/schemas/callouts/inline_block.json +20 -0
  108. data/lib/screenkit/schemas/episode.json +74 -0
  109. data/lib/screenkit/schemas/project.json +37 -0
  110. data/lib/screenkit/schemas/refs/anchor.json +17 -0
  111. data/lib/screenkit/schemas/refs/animation.json +7 -0
  112. data/lib/screenkit/schemas/refs/background.json +14 -0
  113. data/lib/screenkit/schemas/refs/callout.json +27 -0
  114. data/lib/screenkit/schemas/refs/color.json +7 -0
  115. data/lib/screenkit/schemas/refs/directory.json +20 -0
  116. data/lib/screenkit/schemas/refs/intro.json +30 -0
  117. data/lib/screenkit/schemas/refs/logo.json +26 -0
  118. data/lib/screenkit/schemas/refs/outro.json +26 -0
  119. data/lib/screenkit/schemas/refs/position.json +38 -0
  120. data/lib/screenkit/schemas/refs/scenes.json +27 -0
  121. data/lib/screenkit/schemas/refs/size.json +19 -0
  122. data/lib/screenkit/schemas/refs/sound.json +36 -0
  123. data/lib/screenkit/schemas/refs/spacing.json +22 -0
  124. data/lib/screenkit/schemas/refs/text_style.json +18 -0
  125. data/lib/screenkit/schemas/refs/transition.json +15 -0
  126. data/lib/screenkit/schemas/refs/tts.json +47 -0
  127. data/lib/screenkit/schemas/refs/watermark.json +18 -0
  128. data/lib/screenkit/schemas/tts/elevenlabs.json +67 -0
  129. data/lib/screenkit/schemas/tts/say.json +16 -0
  130. data/lib/screenkit/shell.rb +58 -0
  131. data/lib/screenkit/sound.rb +44 -0
  132. data/lib/screenkit/spacing.rb +23 -0
  133. data/lib/screenkit/spinner.rb +39 -0
  134. data/lib/screenkit/transition.rb +16 -0
  135. data/lib/screenkit/tts/eleven_labs.rb +51 -0
  136. data/lib/screenkit/tts/say.rb +31 -0
  137. data/lib/screenkit/utils.rb +87 -0
  138. data/lib/screenkit/version.rb +5 -0
  139. data/lib/screenkit/watermark.rb +34 -0
  140. data/lib/screenkit.rb +3 -0
  141. data/screenkit.gemspec +56 -0
  142. metadata +426 -0
data/DOCUMENTATION.md ADDED
@@ -0,0 +1,972 @@
1
+ # ScreenKit Documentation
2
+
3
+ **Terminal to screencast, simplified**
4
+
5
+ ScreenKit is a Ruby-based tool for creating professional screencasts from
6
+ terminal recordings. It automates the process of combining intro/outro scenes,
7
+ terminal recordings (via [demotapes](https://github.com/fnando/demotape)),
8
+ voiceovers, background music, callouts (lower thirds), and watermarks into
9
+ polished video content.
10
+
11
+ ## Table of Contents
12
+
13
+ - [Installation](#installation)
14
+ - [Quick Start](#quick-start)
15
+ - [CLI Commands](#cli-commands)
16
+ - [Project Configuration](#project-configuration)
17
+ - [Episode Configuration](#episode-configuration)
18
+ - [Scenes](#scenes)
19
+ - [Callouts](#callouts)
20
+ - [Text-to-Speech (TTS)](#text-to-speech-tts)
21
+ - [Animations](#animations)
22
+ - [File Structure](#file-structure)
23
+ - [Advanced Features](#advanced-features)
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ gem install screenkit
31
+ ```
32
+
33
+ Or add to your Gemfile:
34
+
35
+ ```ruby
36
+ gem "screenkit"
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Create a New Project
44
+
45
+ ```bash
46
+ screenkit new my-screencast
47
+ cd my-screencast
48
+ ```
49
+
50
+ This generates:
51
+
52
+ - `screenkit.yml` - Project configuration
53
+ - `episodes/` - Directory for episodes
54
+ - `resources/` - Images, sounds, fonts, etc.
55
+ - `output/` - Generated videos
56
+
57
+ ### 2. Create a New Episode
58
+
59
+ ```bash
60
+ screenkit episode new --title "My First Episode"
61
+ ```
62
+
63
+ This creates an episode directory with:
64
+
65
+ - `config.yml` - Episode configuration
66
+ - `content/` - Terminal recording files (`.tape` files)
67
+ - `scripts/` - Voiceover scripts (`.txt` files)
68
+ - `voiceovers/` - Generated voiceover audio
69
+ - `resources/` - Episode-specific resources
70
+
71
+ ### 3. Export the Episode
72
+
73
+ ```bash
74
+ screenkit episode export --dir episodes/001-my-first-episode
75
+ ```
76
+
77
+ The final video is saved to the `output/` directory.
78
+
79
+ ---
80
+
81
+ ## CLI Commands
82
+
83
+ ### Root Commands
84
+
85
+ #### `screenkit new PATH`
86
+
87
+ Create a new ScreenKit project at the specified path.
88
+
89
+ ```bash
90
+ screenkit new my-project
91
+ ```
92
+
93
+ #### `screenkit callout`
94
+
95
+ Generate a standalone callout PNG for testing.
96
+
97
+ **Options:**
98
+
99
+ - `--type` (required) - Callout type (e.g., `info`, `warning`)
100
+ - `--title` (required) - Callout title text
101
+ - `--body` (required) - Callout body text
102
+ - `--output` - Output path for PNG (optional)
103
+
104
+ ```bash
105
+ screenkit callout --type info --title "Note" --body "This is important" --output callout.png
106
+ ```
107
+
108
+ ### Episode Commands
109
+
110
+ #### `screenkit episode new`
111
+
112
+ Create a new episode.
113
+
114
+ **Options:**
115
+
116
+ - `--title` (required) - Episode title
117
+ - `--config` - Path to project config file (default: `screenkit.yml`)
118
+
119
+ ```bash
120
+ screenkit episode new --title "Getting Started with Ruby"
121
+ ```
122
+
123
+ #### `screenkit episode export`
124
+
125
+ Export an episode to video.
126
+
127
+ **Options:**
128
+
129
+ - `--dir` (required) - Episode directory path
130
+ - `--voice-api-key` - API key for TTS service (e.g., ElevenLabs)
131
+ - `--overwrite` - Overwrite existing exported files (default: `false`)
132
+ - `--match-segment` - Only export segments matching this string
133
+ - `--output-dir` - Custom output directory path
134
+ - `--banner` - Display ScreenKit banner (default: `true`)
135
+ - `--require` - Additional Ruby files to require (can be used multiple times)
136
+ - `--config` - Path to project config file (default: `screenkit.yml`)
137
+
138
+ ```bash
139
+ screenkit episode export --dir episodes/001-getting-started
140
+ screenkit episode export --dir episodes/001-getting-started --overwrite
141
+ screenkit episode export --dir episodes/001-getting-started --match-segment "002"
142
+
143
+ # Load custom TTS engines or callout styles
144
+ screenkit episode export --dir episodes/001-getting-started --require ./lib/custom_tts.rb
145
+ screenkit episode export --dir episodes/001-getting-started --require ./lib/custom_tts.rb --require ./lib/custom_callout.rb
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Project Configuration
151
+
152
+ The `screenkit.yml` file defines project-wide settings.
153
+
154
+ ### Schema
155
+
156
+ ```yaml
157
+ schema: 1 # Required: Schema version (currently 1)
158
+ ```
159
+
160
+ ### Directory Structure
161
+
162
+ ```yaml
163
+ # Episode directory naming pattern
164
+ # Supports placeholders: %{episode_number}, %{episode_slug}, %{date}
165
+ # Use %<episode_number>03d for padded numbers (e.g., 001)
166
+ episode_dir: episodes/%<episode_number>03d-%{episode_slug}
167
+
168
+ # Output directory for generated videos
169
+ # Supports placeholder: %{episode_dirname}
170
+ output_dir: output/%{episode_dirname}
171
+
172
+ # Resource directories (searched in order)
173
+ resources_dir:
174
+ - .
175
+ - "%{episode_dir}"
176
+ - resources
177
+ - "%{episode_dir}/resources"
178
+ - ~/Library/Fonts
179
+ - /usr/share/fonts
180
+ ```
181
+
182
+ ### Background Music
183
+
184
+ ```yaml
185
+ # String: Path to specific backtrack file
186
+ backtrack: resources/music/background.mp3
187
+
188
+ # String: Directory - random file selected
189
+ backtrack: backtracks/
190
+
191
+ # Boolean: Disable backtrack
192
+ backtrack: false
193
+ ```
194
+
195
+ ### Watermark
196
+
197
+ ```yaml
198
+ # Simple string path
199
+ watermark: watermark.png
200
+
201
+ # Detailed configuration
202
+ watermark:
203
+ path: "watermark.png"
204
+ anchor: [right, bottom] # Positioning
205
+ margin: 100 # Margin from edge (pixels)
206
+ opacity: 0.8 # 0.0 to 1.0
207
+
208
+ # Disable watermark
209
+ watermark: false
210
+ ```
211
+
212
+ ### Callout Definitions
213
+
214
+ Define reusable callout styles:
215
+
216
+ ```yaml
217
+ callouts:
218
+ default:
219
+ background_color: "#ffff00"
220
+ shadow: "#2242d3" # Color string or false
221
+
222
+ # Title styling
223
+ title_style:
224
+ color: "#000000"
225
+ size: 40
226
+ font_path: opensans/OpenSans-ExtraBold.ttf
227
+
228
+ # Body styling
229
+ body_style:
230
+ color: "#000000"
231
+ size: 32
232
+ font_path: opensans/OpenSans-Semibold.ttf
233
+
234
+ # Positioning
235
+ anchor: [left, bottom] # [horizontal, vertical]
236
+ margin: 100 # Margin from anchor edge
237
+ padding: 50 # Internal padding
238
+
239
+ # Animation
240
+ animation: fade # "fade" or "slide"
241
+
242
+ # Transitions
243
+ in_transition:
244
+ duration: 0.4
245
+ sound: pop.mp3
246
+
247
+ out_transition:
248
+ duration: 0.3
249
+ sound:
250
+ path: pop.mp3
251
+ volume: 0.7 # 0.0 to 1.0
252
+
253
+ warning:
254
+ background_color: "#ff6600"
255
+ # ... additional callout types
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Episode Configuration
261
+
262
+ The `episodes/*/config.yml` file defines episode-specific settings.
263
+
264
+ ### Basic Settings
265
+
266
+ ```yaml
267
+ # Required: Episode title (displayed in intro)
268
+ title: "Creating Screencasts with ScreenKit"
269
+ ```
270
+
271
+ ### Episode-Specific Overrides
272
+
273
+ Episodes can override project settings:
274
+
275
+ ```yaml
276
+ # Override backtrack
277
+ backtrack: resources/custom-music.mp3
278
+ backtrack: false # Disable for this episode
279
+
280
+ # Override TTS settings
281
+ tts:
282
+ engine: elevenlabs
283
+ voice_id: custom_voice_id
284
+
285
+ # Override watermark
286
+ watermark: false
287
+ ```
288
+
289
+ ### Callout Instances
290
+
291
+ Define when and where callouts appear:
292
+
293
+ ```yaml
294
+ callouts:
295
+ - type: info # References callout defined in project config
296
+ title: "ScreenKit"
297
+ body: "Visit https://github.com/fnando/screenkit"
298
+ starts_at: 3 # Start time (seconds or HH:MM:SS)
299
+ duration: 5 # Duration in seconds
300
+ width: 600 # Optional: Override width (pixels or percentage)
301
+
302
+ - type: warning
303
+ title: "Important"
304
+ body: "Remember to save your work"
305
+ starts_at: "00:01:30" # HH:MM:SS format
306
+ duration: 4
307
+ width: "50%" # Percentage of screen width
308
+ ```
309
+
310
+ #### Time Formats
311
+
312
+ - **Seconds**: `starts_at: 90` (90 seconds)
313
+ - **HH:MM:SS**: `starts_at: "00:01:30"` (1 minute 30 seconds)
314
+ - **Duration**: Always in seconds or time units (`5s`, `2m`, `1h`)
315
+
316
+ ---
317
+
318
+ ## Scenes
319
+
320
+ ScreenKit supports three scene types: **intro**, **outro**, and **segment**.
321
+
322
+ ### Intro Scene
323
+
324
+ Opening scene with logo and title.
325
+
326
+ ```yaml
327
+ scenes:
328
+ intro:
329
+ duration: 5.5 # Scene duration (seconds)
330
+ fade_in: 0 # Fade-in duration
331
+ fade_out: 0.5 # Fade-out duration
332
+
333
+ # Background (color or image path)
334
+ background: "#100f50"
335
+ background: resources/intro-bg.png
336
+
337
+ # Title text
338
+ title:
339
+ x: 100 # X position (pixels or "center")
340
+ y: 300 # Y position (pixels or "center")
341
+ font_path: opensans/OpenSans-ExtraBold.ttf
342
+ size: 144 # Font size
343
+ color: "#ffffff"
344
+
345
+ # Logo
346
+ logo:
347
+ path: logo.png
348
+ x: 100 # X position (pixels or "center")
349
+ y: 200 # Y position (pixels or "center")
350
+ width: 300 # Width in pixels (height auto-calculated)
351
+
352
+ # Sound effect
353
+ sound: chime.mp3
354
+ sound: false # Disable sound
355
+ ```
356
+
357
+ ### Outro Scene
358
+
359
+ Closing scene with logo.
360
+
361
+ ```yaml
362
+ scenes:
363
+ outro:
364
+ duration: 5.5
365
+ fade_in: 0.5
366
+ fade_out: 0.5
367
+ background: "#100f50"
368
+
369
+ logo:
370
+ path: logo.png
371
+ x: center # Center horizontally
372
+ y: center # Center vertically
373
+ width: 300
374
+
375
+ sound: chime.mp3
376
+ ```
377
+
378
+ ### Segment Scene
379
+
380
+ Main content configuration.
381
+
382
+ ```yaml
383
+ scenes:
384
+ segment:
385
+ # Crossfade duration between video segments
386
+ crossfade_duration: 0.5
387
+ ```
388
+
389
+ ---
390
+
391
+ ## Callouts
392
+
393
+ Callouts (also known as lower thirds) are informational overlays that appear
394
+ during the video.
395
+
396
+ ### Callout Styles
397
+
398
+ ScreenKit provides two built-in callout styles:
399
+
400
+ #### Default Style
401
+
402
+ The default style displays a title and body in a box with optional shadow.
403
+
404
+ ```yaml
405
+ callouts:
406
+ info:
407
+ style: default # Optional: defaults to "default"
408
+ background_color: "#ffff00" # Background color (hex)
409
+
410
+ # Shadow
411
+ shadow: "#2242d3" # Simple shadow (color)
412
+ shadow: # Detailed shadow
413
+ color: "#2242d3"
414
+ offset: 10 # Shadow offset in pixels
415
+ shadow: false # No shadow
416
+
417
+ # Text Styles
418
+ title_style:
419
+ color: "#000000"
420
+ size: 40
421
+ font_path: opensans/OpenSans-ExtraBold.ttf
422
+
423
+ body_style:
424
+ color: "#000000"
425
+ size: 32
426
+ font_path: opensans/OpenSans-Semibold.ttf
427
+
428
+ # Layout
429
+ padding: 50 # Internal padding (pixels)
430
+ margin: 100 # Margin from edge (pixels)
431
+ anchor: [left, bottom] # Position anchor point
432
+
433
+ # Animation
434
+ animation: fade # "fade" or "slide"
435
+
436
+ # Transitions
437
+ in_transition:
438
+ duration: 0.4 # Transition duration (seconds)
439
+ sound: pop.mp3 # Sound effect
440
+
441
+ out_transition:
442
+ duration: 0.3
443
+ sound:
444
+ path: pop.mp3
445
+ volume: 0.7 # Volume (0.0 to 1.0)
446
+ ```
447
+
448
+ **Usage in episode:**
449
+
450
+ ```yaml
451
+ callouts:
452
+ - type: info
453
+ title: "ScreenKit"
454
+ body: "Visit https://github.com/fnando/screenkit"
455
+ starts_at: 3
456
+ duration: 5
457
+ ```
458
+
459
+ #### Inline Block Style
460
+
461
+ The inline block style displays text with a background highlight on each line,
462
+ similar to syntax highlighting or code comments. Perfect for displaying code
463
+ snippets, commands, or short inline text.
464
+
465
+ ```yaml
466
+ callouts:
467
+ code:
468
+ style: inline_block
469
+ background_color: "#000000" # Background color (hex)
470
+
471
+ # Text Style (single style for all text)
472
+ text_style:
473
+ color: "#ffffff"
474
+ size: 40
475
+ font_path: opensans/OpenSans-ExtraBold.ttf
476
+
477
+ # Layout
478
+ padding: 20 # Padding around text (pixels)
479
+ margin: 100 # Margin from edge (pixels)
480
+ anchor: [left, center] # Position anchor point
481
+ width: 600 # Maximum width (pixels)
482
+
483
+ # Animation
484
+ animation: fade # "fade" or "slide"
485
+
486
+ # Transitions
487
+ in_transition:
488
+ duration: 0.4
489
+ sound: false
490
+
491
+ out_transition:
492
+ duration: 0.3
493
+ sound: false
494
+ ```
495
+
496
+ **Usage in episode:**
497
+
498
+ ```yaml
499
+ callouts:
500
+ # Single line text (auto-wrapped)
501
+ - type: code
502
+ text: "npm install screenkit --save-dev"
503
+ starts_at: 5
504
+ duration: 4
505
+
506
+ # Multi-line text (explicit line breaks)
507
+ - type: code
508
+ text: |
509
+ git add .
510
+ git commit -m "Update"
511
+ git push
512
+ starts_at: 15
513
+ duration: 6
514
+ ```
515
+
516
+ **Key differences from default style:**
517
+
518
+ - Uses `text` instead of `title` and `body`
519
+ - Only one `text_style` (no separate title/body styles)
520
+ - No `shadow` option
521
+ - Each line gets its own background rectangle
522
+ - Text can include manual line breaks (`\n`)
523
+ - Auto-wraps based on `width` if no line breaks present
524
+
525
+ ### Anchor Positions
526
+
527
+ Anchor determines where the callout is positioned:
528
+
529
+ **Horizontal**: `left`, `center`, `right`
530
+ **Vertical**: `top`, `center`, `bottom`
531
+
532
+ ```yaml
533
+ anchor: [left, top] # Top-left corner
534
+ anchor: [center, center] # Center of screen
535
+ anchor: [right, bottom] # Bottom-right corner
536
+ ```
537
+
538
+ ### Position Values
539
+
540
+ - **Pixels**: `x: 100`, `y: 200`
541
+ - **Center**: `x: center`, `y: center`
542
+
543
+ ### Size Values
544
+
545
+ - **Pixels**: `width: 600`
546
+ - **Percentage**: `width: "50%"` (percentage of screen width)
547
+
548
+ ---
549
+
550
+ ## Text-to-Speech (TTS)
551
+
552
+ ScreenKit supports multiple TTS engines for voiceovers.
553
+
554
+ ### macOS `say` Engine
555
+
556
+ Uses the built-in macOS `say` command.
557
+
558
+ ```yaml
559
+ tts:
560
+ engine: say
561
+ voice: Alex # Optional: Voice name
562
+ rate: 150 # Words per minute (optional)
563
+ ```
564
+
565
+ ### ElevenLabs Engine
566
+
567
+ Professional AI voice synthesis.
568
+
569
+ ```yaml
570
+ tts:
571
+ engine: elevenlabs
572
+ voice_id: "56AoDkrOh6qfVPDXZ7Pt" # Required: ElevenLabs voice ID
573
+ language_code: en # 2-letter language code
574
+
575
+ # Optional: Voice settings
576
+ voice_settings:
577
+ speed: 0.9 # Speech speed (default: 1.0)
578
+ stability: 0.5 # Voice stability (0.0 - 1.0)
579
+ similarity: 0.75 # Voice similarity (0.0 - 1.0)
580
+ style: 0.0 # Speaking style (0.0+)
581
+
582
+ # Optional: Output format
583
+ output_format: mp3_44100_128
584
+
585
+ # Optional: Model ID
586
+ model_id: eleven_monolingual_v1
587
+ ```
588
+
589
+ #### ElevenLabs Output Formats
590
+
591
+ - MP3: `mp3_22050_32`, `mp3_44100_128`, `mp3_44100_192`
592
+ - PCM: `pcm_16000`, `pcm_24000`, `pcm_44100`
593
+ - Opus: `opus_48000_32`, `opus_48000_64`
594
+ - Others: `ulaw_8000`, `alaw_8000`
595
+
596
+ ### Disable TTS
597
+
598
+ ```yaml
599
+ tts: false
600
+ ```
601
+
602
+ ### Custom TTS Engines
603
+
604
+ You can create custom TTS engines by placing them in the `ScreenKit::TTS`
605
+ module. Custom engines must implement the `generate` method:
606
+
607
+ ```ruby
608
+ module ScreenKit
609
+ module TTS
610
+ class CustomEngine
611
+ include Shell
612
+ extend SchemaValidator
613
+
614
+ # Optional: Define schema path for validation
615
+ def self.schema_path
616
+ ScreenKit.root_dir.join("screenkit/schemas/tts/custom_engine.json")
617
+ end
618
+
619
+ def initialize(**options)
620
+ @options = options
621
+ # Validate options against schema if defined
622
+ self.class.validate!(@options) if respond_to?(:validate!)
623
+ end
624
+
625
+ def generate(text:, output_path:, log_path: nil)
626
+ # Generate audio file from text
627
+ # Write output to output_path
628
+ # Optionally log to log_path
629
+
630
+ # Example implementation:
631
+ # File.write(output_path, generated_audio_data)
632
+ end
633
+ end
634
+ end
635
+ end
636
+ ```
637
+
638
+ **Configuration:**
639
+
640
+ ```yaml
641
+ tts:
642
+ engine: custom_engine # Camelized to CustomEngine
643
+ # Add your custom options here
644
+ api_key: your_api_key
645
+ custom_option: value
646
+ ```
647
+
648
+ The engine name is camelized (e.g., `custom_engine` → `CustomEngine`,
649
+ `google_cloud` → `GoogleCloud`) and loaded as
650
+ `ScreenKit::TTS::#{CamelizedName}`.
651
+
652
+ ---
653
+
654
+ ## Animations
655
+
656
+ ScreenKit supports two animation types for callouts:
657
+
658
+ ### Fade Animation
659
+
660
+ Callouts fade in and out with opacity changes.
661
+
662
+ ```yaml
663
+ animation: fade
664
+ in_transition:
665
+ duration: 0.4
666
+ out_transition:
667
+ duration: 0.3
668
+ ```
669
+
670
+ **Behavior:**
671
+
672
+ - Fades in from transparent to opaque
673
+ - Remains visible
674
+ - Fades out to transparent
675
+
676
+ ### Slide Animation
677
+
678
+ Callouts slide in from the left and slide out to the left with blur effects.
679
+
680
+ ```yaml
681
+ animation: slide
682
+ in_transition:
683
+ duration: 0.4
684
+ out_transition:
685
+ duration: 0.3
686
+ ```
687
+
688
+ **Behavior:**
689
+
690
+ - Slides in from left (off-screen to position) with blur
691
+ - Sharp focus when static
692
+ - Slides out to left with blur
693
+
694
+ ---
695
+
696
+ ## File Structure
697
+
698
+ ### Episode Directory Structure
699
+
700
+ ```
701
+ episodes/001-episode-name/
702
+ ├── config.yml # Episode configuration
703
+ ├── content/ # Terminal recordings
704
+ │ ├── 001.tape # VHS tape files
705
+ │ ├── 002.tape
706
+ │ └── ...
707
+ ├── scripts/ # Voiceover scripts
708
+ │ ├── 001.txt # Text for TTS
709
+ │ ├── 002.txt
710
+ │ └── ...
711
+ ├── voiceovers/ # Generated audio
712
+ │ ├── 001.aiff
713
+ │ ├── 002.aiff
714
+ │ └── ...
715
+ └── resources/ # Episode-specific resources
716
+ ├── images/
717
+ ├── sounds/
718
+ └── fonts/
719
+ ```
720
+
721
+ ### VHS Tape Files
722
+
723
+ ScreenKit uses [VHS](https://github.com/charmbracelet/vhs) tape files for
724
+ terminal recordings:
725
+
726
+ ```tape
727
+ # content/001.tape
728
+ Type "echo 'Hello, World!'"
729
+ Sleep 100ms
730
+ Enter
731
+ Sleep 2s
732
+ ```
733
+
734
+ ### Script Files
735
+
736
+ Plain text files for voiceover generation:
737
+
738
+ ```txt
739
+ # scripts/001.txt
740
+ Welcome to this tutorial on ScreenKit.
741
+ Today we'll learn how to create amazing screencasts.
742
+ ```
743
+
744
+ ### Naming Convention
745
+
746
+ Files are matched by number:
747
+
748
+ - `content/001.tape` → `scripts/001.txt` → `voiceovers/001.aiff`
749
+ - Segments are processed in numerical order
750
+ - Missing scripts create silent segments
751
+
752
+ ---
753
+
754
+ ## Advanced Features
755
+
756
+ ### Resource Lookup
757
+
758
+ Resources are searched in order from `resources_dir`:
759
+
760
+ 1. Current directory
761
+ 2. Episode directory
762
+ 3. Project resources
763
+ 4. Episode resources
764
+ 5. System font directories
765
+
766
+ Reference resources by partial path:
767
+
768
+ ```yaml
769
+ font_path: opensans/OpenSans-Bold.ttf # Found in resources/fonts/
770
+ ```
771
+
772
+ ### Placeholders
773
+
774
+ #### Project Configuration
775
+
776
+ - `%{episode_number}` - Episode number (1, 2, 3...)
777
+ - `%<episode_number>03d` - Padded episode number (001, 002, 003...)
778
+ - `%{episode_slug}` - URL-friendly episode title
779
+ - `%{date}` - Current date (`YYYY-MM-DD`)
780
+ - `%{episode_dirname}` - Episode directory name
781
+
782
+ ```yaml
783
+ episode_dir: episodes/%<episode_number>03d-%{episode_slug}
784
+ output_dir: output/%{episode_dirname}
785
+ ```
786
+
787
+ ### Color Formats
788
+
789
+ Colors support hex format with optional alpha channel:
790
+
791
+ ```yaml
792
+ # 6-character hex (RGB)
793
+ color: "#ffffff" # White
794
+
795
+ # 8-character hex (RGBA) - includes alpha channel for transparency
796
+ color: "#ffffff80" # White with 50% transparency
797
+ ```
798
+
799
+ Alpha channel values range from `00` (fully transparent) to `ff` (fully opaque).
800
+
801
+ ### Sound Configuration
802
+
803
+ Three ways to specify sounds:
804
+
805
+ ```yaml
806
+ # String path
807
+ sound: pop.mp3
808
+
809
+ # Detailed configuration
810
+ sound:
811
+ path: pop.mp3
812
+ volume: 0.7 # 0.0 to 1.0
813
+
814
+ # Disable
815
+ sound: false
816
+ ```
817
+
818
+ ### Shadow Configuration
819
+
820
+ ```yaml
821
+ # Simple color
822
+ shadow: "#2242d3"
823
+
824
+ # Detailed configuration
825
+ shadow:
826
+ color: "#2242d3"
827
+ offset: 10 # Pixels
828
+
829
+ # Disable
830
+ shadow: false
831
+ ```
832
+
833
+ ### Background Configuration
834
+
835
+ ```yaml
836
+ # Solid color
837
+ background: "#100f50"
838
+
839
+ # Image path
840
+ background: resources/background.png
841
+ ```
842
+
843
+ ### Spacing Configuration
844
+
845
+ Spacing values accept:
846
+
847
+ ```yaml
848
+ margin: 100 # Single value (all sides)
849
+ padding: 50 # Single value (all sides)
850
+ ```
851
+
852
+ ### Text Wrapping
853
+
854
+ Long titles are automatically wrapped based on approximate text width. Manual
855
+ line breaks in the episode title are preserved.
856
+
857
+ ---
858
+
859
+ ## Schema Validation
860
+
861
+ ScreenKit validates configurations against JSON schemas:
862
+
863
+ - **Project**: `lib/screenkit/schemas/project.json`
864
+ - **Episode**: `lib/screenkit/schemas/episode.json`
865
+ - **Callouts**: `lib/screenkit/schemas/callouts/*.json`
866
+ - **TTS**: `lib/screenkit/schemas/tts/*.json`
867
+
868
+ Use the `yaml-language-server` comment for IDE support:
869
+
870
+ ```yaml
871
+ # yaml-language-server: $schema=../../schemas/project.json
872
+ ```
873
+
874
+ ---
875
+
876
+ ## Export Process
877
+
878
+ When exporting an episode, ScreenKit:
879
+
880
+ 1. **Validates** project and episode configurations
881
+ 2. **Generates voiceovers** from script files (if TTS enabled)
882
+ 3. **Renders terminal recordings** from tape files using VHS
883
+ 4. **Combines segments** with crossfade transitions
884
+ 5. **Adds intro/outro** scenes
885
+ 6. **Overlays callouts** with animations
886
+ 7. **Applies watermark** (if configured)
887
+ 8. **Mixes background music** (if configured)
888
+ 9. **Outputs final video** to the output directory
889
+
890
+ ### Segment Filtering
891
+
892
+ Export only specific segments:
893
+
894
+ ```bash
895
+ screenkit episode export --dir episodes/001-test --match-segment "002"
896
+ ```
897
+
898
+ This processes only segments matching "002" (e.g., `002.tape`, `003.tape` won't
899
+ be processed).
900
+
901
+ ---
902
+
903
+ ## Tips & Best Practices
904
+
905
+ ### Performance
906
+
907
+ - Use `--match-segment` during development to export only changed segments
908
+ - Place frequently-accessed resources in the first `resources_dir` entry
909
+ - Use high-resolution logos (2x target size) for best quality
910
+
911
+ ### Audio
912
+
913
+ - Keep voiceover scripts concise and natural
914
+ - Test TTS voices before bulk generation
915
+ - Background music volume is automatically adjusted to not overpower voiceovers
916
+
917
+ ### Visuals
918
+
919
+ - Use consistent branding across callouts
920
+ - Test callout timing with `screenkit callout` command
921
+ - PNG images with transparency work best for logos and watermarks
922
+
923
+ ### Organization
924
+
925
+ - Use numbered segments (001, 002, etc.) for proper ordering
926
+ - Keep episode-specific resources in episode directories
927
+ - Use descriptive callout type names (`info`, `warning`, `tip`, etc.)
928
+
929
+ ---
930
+
931
+ ## Troubleshooting
932
+
933
+ ### Common Issues
934
+
935
+ **"Gem not found" error:**
936
+
937
+ ```bash
938
+ bundle install
939
+ bundle exec screenkit ...
940
+ ```
941
+
942
+ **"Schema validation failed":**
943
+
944
+ - Check YAML syntax
945
+ - Verify required fields are present
946
+ - Use schema hints with `yaml-language-server`
947
+
948
+ **Missing resources:**
949
+
950
+ - Check `resources_dir` configuration
951
+ - Verify file paths are relative to resource directories
952
+ - Use absolute paths for system resources
953
+
954
+ **TTS not working:**
955
+
956
+ - For ElevenLabs: Set `--voice-api-key` or `ELEVENLABS_API_KEY` env variable
957
+ - For macOS `say`: Verify voice name with `say -v ?`
958
+
959
+ ---
960
+
961
+ ## Contributing
962
+
963
+ For development and contribution guidelines, see:
964
+
965
+ - [CONTRIBUTING.md](CONTRIBUTING.md)
966
+ - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
967
+
968
+ ---
969
+
970
+ ## License
971
+
972
+ MIT License - See [LICENSE.md](LICENSE.md)