screenkit 0.0.0 → 0.0.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +44 -0
  3. data/.github/workflows/ruby-tests.yml +21 -1
  4. data/Brewfile +5 -0
  5. data/CHANGELOG.md +13 -1
  6. data/CONTRIBUTING.md +25 -0
  7. data/DOCUMENTATION.md +132 -48
  8. data/Dockerfile +107 -0
  9. data/lib/screen_kit.rb +17 -16
  10. data/lib/screenkit/animation_filters.rb +16 -0
  11. data/lib/screenkit/callout/styles/base.rb +12 -0
  12. data/lib/screenkit/callout/styles/file_copy.rb +26 -0
  13. data/lib/screenkit/callout/styles/inline_block.rb +6 -9
  14. data/lib/screenkit/callout/styles/{default.rb → shadow_block.rb} +9 -11
  15. data/lib/screenkit/callout.rb +1 -1
  16. data/lib/screenkit/cli/episode.rb +1 -1
  17. data/lib/screenkit/config/episode.rb +6 -0
  18. data/lib/screenkit/config/project.rb +5 -2
  19. data/lib/screenkit/duration.rb +5 -0
  20. data/lib/screenkit/exporter/demotape.rb +20 -2
  21. data/lib/screenkit/exporter/episode.rb +42 -16
  22. data/lib/screenkit/exporter/intro.rb +4 -4
  23. data/lib/screenkit/exporter/outro.rb +5 -5
  24. data/lib/screenkit/exporter/segment.rb +270 -74
  25. data/lib/screenkit/generators/episode/callouts/001.yml +12 -0
  26. data/lib/screenkit/generators/episode/config.yml.erb +8 -8
  27. data/lib/screenkit/generators/project/screenkit.yml +123 -27
  28. data/lib/screenkit/schema_validator.rb +1 -1
  29. data/lib/screenkit/schemas/callout_styles/file_copy.json +8 -0
  30. data/lib/screenkit/schemas/callout_styles/inline_block.json +20 -0
  31. data/lib/screenkit/schemas/{callouts/default.json → callout_styles/shadow_block.json} +2 -2
  32. data/lib/screenkit/schemas/callouts/inline_block.json +20 -11
  33. data/lib/screenkit/schemas/callouts/shadow_block.json +39 -0
  34. data/lib/screenkit/schemas/episode.json +7 -55
  35. data/lib/screenkit/schemas/project.json +6 -6
  36. data/lib/screenkit/schemas/refs/anchor.json +1 -1
  37. data/lib/screenkit/schemas/refs/animation.json +1 -1
  38. data/lib/screenkit/schemas/refs/background.json +1 -1
  39. data/lib/screenkit/schemas/refs/{callout.json → callout_style.json} +6 -8
  40. data/lib/screenkit/schemas/refs/color.json +1 -1
  41. data/lib/screenkit/schemas/refs/demotape.json +48 -0
  42. data/lib/screenkit/schemas/refs/demotape_themes.json +367 -0
  43. data/lib/screenkit/schemas/refs/directory.json +1 -1
  44. data/lib/screenkit/schemas/refs/duration.json +9 -0
  45. data/lib/screenkit/schemas/refs/intro.json +4 -16
  46. data/lib/screenkit/schemas/refs/logo.json +1 -1
  47. data/lib/screenkit/schemas/refs/outro.json +4 -16
  48. data/lib/screenkit/schemas/refs/position.json +1 -1
  49. data/lib/screenkit/schemas/refs/scenes.json +2 -6
  50. data/lib/screenkit/schemas/refs/size.json +1 -1
  51. data/lib/screenkit/schemas/refs/sound.json +1 -1
  52. data/lib/screenkit/schemas/refs/spacing.json +1 -1
  53. data/lib/screenkit/schemas/refs/text_style.json +1 -1
  54. data/lib/screenkit/schemas/refs/transition.json +2 -6
  55. data/lib/screenkit/schemas/refs/tts.json +4 -41
  56. data/lib/screenkit/schemas/refs/tts_builtin.json +23 -0
  57. data/lib/screenkit/schemas/refs/watermark.json +1 -1
  58. data/lib/screenkit/schemas/tts/elevenlabs.json +11 -2
  59. data/lib/screenkit/schemas/tts/espeak.json +26 -0
  60. data/lib/screenkit/schemas/tts/say.json +12 -2
  61. data/lib/screenkit/shell.rb +6 -0
  62. data/lib/screenkit/time_formatter.rb +14 -0
  63. data/lib/screenkit/tts/base.rb +21 -0
  64. data/lib/screenkit/tts/eleven_labs.rb +8 -9
  65. data/lib/screenkit/tts/espeak.rb +30 -0
  66. data/lib/screenkit/tts/say.rb +5 -6
  67. data/lib/screenkit/utils.rb +6 -0
  68. data/lib/screenkit/version.rb +1 -1
  69. metadata +29 -46
  70. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Bold.ttf +0 -0
  71. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-BoldItalic.ttf +0 -0
  72. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBoldItalic.ttf +0 -0
  73. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Italic.ttf +0 -0
  74. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Light.ttf +0 -0
  75. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-LightItalic.ttf +0 -0
  76. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Medium.ttf +0 -0
  77. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-MediumItalic.ttf +0 -0
  78. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Regular.ttf +0 -0
  79. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-SemiBoldItalic.ttf +0 -0
  80. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Bold.ttf +0 -0
  81. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-BoldItalic.ttf +0 -0
  82. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBold.ttf +0 -0
  83. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  84. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Italic.ttf +0 -0
  85. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Light.ttf +0 -0
  86. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-LightItalic.ttf +0 -0
  87. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Medium.ttf +0 -0
  88. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-MediumItalic.ttf +0 -0
  89. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Regular.ttf +0 -0
  90. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBold.ttf +0 -0
  91. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  92. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Bold.ttf +0 -0
  93. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  94. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  95. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  96. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Italic.ttf +0 -0
  97. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Light.ttf +0 -0
  98. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  99. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Medium.ttf +0 -0
  100. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  101. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Regular.ttf +0 -0
  102. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  103. data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
  104. /data/lib/screenkit/generators/project/resources/fonts/{opensans → open-sans}/OFL.txt +0 -0
  105. /data/lib/screenkit/generators/project/resources/fonts/{opensans → open-sans}/OpenSans-ExtraBold.ttf +0 -0
  106. /data/lib/screenkit/generators/project/resources/fonts/{opensans → open-sans}/OpenSans-SemiBold.ttf +0 -0
  107. /data/lib/screenkit/generators/project/resources/fonts/{opensans → open-sans}/README.txt +0 -0
@@ -40,7 +40,7 @@ module ScreenKit
40
40
  end
41
41
 
42
42
  def voiceover_path
43
- return episode.mute_sound_path unless episode.tts?
43
+ return episode.mute_sound_path unless episode.tts_available?
44
44
 
45
45
  dir = episode.root_dir.join("voiceovers")
46
46
  dir.glob("#{prefix}.{#{ContentType.audio.join(',')}}").first ||
@@ -75,15 +75,20 @@ module ScreenKit
75
75
  .export(video_path)
76
76
  when *ContentType.demotape
77
77
  Exporter::Demotape
78
- .new(demotape_path: content_path, log_path:)
79
- .export(video_path)
78
+ .new(
79
+ demotape_path: content_path,
80
+ log_path:,
81
+ options: episode.demotape_options
82
+ ).export(video_path)
80
83
  else
81
84
  raise "Unsupported content type: #{content_path.extname}"
82
85
  end
83
86
  end
84
87
 
85
88
  def crossfade_duration
86
- episode.scenes.fetch(:segment).fetch(:crossfade_duration, 0.5)
89
+ Duration.parse(
90
+ episode.scenes.fetch(:segment).fetch(:crossfade_duration, 0.5)
91
+ )
87
92
  end
88
93
 
89
94
  def merge_audio_and_video(log_path:)
@@ -112,79 +117,19 @@ module ScreenKit
112
117
 
113
118
  audio_mix_inputs = ["[1:a]"]
114
119
  animation_duration = 0.2
120
+ current_input_index = 2 # Start after video (0) and voiceover (1)
115
121
 
116
122
  callouts.each_with_index do |callout, index|
117
- type = callout[:type].to_sym
118
- callout_config = episode.project_config.callouts[type]
119
- in_sound = Sound.new(input: callout_config[:in_transition][:sound],
120
- source: episode.source)
121
- out_sound = Sound.new(input: callout_config[:out_transition][:sound],
122
- source: episode.source)
123
-
124
- starts_at = callout[:starts_at]
125
- max_duration = [content_duration - 0.2, 0].max
126
- duration = callout[:duration]
127
- .clamp(0, max_duration)
128
- .round(half: :down)
129
- ends_at = starts_at + duration
130
- callout_image_path = episode.output_dir.join("callouts",
131
- "#{prefix}-#{index}.png")
132
- image_width, image_height = image_size(callout_image_path)
133
-
134
- x, y = calculate_position(
135
- anchor: Anchor.new(callout_config[:anchor]),
136
- margin: Spacing.new(callout_config[:margin] || 0),
137
- width: image_width,
138
- height: image_height
139
- )
140
-
141
- inputs += [
142
- "-loop", "1",
143
- "-t", duration,
144
- "-i", callout_image_path,
145
-
146
- # Add sound for transition in
147
- "-i", in_sound.path,
148
-
149
- # Add sound for transition out
150
- "-i", out_sound.path
151
- ]
152
-
153
- input_stream = "v#{index}"
154
- output_stream = "v#{index + 1}"
155
- callout_index = 2 + (index * 3)
156
-
157
- animation_filters = AnimationFilters.new(
158
- content_duration:,
159
- callout_index:,
160
- input_stream:,
161
- output_stream:,
123
+ current_input_index = process_callout(
124
+ callout:,
162
125
  index:,
163
- starts_at:,
164
- ends_at:,
165
- x:,
166
- y:,
126
+ content_duration:,
167
127
  animation_duration:,
168
- image_width:,
169
- image_height:
170
- ).send(callout_config[:animation])
171
-
172
- filters.concat(animation_filters[:video])
173
-
174
- # Delay and mix callout sounds
175
- in_index = callout_index + 1
176
- out_index = callout_index + 2
177
-
178
- filters << "[#{in_index}:a]volume=#{in_sound.volume}," \
179
- "adelay=#{(starts_at * 1000).to_i}|" \
180
- "#{(starts_at * 1000).to_i}[in_#{index}]"
181
- filters << "[#{out_index}:a]volume=#{out_sound.volume}," \
182
- "adelay=#{(animation_filters[:out_start] * 1000).to_i}|" \
183
- "#{(animation_filters[:out_start] * 1000).to_i}" \
184
- "[out_#{index}]"
185
-
186
- audio_mix_inputs << "[in_#{index}]"
187
- audio_mix_inputs << "[out_#{index}]"
128
+ inputs:,
129
+ filters:,
130
+ audio_mix_inputs:,
131
+ current_input_index:
132
+ )
188
133
  end
189
134
 
190
135
  # Mix all audio streams (voiceover + all sounds)
@@ -235,7 +180,7 @@ module ScreenKit
235
180
  def create_voiceover(log_path:)
236
181
  return if voiceover_path&.file? && !episode.options.overwrite
237
182
  return unless script_path.file?
238
- return unless episode.tts?
183
+ return unless episode.tts_available?
239
184
 
240
185
  FileUtils.mkdir_p(voiceover_path.dirname)
241
186
 
@@ -253,6 +198,257 @@ module ScreenKit
253
198
 
254
199
  format(path.to_s, prefix:) if path
255
200
  end
201
+
202
+ def process_callout(
203
+ callout:,
204
+ index:,
205
+ content_duration:,
206
+ animation_duration:,
207
+ inputs:,
208
+ filters:,
209
+ audio_mix_inputs:,
210
+ current_input_index:
211
+ )
212
+ type = callout[:type].to_sym
213
+ callout_config = episode.callout_styles[type]
214
+ in_sound = Sound.new(input: callout_config[:in_transition][:sound],
215
+ source: episode.source)
216
+ out_sound = Sound.new(input: callout_config[:out_transition][:sound],
217
+ source: episode.source)
218
+
219
+ starts_at = TimeFormatter.parse(callout[:starts_at])
220
+ max_duration = [content_duration - 0.2, 0].max
221
+ duration = Duration.parse(callout[:duration])
222
+ .clamp(0, max_duration)
223
+ .round(half: :down)
224
+ ends_at = starts_at + duration
225
+ callout_duration = ends_at - starts_at
226
+
227
+ callout_path = find_callout_path(index)
228
+ video_callout = video_callout?(callout_path)
229
+ has_video_audio = has_audio?(callout_path)
230
+
231
+ callout_width, callout_height = image_size(callout_path)
232
+ x, y = calculate_callout_position(
233
+ video_callout:,
234
+ callout_config:,
235
+ callout_width:,
236
+ callout_height:
237
+ )
238
+
239
+ callout_index, current_input_index = add_callout_inputs(
240
+ inputs:,
241
+ current_input_index:,
242
+ callout_path:,
243
+ video_callout:,
244
+ has_video_audio:,
245
+ duration:
246
+ )
247
+
248
+ in_sound_index, out_sound_index, current_input_index =
249
+ add_transition_sound_inputs(
250
+ inputs:,
251
+ current_input_index:,
252
+ video_callout:,
253
+ in_sound:,
254
+ out_sound:
255
+ )
256
+
257
+ animation_filters = add_video_filters(
258
+ filters:,
259
+ index:,
260
+ callout_index:,
261
+ callout_config:,
262
+ video_callout:,
263
+ content_duration:,
264
+ starts_at:,
265
+ ends_at:,
266
+ x:,
267
+ y:,
268
+ animation_duration:,
269
+ callout_width:,
270
+ callout_height:
271
+ )
272
+
273
+ add_audio_filters(
274
+ filters:,
275
+ audio_mix_inputs:,
276
+ index:,
277
+ callout_index:,
278
+ video_callout:,
279
+ has_video_audio:,
280
+ starts_at:,
281
+ callout_duration:,
282
+ in_sound:,
283
+ out_sound:,
284
+ in_sound_index:,
285
+ out_sound_index:,
286
+ animation_filters:
287
+ )
288
+
289
+ current_input_index
290
+ end
291
+
292
+ def find_callout_path(index)
293
+ callout_path = episode.output_dir.join("callouts").glob(
294
+ "#{prefix}-#{index}.{png,#{ContentType.video.join(',')}}"
295
+ ).first
296
+
297
+ raise "Callout file not found for #{prefix}-#{index}" unless callout_path
298
+
299
+ callout_path
300
+ end
301
+
302
+ def video_callout?(callout_path)
303
+ ContentType.video.include?(callout_path.extname.delete_prefix("."))
304
+ end
305
+
306
+ def calculate_callout_position(
307
+ video_callout:,
308
+ callout_config:,
309
+ callout_width:,
310
+ callout_height:
311
+ )
312
+ # For video callouts, ignore anchor/margin and position at 0,0
313
+ # (assumes videos are already properly sized and positioned)
314
+ if video_callout
315
+ [0, 0]
316
+ else
317
+ calculate_position(
318
+ anchor: Anchor.new(callout_config[:anchor]),
319
+ margin: Spacing.new(callout_config[:margin] || 0),
320
+ width: callout_width,
321
+ height: callout_height
322
+ )
323
+ end
324
+ end
325
+
326
+ def add_callout_inputs(
327
+ inputs:,
328
+ current_input_index:,
329
+ callout_path:,
330
+ video_callout:,
331
+ has_video_audio:,
332
+ duration:
333
+ )
334
+ callout_index = current_input_index
335
+
336
+ if video_callout
337
+ # Don't use -t for videos, let them play naturally
338
+ inputs << "-i" << callout_path
339
+ current_input_index += 1
340
+
341
+ # Add mute audio if video has no audio
342
+ unless has_video_audio
343
+ inputs << "-t" << duration << "-i" << episode.mute_sound_path
344
+ current_input_index += 1
345
+ end
346
+ else
347
+ inputs << "-loop" << "1" << "-t" << duration << "-i" << callout_path
348
+ current_input_index += 1
349
+ end
350
+
351
+ [callout_index, current_input_index]
352
+ end
353
+
354
+ def add_transition_sound_inputs(
355
+ inputs:,
356
+ current_input_index:,
357
+ video_callout:,
358
+ in_sound:,
359
+ out_sound:
360
+ )
361
+ # Add transition sounds (only for non-video callouts)
362
+ return [nil, nil, current_input_index] if video_callout
363
+
364
+ in_sound_index = current_input_index
365
+ inputs << "-i" << in_sound.path
366
+ current_input_index += 1
367
+
368
+ out_sound_index = current_input_index
369
+ inputs << "-i" << out_sound.path
370
+ current_input_index += 1
371
+
372
+ [in_sound_index, out_sound_index, current_input_index]
373
+ end
374
+
375
+ def add_video_filters(
376
+ filters:,
377
+ index:,
378
+ callout_index:,
379
+ callout_config:,
380
+ video_callout:,
381
+ content_duration:,
382
+ starts_at:,
383
+ ends_at:,
384
+ x:,
385
+ y:,
386
+ animation_duration:,
387
+ callout_width:,
388
+ callout_height:
389
+ )
390
+ input_stream = "v#{index}"
391
+ output_stream = "v#{index + 1}"
392
+ animation_method = video_callout ? :video : callout_config[:animation]
393
+
394
+ animation_filters = AnimationFilters.new(
395
+ content_duration:,
396
+ callout_index:,
397
+ input_stream:,
398
+ output_stream:,
399
+ index:,
400
+ starts_at:,
401
+ ends_at:,
402
+ x:,
403
+ y:,
404
+ animation_duration:,
405
+ image_width: callout_width,
406
+ image_height: callout_height
407
+ ).send(animation_method)
408
+
409
+ filters.concat(animation_filters[:video])
410
+
411
+ animation_filters
412
+ end
413
+
414
+ def add_audio_filters(
415
+ filters:,
416
+ audio_mix_inputs:,
417
+ index:,
418
+ callout_index:,
419
+ video_callout:,
420
+ has_video_audio:,
421
+ starts_at:,
422
+ callout_duration:,
423
+ in_sound:,
424
+ out_sound:,
425
+ in_sound_index:,
426
+ out_sound_index:,
427
+ animation_filters:
428
+ )
429
+ # Mix video audio if present
430
+ if video_callout && has_video_audio
431
+ filters <<
432
+ "[#{callout_index}:a]atrim=end=#{callout_duration}," \
433
+ "asetpts=PTS-STARTPTS,adelay=#{(starts_at * 1000).to_i}|" \
434
+ "#{(starts_at * 1000).to_i}[video_audio_#{index}]"
435
+ audio_mix_inputs << "[video_audio_#{index}]"
436
+ end
437
+
438
+ # For non-video callouts, add transition sounds
439
+ return if video_callout
440
+
441
+ filters << "[#{in_sound_index}:a]volume=#{in_sound.volume}," \
442
+ "adelay=#{(starts_at * 1000).to_i}|" \
443
+ "#{(starts_at * 1000).to_i}[in_#{index}]"
444
+ filters << "[#{out_sound_index}:a]volume=#{out_sound.volume}," \
445
+ "adelay=#{(animation_filters[:out_start] * 1000).to_i}|" \
446
+ "#{(animation_filters[:out_start] * 1000).to_i}" \
447
+ "[out_#{index}]"
448
+
449
+ audio_mix_inputs << "[in_#{index}]"
450
+ audio_mix_inputs << "[out_#{index}]"
451
+ end
256
452
  end
257
453
  end
258
454
  end
@@ -0,0 +1,12 @@
1
+ ---
2
+ - type: default
3
+ title: ScreenKit
4
+ body: https://github.com/fnando/screenkit
5
+ duration: 5s
6
+ starts_at: 00:00:02
7
+ width: 800
8
+
9
+ - type: inline_block
10
+ text: gem install screenkit
11
+ duration: 5s
12
+ starts_at: 00:00:02
@@ -1,5 +1,5 @@
1
1
  ---
2
- # yaml-language-server: $schema=../../../screenkit/lib/screenkit/schemas/episode.json
2
+ # yaml-language-server: $schema=https://screenkit.dev/schemas/episode.json
3
3
 
4
4
  # The episode title. When there are no linebreaks, they will be inferred based
5
5
  # on the approximate text width.
@@ -10,7 +10,7 @@ title: "CREATING SCREENCASTS WITH SCREENKIT"
10
10
  #
11
11
  # The project's configuration can define the callout structure, while the
12
12
  # episode configuration can define the content and timing.
13
- callouts:
13
+ callouts_styles:
14
14
  - type: info
15
15
  title: "ScreenKit"
16
16
  body: "Visit https://github.com/fnando/screenkit to learn more."
@@ -45,7 +45,7 @@ callouts:
45
45
  # title:
46
46
  # x: 100
47
47
  # y: 300
48
- # font_path: lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBold.ttf
48
+ # font_path: open-sans/OpenSans-ExtraBold.ttf
49
49
  # size: 144
50
50
  # color: "#ffffff"
51
51
  #
@@ -53,7 +53,7 @@ callouts:
53
53
  # # Works best with a transparent PNG file in high resolution (at least 2x the
54
54
  # # size it'll be displayed).
55
55
  # logo:
56
- # path: lib/screenkit/generators/project/resources/images/logo.png
56
+ # path: images/logo.png
57
57
  # x: 100
58
58
  # y: 200
59
59
  # width: 200
@@ -62,7 +62,7 @@ callouts:
62
62
  # # The sound to be played along with the logo in the intro.
63
63
  # # This must correspond to a sound file in the sounds directory, with any
64
64
  # # audio extension (e.g. mp3, m4a, wav).
65
- # sound: lib/screenkit/generators/project/resources/sounds/chime.mp3
65
+ # sound: sounds/chime.mp3
66
66
  #
67
67
  # outro:
68
68
  # # The duration of the outro scene in seconds.
@@ -78,11 +78,11 @@ callouts:
78
78
  # background: "#100f50"
79
79
  #
80
80
  # # The sound to be played along with the logo in the outro.
81
- # sound: lib/screenkit/generators/project/resources/sounds/chime.mp3
81
+ # sound: sounds/chime.mp3
82
82
  #
83
83
  # # The logo to be displayed in the video outro.
84
84
  # logo:
85
- # path: lib/screenkit/generators/project/resources/images/logo.png
85
+ # path: images/logo.png
86
86
  # x: center
87
87
  # y: center
88
88
  # width: 500
@@ -95,7 +95,7 @@ callouts:
95
95
  # When present, this will have higher precedence over the project's
96
96
  # configuration, so you can have different intro/outro segments for each
97
97
  # episode.
98
- # voice:
98
+ # tts:
99
99
  # engine: elevenlabs
100
100
  # voice_id: 56AoDkrOh6qfVPDXZ7Pt
101
101
  # language_code: en
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  # yaml-language-server: $schema=../../schemas/project.json
3
+ ## yaml-language-server: $schema=https://screenkit.dev/schemas/project.json
3
4
 
4
5
  # ScreenKit project configuration file.
5
6
  schema: 1
@@ -76,19 +77,35 @@ watermark:
76
77
  margin: 100
77
78
 
78
79
  # TTS configuration.
79
- # To disable voiceover entirely, set `tts: false`.
80
- # tts:
81
- # engine: elevenlabs
82
- # voice_id: 56AoDkrOh6qfVPDXZ7Pt
83
- # language_code: en
84
- # voice_settings:
85
- # speed: 0.9
86
- # stability: 0.5
87
- # similarity: 0.75
88
- # style: 0.0
80
+ # To disable voiceover entirely, set `tts: false` or disable all engines
81
+ # individually.
82
+ #
83
+ # You can set up multiple TTS engines to be used as fallbacks in case one fails.
84
+ # Each TTS engine has its own detection mechanism. For instance, say and espeak
85
+ # checks for a binary with the same name. ElevenLabs checks for the presence of
86
+ # `--tts-api-key`.
89
87
  tts:
90
- engine: say
91
- rate: 150
88
+ # Apple Say TTS engine configuration.
89
+ - engine: say
90
+ rate: 150
91
+ enabled: true
92
+
93
+ # eSpeak TTS engine configuration.
94
+ - engine: espeak
95
+ rate: 150
96
+ voice: en-us
97
+ enabled: true
98
+
99
+ # Eleven Labs TTS engine configuration.
100
+ - engine: eleven_labs
101
+ enabled: true
102
+ voice_id: 56AoDkrOh6qfVPDXZ7Pt
103
+ language_code: en
104
+ voice_settings:
105
+ speed: 0.9
106
+ stability: 0.5
107
+ similarity: 0.75
108
+ style: 0.0
92
109
 
93
110
  # Scene configurations define how different scenes in the video are rendered.
94
111
  # There are three types of scenes: intro, outro, and segment.
@@ -98,13 +115,13 @@ tts:
98
115
  scenes:
99
116
  intro:
100
117
  # The duration of the intro scene in seconds.
101
- duration: 5.5
118
+ duration: 5.5s
102
119
 
103
120
  # The fade-in duration in seconds.
104
- fade_in: 0
121
+ fade_in: 0s
105
122
 
106
123
  # The fade-out duration in seconds.
107
- fade_out: 0.5
124
+ fade_out: 0.5s
108
125
 
109
126
  # The background color/image of the intro scene.
110
127
  background: "#100f50"
@@ -113,7 +130,7 @@ scenes:
113
130
  title:
114
131
  x: 100
115
132
  y: 300
116
- font_path: opensans/OpenSans-ExtraBold.ttf
133
+ font_path: open-sans/OpenSans-ExtraBold.ttf
117
134
  size: 144
118
135
  color: "#ffffff"
119
136
 
@@ -133,13 +150,13 @@ scenes:
133
150
 
134
151
  outro:
135
152
  # The duration of the outro scene in seconds.
136
- duration: 5.5
153
+ duration: 5.5s
137
154
 
138
155
  # The fade-in duration in seconds.
139
- fade_in: 0.5
156
+ fade_in: 0.5s
140
157
 
141
158
  # The fade-out duration in seconds.
142
- fade_out: 0.5
159
+ fade_out: 0.5s
143
160
 
144
161
  # The background color/image of the outro scene.
145
162
  background: "#100f50"
@@ -156,34 +173,113 @@ scenes:
156
173
 
157
174
  segment:
158
175
  # The duration of the crossover transition between segments in seconds.
159
- crossfade_duration: 0.5
176
+ crossfade_duration: 0.5s
177
+
178
+ # Configure [demotape](https://github.com/fnando/demotape).
179
+ demotape:
180
+ # Global speed for typing
181
+ typing_speed: 50ms
182
+
183
+ # Whether to add variability to typing speed
184
+ variable_typing: 0.25
185
+
186
+ # Shell to use for the terminal session
187
+ shell: zsh
188
+
189
+ # Path to a shell rc file to source on startup
190
+ # rc_file:
191
+
192
+ # Do not load the default rc file
193
+ no_rc: false
194
+
195
+ # A built-in theme name or a path to a custom theme JSON file
196
+ theme: default_dark
197
+
198
+ # Font size for the terminal session
199
+ font_size: 32
200
+
201
+ # Font family for the terminal session
202
+ font_family: "'JetBrainsMono Nerd Font Propo', monospace"
203
+
204
+ # Line height for the terminal session
205
+ line_height: 1.2
206
+
207
+ # Whether the cursor blinks
208
+ cursor_blink: true
209
+
210
+ # The width of the cursor when in 'bar' style
211
+ cursor_width: 2
212
+
213
+ # The style of the cursor when the terminal is focused.
214
+ cursor_style: block
215
+
216
+ # Letter spacing for the terminal session
217
+ letter_spacing: 0
218
+
219
+ # Padding around the terminal content. It can be 1, 2, or 4 numbers.
220
+ padding: 50
221
+
222
+ # Loop the GIF infinitely
223
+ loop: true
224
+
225
+ # Delay time (in seconds) before restarting the GIF animation
226
+ loop_delay: 0
227
+
228
+ # Margin around the terminal content. It can be 1, 2, or 4 numbers.
229
+ margin: 0
230
+
231
+ # Color or image to use for the margin around the terminal content
232
+ margin_fill: "#000000"
233
+
234
+ # Border radius for the terminal canvas
235
+ border_radius: 0
236
+
237
+ # Delay (in seconds) before pressing Enter
238
+ run_enter_delay: 1s
239
+
240
+ # Delay (in seconds) after running each command
241
+ run_sleep: 3s
160
242
 
161
243
  # Callout configurations define the appearance and behavior of callouts used
162
244
  # in the video. Callouts are visual elements that highlight important
163
245
  # information. Each callout type can have its own settings for icon, background,
164
246
  # text styles, animations, and sounds.
165
- callouts:
166
- info:
167
- # yaml-language-server: $schema=../../schemas/callouts/default.json
247
+ callout_styles:
248
+ shadow_block:
249
+ # yaml-language-server: $schema=https://screenkit.dev/schemas/callout_styles/default.json
250
+ style: shadow_block
168
251
  background_color: "#ffff00"
169
252
  shadow: "#2242d3"
170
253
  title_style:
171
254
  color: "#000000"
172
255
  size: 40
173
- font_path: opensans/OpenSans-ExtraBold.ttf
256
+ font_path: open-sans/OpenSans-ExtraBold.ttf
174
257
  body_style:
175
258
  color: "#000000"
176
259
  size: 32
177
- font_path: opensans/OpenSans-Semibold.ttf
260
+ font_path: open-sans/OpenSans-Semibold.ttf
178
261
  margin: 100
179
262
  padding: 50
180
263
  anchor: [left, bottom]
181
264
  animation: fade
182
265
  in_transition:
183
- duration: 0.4
266
+ duration: 0.4s
184
267
  sound: pop.mp3
185
268
  out_transition:
186
- duration: 0.3
269
+ duration: 0.3s
187
270
  sound:
188
271
  path: pop.mp3
189
272
  volume: 0.7
273
+
274
+ inline_block:
275
+ # yaml-language-server: $schema=https://screenkit.dev/schemas/callout_styles/inline_block.json
276
+ style: inline_block
277
+ anchor: [left, bottom]
278
+ margin: 100
279
+ animation: fade
280
+ in_transition:
281
+ duration: 0.4s
282
+ sound: pop.mp3
283
+ out_transition:
284
+ duration: 0.4s
285
+ sound: pop.mp3
@@ -6,7 +6,7 @@ module ScreenKit
6
6
  errors = JSON::Validator
7
7
  .fully_validate("file://#{schema_path}", attributes)
8
8
 
9
- return if errors.empty?
9
+ return true if errors.empty?
10
10
 
11
11
  raise ArgumentError, "Invalid attributes: #{errors.first}"
12
12
  end
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://screenkit.dev/schemas/callout_styles/file_copy.json",
4
+ "title": "File Copy Callout",
5
+ "type": "object",
6
+ "required": ["file_path"],
7
+ "properties": { "file_path": { "type": "string", "format": "uri" } }
8
+ }