screenkit 0.0.8 → 0.0.9

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -1
  3. data/DOCUMENTATION.md +104 -1
  4. data/lib/screenkit/callout/styles/file_copy.rb +1 -2
  5. data/lib/screenkit/callout/styles/inline_block.rb +13 -5
  6. data/lib/screenkit/callout/styles/shadow_block.rb +1 -2
  7. data/lib/screenkit/callout/styles/social.rb +304 -0
  8. data/lib/screenkit/callout/text_style.rb +2 -2
  9. data/lib/screenkit/callout.rb +1 -1
  10. data/lib/screenkit/config/base.rb +8 -1
  11. data/lib/screenkit/config/episode.rb +1 -1
  12. data/lib/screenkit/config/project.rb +5 -1
  13. data/lib/screenkit/exporter/intro.rb +2 -3
  14. data/lib/screenkit/exporter/outro.rb +14 -4
  15. data/lib/screenkit/exporter/video.rb +6 -4
  16. data/lib/screenkit/generators/project/screenkit.yml +16 -0
  17. data/lib/screenkit/resources/callout_styles/social/blog.png +0 -0
  18. data/lib/screenkit/resources/callout_styles/social/bsky.png +0 -0
  19. data/lib/screenkit/resources/callout_styles/social/discord.png +0 -0
  20. data/lib/screenkit/resources/callout_styles/social/dribbble.png +0 -0
  21. data/lib/screenkit/resources/callout_styles/social/github.png +0 -0
  22. data/lib/screenkit/resources/callout_styles/social/instagram.png +0 -0
  23. data/lib/screenkit/resources/callout_styles/social/linkedin.png +0 -0
  24. data/lib/screenkit/resources/callout_styles/social/mastodon.png +0 -0
  25. data/lib/screenkit/resources/callout_styles/social/snap.png +0 -0
  26. data/lib/screenkit/resources/callout_styles/social/soundcloud.png +0 -0
  27. data/lib/screenkit/resources/callout_styles/social/spotify.png +0 -0
  28. data/lib/screenkit/resources/callout_styles/social/tiktok.png +0 -0
  29. data/lib/screenkit/resources/callout_styles/social/twitch.png +0 -0
  30. data/lib/screenkit/resources/callout_styles/social/youtube.png +0 -0
  31. data/lib/screenkit/schemas/callout_styles/social.json +55 -0
  32. data/lib/screenkit/schemas/refs/text_style.json +4 -0
  33. data/lib/screenkit/sound.rb +1 -1
  34. data/lib/screenkit/tts/espeak.rb +1 -1
  35. data/lib/screenkit/tts/say.rb +1 -1
  36. data/lib/screenkit/version.rb +1 -1
  37. data/lib/screenkit.rb +6 -1
  38. data/screenkit.gemspec +1 -0
  39. metadata +35 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9432b0beeacae6f145c48e9935c18d92c62c08c69ea7177a3d282db689b0d9d5
4
- data.tar.gz: 9cbf970a8998f0cb3bd5778489faa0ecc521b800862c1d8c8112cb11e0232bcd
3
+ metadata.gz: 60085249659092fa92d7fab2c913f5e8d8fc3b8d9ce0ac467322ef590a08e5f9
4
+ data.tar.gz: 318d53f5ba88c1b8f2edd6d19ac263f2fcee0df7e7b5cb639f760b867be6e4ad
5
5
  SHA512:
6
- metadata.gz: 4b952dc0d719ccdf1496b2ee0cb430e9ba8271e5e9373f1859d8132ed68febf86daa9ff063cbc05a75a314de32d8a279eb4336021344caf70e9cc297e8eaac37
7
- data.tar.gz: 4660532603407564ab3315a0d21d6af592d5fad3179cbd7bafade4438e97708a95ba2d9eab34fd61507aee02b58c90bed5d487f7affa4951351a1d3442c37d46
6
+ metadata.gz: 565c78ea1e6701ee66087d22f4e62614ab765e1e625b50bdbeb9a953b3b4d7980dc6f8e7cad87250e1760f2923b10747c83c867f59347d1b4c37089d5ac87bb0
7
+ data.tar.gz: df2439bbb02d0bf47ecc80abdeafdd285f5c1f63782ce2a4411cb326a7d6b5edb6ddc79b36969555ad699439f4acea557265f52e1fc4a52be1d8e2d75e614a73
data/CHANGELOG.md CHANGED
@@ -11,7 +11,12 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
- # v0.0.8
14
+ ## v0.0.9
15
+
16
+ - [Added] Add social callouts.
17
+ - [Added] Add align support for inline block callouts.
18
+
19
+ ## v0.0.8
15
20
 
16
21
  - [Fixed] Further improvements to support extensions.
17
22
 
data/DOCUMENTATION.md CHANGED
@@ -474,7 +474,109 @@ during the video.
474
474
 
475
475
  ### Callout Styles
476
476
 
477
- ScreenKit provides two built-in callout styles:
477
+ ScreenKit provides a few built-in callout styles:
478
+
479
+ #### Social Style
480
+
481
+ The social callout style displays social media information with icons.
482
+
483
+ ```yaml
484
+ callout_styles:
485
+ # Basic setup to use presets
486
+ social:
487
+ style: social
488
+ anchor: [left, bottom]
489
+ margin: 100
490
+ animation: fade
491
+ in_transition:
492
+ duration: 0.4s
493
+ sound: pop.mp3
494
+ out_transition:
495
+ duration: 0.4s
496
+ sound: pop.mp3
497
+
498
+ # Custom setup
499
+ custom_social:
500
+ style: social
501
+ background_color: "#E0D300"
502
+ label_style:
503
+ size: 24
504
+ color: "#00000088"
505
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
506
+ text_style:
507
+ size: 50
508
+ color: "#000000"
509
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
510
+ label: "CUSTOM"
511
+ icon:
512
+ path: "images/custom.png" # Must be a 160x160 image
513
+ background_color: "#000000"
514
+ anchor: [left, bottom]
515
+ margin: 100
516
+ animation: fade
517
+ in_transition:
518
+ duration: 0.4s
519
+ sound: pop.mp3
520
+ out_transition:
521
+ duration: 0.4s
522
+ sound: pop.mp3
523
+ ```
524
+
525
+ ##### Usage in episode
526
+
527
+ ScreenKit comes with some presets where you don't need to set up anything, but
528
+ you can also create custom callouts.
529
+
530
+ Available presets:
531
+
532
+ - `blog`
533
+ - `bsky`
534
+ - `discord`
535
+ - `dribbble`
536
+ - `github`
537
+ - `linkedin`
538
+ - `mastodon`
539
+ - `snap`
540
+ - `soundcloud`
541
+ - `spotify`
542
+ - `tiktok`
543
+ - `twitch`
544
+ - `youtube`
545
+
546
+ ```yaml
547
+ callouts:
548
+ # Using a preset
549
+ - type: social
550
+ preset: github
551
+ text: "@fnando"
552
+ starts: 1
553
+ duration: 5s
554
+
555
+ # Using a custom callout style
556
+ - type: custom_social
557
+ text: "@fnando"
558
+ starts: 1
559
+ duration: 5s
560
+
561
+ # Defining a custom callout inline
562
+ - type: social
563
+ text: "@fnando"
564
+ starts: 1
565
+ duration: 5s
566
+ background_color: "#E0D300"
567
+ label_style:
568
+ size: 24
569
+ color: "#00000088"
570
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
571
+ text_style:
572
+ size: 50
573
+ color: "#000000"
574
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
575
+ label: "CUSTOM"
576
+ icon:
577
+ path: "images/custom.png" # Must be a 160x160 image
578
+ background_color: "#000000"
579
+ ```
478
580
 
479
581
  #### Shadow Block Style
480
582
 
@@ -552,6 +654,7 @@ callouts_styles:
552
654
  color: "#ffffff"
553
655
  size: 40
554
656
  font_path: open-sans/OpenSans-ExtraBold.ttf
657
+ align: left # Text alignment: left, center, right
555
658
 
556
659
  # Layout
557
660
  padding: 20
@@ -5,8 +5,7 @@ module ScreenKit
5
5
  module Styles
6
6
  class FileCopy < Base
7
7
  def self.schema_path
8
- ScreenKit.root_dir
9
- .join("screenkit/schemas/callout_styles/file_copy.json")
8
+ ScreenKit.root_dir.join("schemas/callout_styles/file_copy.json")
10
9
  end
11
10
 
12
11
  def initialize(source:, **kwargs)
@@ -10,8 +10,7 @@ module ScreenKit
10
10
  :padding, :text, :width
11
11
 
12
12
  def self.schema_path
13
- ScreenKit.root_dir
14
- .join("screenkit/schemas/callout_styles/inline_block.json")
13
+ ScreenKit.root_dir.join("schemas/callout_styles/inline_block.json")
15
14
  end
16
15
 
17
16
  def initialize(source:, **kwargs)
@@ -88,18 +87,27 @@ module ScreenKit
88
87
  image << "xc:none"
89
88
 
90
89
  line_images.each do |path, width, height|
90
+ offset_x = case text_style.align
91
+ when "right"
92
+ max_line_width - width
93
+ when "center"
94
+ (max_line_width - width) / 2
95
+ else
96
+ 0
97
+ end
98
+
91
99
  # Draw rectangle background
92
100
  image << "-fill"
93
101
  image << background_color
94
102
  image << "-draw"
95
- image << "rectangle 0,#{offset_y}," \
96
- "#{width + padding_x}," \
103
+ image << "rectangle #{offset_x},#{offset_y}," \
104
+ "#{width + padding_x + offset_x}," \
97
105
  "#{offset_y + height + padding_y}"
98
106
 
99
107
  # Composite line text
100
108
  image << path
101
109
  image << "-geometry"
102
- image << "+#{padding.top}+#{offset_y + padding.left}"
110
+ image << "+#{padding.left + offset_x}+#{offset_y + padding.top}"
103
111
  image << "-composite"
104
112
  offset_y += padding_y + height
105
113
  end
@@ -10,8 +10,7 @@ module ScreenKit
10
10
  :title, :title_style, :width
11
11
 
12
12
  def self.schema_path
13
- ScreenKit.root_dir
14
- .join("screenkit/schemas/callout_styles/shadow_block.json")
13
+ ScreenKit.root_dir.join("schemas/callout_styles/shadow_block.json")
15
14
  end
16
15
 
17
16
  def initialize(source:, **kwargs)
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mini_magick"
4
+
5
+ module ScreenKit
6
+ class Callout
7
+ module Styles
8
+ class Social < Base
9
+ attr_reader :text, :label, :icon, :label_style, :text_style,
10
+ :background_color
11
+
12
+ LABEL_STYLE = {
13
+ color: "#ffffff88",
14
+ size: 24,
15
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
16
+ }.freeze
17
+
18
+ TEXT_STYLE = {
19
+ color: "#ffffff",
20
+ size: 50,
21
+ font_path: "open-sans/OpenSans-ExtraBold.ttf"
22
+ }.freeze
23
+
24
+ def self.presets
25
+ @presets ||= {
26
+ instagram: {
27
+ label: "INSTAGRAM",
28
+ icon: {
29
+ path: "callout_styles/social/instagram.png",
30
+ background_color: "#C13684"
31
+ },
32
+ label_style: LABEL_STYLE,
33
+ text_style: TEXT_STYLE,
34
+ background_color: "#693050"
35
+ },
36
+
37
+ spotify: {
38
+ label: "SPOTIFY",
39
+ icon: {
40
+ path: "callout_styles/social/spotify.png",
41
+ background_color: "#1cd760"
42
+ },
43
+ label_style: LABEL_STYLE,
44
+ text_style: TEXT_STYLE,
45
+ background_color: "#205C36"
46
+ },
47
+
48
+ bsky: {
49
+ label: "BSKY",
50
+ icon: {
51
+ path: "callout_styles/social/bsky.png",
52
+ background_color: "#0085FF"
53
+ },
54
+ label_style: LABEL_STYLE,
55
+ text_style: TEXT_STYLE,
56
+ background_color: "#004483"
57
+ },
58
+
59
+ linkedin: {
60
+ label: "LINKEDIN",
61
+ icon: {
62
+ path: "callout_styles/social/linkedin.png",
63
+ background_color: "#007EBB"
64
+ },
65
+ label_style: LABEL_STYLE,
66
+ text_style: TEXT_STYLE,
67
+ background_color: "#00486A"
68
+ },
69
+
70
+ dribbble: {
71
+ label: "DRIBBBLE",
72
+ icon: {
73
+ path: "callout_styles/social/dribbble.png",
74
+ background_color: "#EC4989"
75
+ },
76
+ label_style: LABEL_STYLE,
77
+ text_style: TEXT_STYLE,
78
+ background_color: "#7A2245"
79
+ },
80
+
81
+ tiktok: {
82
+ label: "TIKTOK",
83
+ icon: {
84
+ path: "callout_styles/social/tiktok.png",
85
+ background_color: "#000000"
86
+ },
87
+ label_style: LABEL_STYLE,
88
+ text_style: TEXT_STYLE,
89
+ background_color: "#252525"
90
+ },
91
+
92
+ youtube: {
93
+ label: "YOUTUBE",
94
+ icon: {
95
+ path: "callout_styles/social/youtube.png",
96
+ background_color: "#FF0000"
97
+ },
98
+ label_style: LABEL_STYLE,
99
+ text_style: TEXT_STYLE,
100
+ background_color: "#7B1212"
101
+ },
102
+
103
+ snap: {
104
+ label: "SNAP",
105
+ icon: {
106
+ path: "callout_styles/social/snap.png",
107
+ background_color: "#FEFC05"
108
+ },
109
+ label_style: LABEL_STYLE,
110
+ text_style: TEXT_STYLE,
111
+ background_color: "#51511A"
112
+ },
113
+
114
+ mastodon: {
115
+ label: "MASTODON",
116
+ icon: {
117
+ path: "callout_styles/social/mastodon.png",
118
+ background_color: "#3188D4"
119
+ },
120
+ label_style: LABEL_STYLE,
121
+ text_style: TEXT_STYLE,
122
+ background_color: "#1F4566"
123
+ },
124
+
125
+ blog: {
126
+ label: "BLOG",
127
+ icon: {
128
+ path: "callout_styles/social/blog.png",
129
+ background_color: "#5831D4"
130
+ },
131
+ label_style: LABEL_STYLE,
132
+ text_style: TEXT_STYLE,
133
+ background_color: "#321E6F"
134
+ },
135
+
136
+ twitch: {
137
+ label: "TWITCH",
138
+ icon: {
139
+ path: "callout_styles/social/twitch.png",
140
+ background_color: "#9146FF"
141
+ },
142
+ label_style: LABEL_STYLE,
143
+ text_style: TEXT_STYLE,
144
+ background_color: "#461B85"
145
+ },
146
+
147
+ github: {
148
+ label: "GITHUB",
149
+ icon: {
150
+ path: "callout_styles/social/github.png",
151
+ background_color: "#161514"
152
+ },
153
+ label_style: LABEL_STYLE,
154
+ text_style: TEXT_STYLE,
155
+ background_color: "#373737"
156
+ },
157
+
158
+ soundcloud: {
159
+ label: "SOUNDCLOUD",
160
+ icon: {
161
+ path: "callout_styles/social/soundcloud.png",
162
+ background_color: "#FE2401"
163
+ },
164
+ label_style: LABEL_STYLE,
165
+ text_style: TEXT_STYLE,
166
+ background_color: "#6D1507"
167
+ },
168
+
169
+ discord: {
170
+ label: "DISCORD",
171
+ icon: {
172
+ path: "callout_styles/social/discord.png",
173
+ background_color: "#5865F2"
174
+ },
175
+ label_style: LABEL_STYLE,
176
+ text_style: TEXT_STYLE,
177
+ background_color: "#2E389D"
178
+ }
179
+ }
180
+ end
181
+
182
+ def self.schema_path
183
+ ScreenKit.root_dir.join("schemas/callout_styles/social.json")
184
+ end
185
+
186
+ def initialize(source:, text:, preset: nil, **kwargs)
187
+ @text = text
188
+ self.class.validate!(kwargs)
189
+ super
190
+ self.options = self.class.presets.fetch(preset.to_sym) if preset
191
+
192
+ options.each do |key, value|
193
+ value = case key
194
+ when :label_style, :text_style
195
+ TextStyle.new(source:, **hi_res(**value))
196
+ else
197
+ value
198
+ end
199
+
200
+ instance_variable_set(:"@#{key}", value)
201
+ end
202
+ end
203
+
204
+ def as_json(*)
205
+ {}
206
+ end
207
+
208
+ def render
209
+ label_path, label_width, label_height = render_text_image(
210
+ type: "label",
211
+ text: label,
212
+ style: label_style,
213
+ width: 600
214
+ )
215
+
216
+ text_path, text_width, _ = render_text_image(
217
+ type: "label",
218
+ text:,
219
+ style: text_style,
220
+ width: 600
221
+ )
222
+
223
+ sizes = hi_res(
224
+ icon: 132,
225
+ padding: 20,
226
+ content_width: [label_width, text_width].max,
227
+ image_height: 172,
228
+ icon_radius: 30,
229
+ panel_radius: 40
230
+ )
231
+
232
+ image_width = (sizes[:icon] + (sizes[:padding] * 2)) +
233
+ sizes[:padding] +
234
+ (sizes[:content_width] / 2) +
235
+ (sizes[:padding] * 2)
236
+ image_height = sizes[:image_height]
237
+ offset_x = sizes[:icon] + sizes[:padding]
238
+ offset_y = sizes[:padding]
239
+ icon_y = offset_y + sizes[:icon]
240
+
241
+ MiniMagick.convert do |image|
242
+ # Create transparent canvas
243
+ image << "-size"
244
+ image << "#{image_width}x#{image_height}"
245
+ image << "xc:none"
246
+
247
+ # Draw main background
248
+ image << "-fill"
249
+ image << options[:background_color]
250
+ image << "-draw"
251
+ image << "roundrectangle 0,0,#{image_width},#{image_height}," \
252
+ "#{sizes[:panel_radius]},#{sizes[:panel_radius]}"
253
+
254
+ # Draw icon background
255
+ image << "-fill"
256
+ image << icon[:background_color]
257
+ image << "-draw"
258
+ image << "roundrectangle #{sizes[:padding]},#{sizes[:padding]}," \
259
+ "#{offset_x},#{icon_y}," \
260
+ "#{sizes[:icon_radius]},#{sizes[:icon_radius]}"
261
+
262
+ # Draw icon
263
+ icon_path = source.search(icon[:path])
264
+ icon_image = MiniMagick::Image.open(icon_path)
265
+ icon_x = sizes[:padding] + ((sizes[:icon] - icon_image.width) / 2)
266
+ icon_y = offset_y + ((sizes[:icon] - icon_image.height) / 2)
267
+
268
+ image << icon_path
269
+ image << "-geometry"
270
+ image << "+#{icon_x}+#{icon_y}"
271
+ image << "-composite"
272
+
273
+ offset_x += sizes[:padding]
274
+ offset_y += sizes[:padding] / 2
275
+
276
+ # Composite label
277
+ image << label_path
278
+ image << "-geometry"
279
+ image << "+#{offset_x}+#{offset_y}"
280
+ image << "-composite"
281
+
282
+ offset_y += label_height
283
+
284
+ # Composite text
285
+ image << text_path
286
+ image << "-geometry"
287
+ image << "+#{offset_x}+#{offset_y}"
288
+ image << "-composite"
289
+
290
+ image << "PNG:#{output_path}"
291
+ end
292
+
293
+ output_path
294
+ rescue MiniMagick::Error => error
295
+ retry if error.message.include?("No such file or directory")
296
+ raise
297
+ ensure
298
+ remove_file(label_path)
299
+ remove_file(text_path)
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
@@ -3,7 +3,7 @@
3
3
  module ScreenKit
4
4
  class Callout
5
5
  class TextStyle
6
- attr_reader :color, :size, :font_path
6
+ attr_reader :color, :size, :font_path, :align
7
7
 
8
8
  def initialize(source:, **kwargs)
9
9
  @source = source
@@ -37,7 +37,7 @@ module ScreenKit
37
37
  end
38
38
 
39
39
  def as_json(*)
40
- {color:, size:, font_path:, rgb_color:, opacity:}
40
+ {color:, size:, font_path:, rgb_color:, opacity:, align:}
41
41
  end
42
42
  end
43
43
  end
@@ -32,7 +32,7 @@ module ScreenKit
32
32
  :style_props, :style_class, :animation, :source, :log_path
33
33
 
34
34
  def self.schema_path
35
- ScreenKit.root_dir.join("screenkit/schemas/refs/callout_style.json")
35
+ ScreenKit.root_dir.join("schemas/refs/callout_style.json")
36
36
  end
37
37
 
38
38
  def initialize(
@@ -5,12 +5,17 @@ module ScreenKit
5
5
  class Base
6
6
  extend SchemaValidator
7
7
 
8
+ attr_reader :raw_options
9
+
8
10
  def self.load_file(path)
9
11
  unless File.file?(path)
10
12
  raise FileNotFoundError, "Config file not found: #{path}"
11
13
  end
12
14
 
13
- config = YAML.load_file(path, symbolize_names: true)
15
+ template = File.read(path)
16
+ contents = ERB.new(template).result
17
+
18
+ config = YAML.load(contents, symbolize_names: true)
14
19
  load(config)
15
20
  end
16
21
 
@@ -21,6 +26,8 @@ module ScreenKit
21
26
  end
22
27
 
23
28
  def initialize(**kwargs)
29
+ @raw_options = kwargs
30
+
24
31
  kwargs.each do |key, value|
25
32
  value = process(key, value)
26
33
  instance_variable_set(:"@#{key}", value)
@@ -26,7 +26,7 @@ module ScreenKit
26
26
 
27
27
  def self.schema_path
28
28
  @schema_path ||=
29
- ScreenKit.root_dir.join("screenkit/schemas/episode.json")
29
+ ScreenKit.root_dir.join("schemas/episode.json")
30
30
  end
31
31
 
32
32
  def initialize(**)
@@ -32,7 +32,7 @@ module ScreenKit
32
32
 
33
33
  def self.schema_path
34
34
  @schema_path ||=
35
- ScreenKit.root_dir.join("screenkit/schemas/project.json")
35
+ ScreenKit.root_dir.join("schemas/project.json")
36
36
  end
37
37
 
38
38
  private def process(key, value)
@@ -45,6 +45,10 @@ module ScreenKit
45
45
  value
46
46
  end
47
47
  end
48
+
49
+ def to_h
50
+ raw_options
51
+ end
48
52
  end
49
53
  end
50
54
  end
@@ -8,7 +8,7 @@ module ScreenKit
8
8
  extend SchemaValidator
9
9
 
10
10
  def self.schema_path
11
- ScreenKit.root_dir.join("screenkit/schemas/refs/intro.json")
11
+ ScreenKit.root_dir.join("schemas/refs/intro.json")
12
12
  end
13
13
 
14
14
  # The intro scene configuration.
@@ -109,11 +109,10 @@ module ScreenKit
109
109
  # Background layer
110
110
  if background_path&.file?
111
111
  if video_file?(background_path)
112
- # Ensure video is 24fps
113
112
  extname = background_path.extname
114
113
  optimized_path = background_path.sub_ext("_24fps#{extname}")
115
114
 
116
- if (-0.02..0.02).cover?(fps(background_path))
115
+ if Video.right_fps?(background_path)
117
116
  optimized_path = background_path
118
117
  end
119
118
 
@@ -8,7 +8,7 @@ module ScreenKit
8
8
  extend SchemaValidator
9
9
 
10
10
  def self.schema_path
11
- ScreenKit.root_dir.join("screenkit/schemas/refs/outro.json")
11
+ ScreenKit.root_dir.join("schemas/refs/outro.json")
12
12
  end
13
13
 
14
14
  # The outro scene configuration.
@@ -65,7 +65,6 @@ module ScreenKit
65
65
  cmd = [
66
66
  "ffmpeg",
67
67
  *inputs,
68
- "-sws_flags", "lanczos+accurate_rnd+full_chroma_int",
69
68
  "-filter_complex", filters,
70
69
  *maps,
71
70
  "-c:v", "libx264", "-crf", "0", "-pix_fmt", "yuv444p",
@@ -95,15 +94,26 @@ module ScreenKit
95
94
  # Background layer
96
95
  if background_path&.file?
97
96
  if video_file?(background_path)
97
+ extname = background_path.extname
98
+ optimized_path = background_path.sub_ext("_24fps#{extname}")
99
+
100
+ if Video.right_fps?(background_path)
101
+ optimized_path = background_path
102
+ end
103
+
104
+ unless optimized_path.file?
105
+ Video.new(input_path: background_path).export(optimized_path)
106
+ end
107
+
98
108
  # Video background
99
- video_duration = duration(background_path)
109
+ video_duration = duration(optimized_path)
100
110
 
101
111
  # Calculate how many loops we need
102
112
  loops_needed = (duration / video_duration).ceil
103
113
 
104
114
  inputs += [
105
115
  "-stream_loop", (loops_needed - 1).to_s, "-i",
106
- background_path
116
+ optimized_path
107
117
  ]
108
118
 
109
119
  # Scale, crop, then trim to exact duration needed
@@ -4,7 +4,7 @@ module ScreenKit
4
4
  module Exporter
5
5
  class Video
6
6
  include Shell
7
- include Utils
7
+ extend Utils
8
8
 
9
9
  # The path to the input video file.
10
10
  attr_reader :input_path
@@ -17,10 +17,12 @@ module ScreenKit
17
17
  @log_path = log_path
18
18
  end
19
19
 
20
- def export(output_path)
21
- fps = fps(input_path)
20
+ def self.right_fps?(path)
21
+ (-0.02..0.02).cover?(24 - fps(path))
22
+ end
22
23
 
23
- if (-0.02..0.02).cover?(24 - fps)
24
+ def export(output_path)
25
+ if self.class.right_fps?(input_path)
24
26
  FileUtils.cp(input_path, output_path)
25
27
  return
26
28
  end
@@ -41,6 +41,7 @@ resources_dir:
41
41
  - "%{episode_dir}/resources/fonts"
42
42
  - resources
43
43
  - "%{episode_dir}/resources"
44
+ - "<%= ScreenKit.resources_dir %>"
44
45
  - ~/Library/Fonts
45
46
  - /Library/Fonts
46
47
  - /System/Library/Fonts
@@ -272,6 +273,8 @@ demotape:
272
273
  # in the video. Callouts are visual elements that highlight important
273
274
  # information. Each callout type can have its own settings for icon, background,
274
275
  # text styles, animations, and sounds.
276
+ #
277
+ # https://github.com/fnando/screenkit/blob/main/DOCUMENTATION.md#callouts
275
278
  callout_styles:
276
279
  shadow_block:
277
280
  # yaml-language-server: $schema=https://screenkit.dev/schemas/callout_styles/default.json
@@ -311,3 +314,16 @@ callout_styles:
311
314
  out_transition:
312
315
  duration: 0.4s
313
316
  sound: pop.mp3
317
+
318
+ social:
319
+ # yaml-language-server: $schema=https://screenkit.dev/schemas/callout_styles/social.json
320
+ style: social
321
+ anchor: [left, bottom]
322
+ margin: 100
323
+ animation: fade
324
+ in_transition:
325
+ duration: 0.4s
326
+ sound: pop.mp3
327
+ out_transition:
328
+ duration: 0.4s
329
+ sound: pop.mp3
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://screenkit.dev/schemas/callout_styles/shadow_block.json",
4
+ "title": "Social Callout",
5
+ "oneOf": [
6
+ {
7
+ "preset": {
8
+ "type": "string",
9
+ "enum": [
10
+ "spotify",
11
+ "youtube",
12
+ "tiktok",
13
+ "snap",
14
+ "mastodon",
15
+ "blog",
16
+ "linkedin",
17
+ "discord",
18
+ "twitch",
19
+ "github",
20
+ "soundcloud",
21
+ "dribbble"
22
+ ]
23
+ }
24
+ },
25
+ {
26
+ "type": "object",
27
+ "required": ["label", "icon", "label_style", "account_style"],
28
+ "properties": {
29
+ "icon": {
30
+ "type": "object",
31
+ "required": ["path", "background_color"],
32
+ "properties": {
33
+ "path": { "type": "string", "format": "uri" },
34
+ "background_color": { "$ref": "../refs/color.json" }
35
+ }
36
+ },
37
+ "background_color": {
38
+ "$ref": "../refs/color.json"
39
+ },
40
+ "label_style": {
41
+ "$ref": "../refs/text_style.json"
42
+ },
43
+ "account_style": {
44
+ "$ref": "../refs/text_style.json"
45
+ },
46
+ "label": {
47
+ "type": "string"
48
+ },
49
+ "text": {
50
+ "type": "string"
51
+ }
52
+ }
53
+ }
54
+ ]
55
+ }
@@ -13,6 +13,10 @@
13
13
  },
14
14
  "color": {
15
15
  "$ref": "color.json"
16
+ },
17
+ "align": {
18
+ "type": "string",
19
+ "enum": ["left", "center", "right"]
16
20
  }
17
21
  }
18
22
  }
@@ -20,7 +20,7 @@ module ScreenKit
20
20
 
21
21
  case input
22
22
  when FalseClass, nil
23
- @path = ScreenKit.root_dir.join("screenkit/resources/mute.mp3")
23
+ @path = ScreenKit.resources_dir.join("mute.mp3")
24
24
  when Hash
25
25
  {path: nil, volume: 1.0}.merge(input) => {path:, volume:}
26
26
  @path = Pathname(path)
@@ -10,7 +10,7 @@ module ScreenKit
10
10
  end
11
11
 
12
12
  def self.schema_path
13
- ScreenKit.root_dir.join("screenkit/schemas/tts/espeak.json")
13
+ ScreenKit.root_dir.join("schemas/tts/espeak.json")
14
14
  end
15
15
 
16
16
  def generate(text:, output_path:, log_path: nil)
@@ -6,7 +6,7 @@ module ScreenKit
6
6
  extend Shell
7
7
 
8
8
  def self.schema_path
9
- ScreenKit.root_dir.join("screenkit/schemas/tts/say.json")
9
+ ScreenKit.root_dir.join("schemas/tts/say.json")
10
10
  end
11
11
 
12
12
  def self.available?(**)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ScreenKit
4
- VERSION = "0.0.8"
4
+ VERSION = "0.0.9"
5
5
  end
data/lib/screenkit.rb CHANGED
@@ -11,6 +11,7 @@ require "etc"
11
11
  require "securerandom"
12
12
  require "demo_tape/duration"
13
13
  require "aitch"
14
+ require "erb"
14
15
 
15
16
  module ScreenKit
16
17
  require_relative "screenkit/version"
@@ -67,7 +68,11 @@ module ScreenKit
67
68
  require_files.call("screenkit/tts/*.rb")
68
69
 
69
70
  def self.root_dir
70
- @root_dir ||= Pathname(__dir__)
71
+ @root_dir ||= Pathname(__dir__).join("screenkit")
72
+ end
73
+
74
+ def self.resources_dir
75
+ @resources_dir ||= root_dir.join("resources")
71
76
  end
72
77
 
73
78
  # Raised when the configuration schema is invalid.
data/screenkit.gemspec CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.add_dependency "aitch"
39
39
  spec.add_dependency "demotape"
40
+ spec.add_dependency "erb"
40
41
  spec.add_dependency "etc"
41
42
  spec.add_dependency "json-schema"
42
43
  spec.add_dependency "mini_magick"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: screenkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Vieira
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: erb
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: etc
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -289,6 +303,7 @@ files:
289
303
  - lib/screenkit/callout/styles/file_copy.rb
290
304
  - lib/screenkit/callout/styles/inline_block.rb
291
305
  - lib/screenkit/callout/styles/shadow_block.rb
306
+ - lib/screenkit/callout/styles/social.rb
292
307
  - lib/screenkit/callout/text_style.rb
293
308
  - lib/screenkit/cli.rb
294
309
  - lib/screenkit/cli/base.rb
@@ -332,12 +347,27 @@ files:
332
347
  - lib/screenkit/logfile.rb
333
348
  - lib/screenkit/parallel_processor.rb
334
349
  - lib/screenkit/path_lookup.rb
350
+ - lib/screenkit/resources/callout_styles/social/blog.png
351
+ - lib/screenkit/resources/callout_styles/social/bsky.png
352
+ - lib/screenkit/resources/callout_styles/social/discord.png
353
+ - lib/screenkit/resources/callout_styles/social/dribbble.png
354
+ - lib/screenkit/resources/callout_styles/social/github.png
355
+ - lib/screenkit/resources/callout_styles/social/instagram.png
356
+ - lib/screenkit/resources/callout_styles/social/linkedin.png
357
+ - lib/screenkit/resources/callout_styles/social/mastodon.png
358
+ - lib/screenkit/resources/callout_styles/social/snap.png
359
+ - lib/screenkit/resources/callout_styles/social/soundcloud.png
360
+ - lib/screenkit/resources/callout_styles/social/spotify.png
361
+ - lib/screenkit/resources/callout_styles/social/tiktok.png
362
+ - lib/screenkit/resources/callout_styles/social/twitch.png
363
+ - lib/screenkit/resources/callout_styles/social/youtube.png
335
364
  - lib/screenkit/resources/mute.mp3
336
365
  - lib/screenkit/resources/transparent.png
337
366
  - lib/screenkit/schema_validator.rb
338
367
  - lib/screenkit/schemas/callout_styles/file_copy.json
339
368
  - lib/screenkit/schemas/callout_styles/inline_block.json
340
369
  - lib/screenkit/schemas/callout_styles/shadow_block.json
370
+ - lib/screenkit/schemas/callout_styles/social.json
341
371
  - lib/screenkit/schemas/callouts/inline_block.json
342
372
  - lib/screenkit/schemas/callouts/shadow_block.json
343
373
  - lib/screenkit/schemas/episode.json
@@ -388,10 +418,10 @@ metadata:
388
418
  rubygems_mfa_required: 'true'
389
419
  homepage_uri: https://github.com/fnando/screenkit
390
420
  bug_tracker_uri: https://github.com/fnando/screenkit/issues
391
- source_code_uri: https://github.com/fnando/screenkit/tree/v0.0.8
392
- changelog_uri: https://github.com/fnando/screenkit/tree/v0.0.8/CHANGELOG.md
393
- documentation_uri: https://github.com/fnando/screenkit/tree/v0.0.8/README.md
394
- license_uri: https://github.com/fnando/screenkit/tree/v0.0.8/LICENSE.md
421
+ source_code_uri: https://github.com/fnando/screenkit/tree/v0.0.9
422
+ changelog_uri: https://github.com/fnando/screenkit/tree/v0.0.9/CHANGELOG.md
423
+ documentation_uri: https://github.com/fnando/screenkit/tree/v0.0.9/README.md
424
+ license_uri: https://github.com/fnando/screenkit/tree/v0.0.9/LICENSE.md
395
425
  rdoc_options: []
396
426
  require_paths:
397
427
  - lib