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
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/logo.json",
4
+ "title": "Logo",
5
+ "type": "object",
6
+ "required": ["path"],
7
+ "allOf": [
8
+ { "$ref": "position.json" },
9
+ {
10
+ "width": {
11
+ "type": "integer",
12
+ "description": "Width of the logo (height is calculated proportionally)",
13
+ "minimum": 1
14
+ }
15
+ },
16
+
17
+ {
18
+ "path": {
19
+ "type": "string",
20
+ "description": "Path to the logo image file",
21
+ "minLength": 1,
22
+ "pattern": "\\S"
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/outro.json",
4
+ "title": "Outro",
5
+ "required": ["logo"],
6
+ "properties": {
7
+ "background": { "$ref": "background.json" },
8
+ "logo": { "$ref": "logo.json" },
9
+ "sound": { "$ref": "sound.json" },
10
+ "duration": {
11
+ "type": "number",
12
+ "description": "Duration of the intro scene in seconds",
13
+ "minimum": 0
14
+ },
15
+ "fade_in": {
16
+ "type": "number",
17
+ "description": "Fade-in duration in seconds",
18
+ "minimum": 0
19
+ },
20
+ "fade_out": {
21
+ "type": "number",
22
+ "description": "Fade-out duration in seconds",
23
+ "minimum": 0
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/position.json",
4
+ "title": "Position",
5
+ "type": "object",
6
+ "properties": {
7
+ "x": {
8
+ "oneOf": [
9
+ {
10
+ "type": "integer",
11
+ "description": "X coordinate in pixels",
12
+ "minimum": 0,
13
+ "default": 0
14
+ },
15
+ {
16
+ "type": "string",
17
+ "enum": ["center"],
18
+ "description": "Center horizontally"
19
+ }
20
+ ]
21
+ },
22
+ "y": {
23
+ "oneOf": [
24
+ {
25
+ "type": "integer",
26
+ "description": "Y coordinate in pixels",
27
+ "minimum": 0,
28
+ "default": 0
29
+ },
30
+ {
31
+ "type": "string",
32
+ "enum": ["center"],
33
+ "description": "Center vertically"
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/scenes.json",
4
+ "title": "Scenes",
5
+ "type": "object",
6
+ "required": ["intro", "outro", "segment"],
7
+ "properties": {
8
+ "intro": {
9
+ "$ref": "intro.json"
10
+ },
11
+ "outro": {
12
+ "$ref": "outro.json"
13
+ },
14
+ "segment": {
15
+ "type": "object",
16
+ "description": "Main content scene configuration",
17
+ "required": ["crossfade_duration"],
18
+ "properties": {
19
+ "crossfade_duration": {
20
+ "type": "number",
21
+ "description": "Duration of the crossover transition between segments in seconds",
22
+ "minimum": 0
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/size.json",
4
+ "title": "Size",
5
+ "type": "object",
6
+ "required": ["width", "height"],
7
+ "properties": {
8
+ "width": {
9
+ "type": "number",
10
+ "description": "Width in pixels",
11
+ "minimum": 1
12
+ },
13
+ "height": {
14
+ "type": "number",
15
+ "description": "Height in pixels",
16
+ "minimum": 1
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/sound.json",
4
+ "title": "Sound",
5
+ "oneOf": [
6
+ {
7
+ "type": "boolean",
8
+ "enum": [false],
9
+ "description": "No sound"
10
+ },
11
+ {
12
+ "type": "string",
13
+ "format": "uri",
14
+ "description": "Path to the sound file"
15
+ },
16
+ {
17
+ "type": "object",
18
+ "required": ["path"],
19
+ "properties": {
20
+ "volume": {
21
+ "type": "number",
22
+ "description": "The sound volume level",
23
+ "minimum": 0.0,
24
+ "maximum": 1.0,
25
+ "default": 1.0
26
+ },
27
+ "path": {
28
+ "type": "string",
29
+ "description": "Path to the sound file",
30
+ "minLength": 1,
31
+ "pattern": "\\S"
32
+ }
33
+ }
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/spacing.json",
4
+ "title": "Spacing (Padding/Margin)",
5
+ "oneOf": [
6
+ {
7
+ "type": "integer",
8
+ "description": "Uniform spacing value applied to all sides",
9
+ "minimum": 0
10
+ },
11
+ {
12
+ "type": "array",
13
+ "description": "Spacing values [top, right, bottom, left]",
14
+ "items": {
15
+ "type": "integer",
16
+ "minimum": 0
17
+ },
18
+ "minItems": 1,
19
+ "maxItems": 4
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/text_style.json",
4
+ "title": "Text Style",
5
+ "properties": {
6
+ "font_path": {
7
+ "type": "string",
8
+ "format": "uri"
9
+ },
10
+ "size": {
11
+ "type": "integer",
12
+ "minimum": 1
13
+ },
14
+ "color": {
15
+ "$ref": "color.json"
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/transition.json",
4
+ "type": "object",
5
+ "title": "Transition",
6
+ "required": ["duration", "sound"],
7
+ "properties": {
8
+ "sound": { "$ref": "sound.json" },
9
+ "duration": {
10
+ "type": "number",
11
+ "description": "Transition duration in seconds",
12
+ "minimum": 0
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/refs/tts.json",
4
+ "title": "The TTS engine used for voiceover",
5
+ "oneOf": [
6
+ {
7
+ "type": "boolean",
8
+ "enum": [false]
9
+ },
10
+ {
11
+ "type": "object",
12
+ "$ref": "../tts/say.json",
13
+ "properties": {
14
+ "engine": {
15
+ "type": "string",
16
+ "enum": ["say"],
17
+ "description": "The TTS engine to use"
18
+ }
19
+ }
20
+ },
21
+ {
22
+ "type": "object",
23
+ "$ref": "../tts/elevenlabs.json",
24
+ "properties": {
25
+ "engine": {
26
+ "type": "string",
27
+ "enum": ["elevenlabs"],
28
+ "description": "The TTS engine to use"
29
+ }
30
+ }
31
+ },
32
+ {
33
+ "type": "object",
34
+ "required": ["engine"],
35
+ "additionalProperties": true,
36
+ "properties": {
37
+ "engine": {
38
+ "type": "string",
39
+ "description": "The TTS engine to use",
40
+ "not": {
41
+ "enum": ["say", "elevenlabs"]
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ]
47
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/project.json",
4
+ "title": "Watermark",
5
+ "oneOf": [
6
+ { "type": "boolean", "enum": [false] },
7
+ { "type": "string", "format": "uri" },
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "path": { "type": "string", "format": "uri" },
12
+ "anchor": { "$ref": "anchor.json" },
13
+ "position": { "$ref": "position.json" },
14
+ "opacity": { "type": "number", "minimum": 0, "maximum": 1 }
15
+ }
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/tts/elevenlabs.json",
4
+ "title": "The ElevenLabs voice engine options",
5
+ "type": "object",
6
+ "required": ["voice_id"],
7
+ "properties": {
8
+ "voice_id": { "type": "string" },
9
+ "output_format": {
10
+ "type": "string",
11
+ "enum": [
12
+ "mp3_22050_32",
13
+ "mp3_24000_48",
14
+ "mp3_44100_32",
15
+ "mp3_44100_64",
16
+ "mp3_44100_96",
17
+ "mp3_44100_128",
18
+ "mp3_44100_192",
19
+ "pcm_8000",
20
+ "pcm_16000",
21
+ "pcm_22050",
22
+ "pcm_24000",
23
+ "pcm_32000",
24
+ "pcm_44100",
25
+ "pcm_48000",
26
+ "ulaw_8000",
27
+ "alaw_8000",
28
+ "opus_48000_32",
29
+ "opus_48000_64",
30
+ "opus_48000_96",
31
+ "opus_48000_128",
32
+ "opus_48000_192"
33
+ ]
34
+ },
35
+ "model_id": { "type": "string" },
36
+ "language_code": {
37
+ "type": "string",
38
+ "minLength": 2,
39
+ "maxLength": 2,
40
+ "pattern": "\\S+"
41
+ },
42
+ "previous_text": { "type": "string" },
43
+ "next_text": { "type": "string" },
44
+ "previous_request_ids": { "type": "array", "items": { "type": "string" } },
45
+ "next_request_ids": { "type": "array", "items": { "type": "string" } },
46
+ "voice_settings": {
47
+ "type": "object",
48
+ "properties": {
49
+ "stability": {
50
+ "type": "number",
51
+ "minimum": 0,
52
+ "maximum": 1
53
+ },
54
+ "similarity_boost": {
55
+ "type": "number",
56
+ "minimum": 0,
57
+ "maximum": 1
58
+ }
59
+ },
60
+ "style": {
61
+ "type": "integer",
62
+ "minimum": 0
63
+ },
64
+ "speed": { "type": "number" }
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "$id": "https://github.com/fnando/screenkit/schemas/tts/say.json",
4
+ "title": "The Apple `say` voice engine options",
5
+ "type": "object",
6
+ "properties": {
7
+ "voice": {
8
+ "description": "The voice to use",
9
+ "oneOf": [{ "type": "null" }, { "type": "string" }]
10
+ },
11
+ "rate": {
12
+ "description": "The speaking rate (words per minute)",
13
+ "oneOf": [{ "type": "null" }, { "type": "number", "pattern": "/^\\d+$/" }]
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ module Shell
5
+ Error = Class.new(StandardError) do
6
+ attr_reader :stdout, :stderr, :command, :args
7
+
8
+ def initialize(message, stdout:, stderr:, command:, args:)
9
+ super(message)
10
+ @stdout = stdout
11
+ @stderr = stderr
12
+ @command = command
13
+ @args = args
14
+ end
15
+ end
16
+
17
+ def run_command(command, *args, log_path: nil)
18
+ args = args.flatten.compact.map(&:to_s)
19
+ stdout, stderr, status = Open3.capture3(command, *args)
20
+ exit_code = status.exitstatus
21
+
22
+ if exit_code&.nonzero?
23
+ output = [command, *args].join(" ")
24
+ $stderr << "Command failed: #{output}\n"
25
+ $stderr << "#{stdout}\n" unless stdout.empty?
26
+ $stderr << "#{stderr}\n" unless stderr.empty?
27
+
28
+ raise Error.new(
29
+ "#{command.inspect} failed with exit=#{exit_code}",
30
+ stdout:,
31
+ stderr:,
32
+ command:,
33
+ args:
34
+ )
35
+ end
36
+
37
+ [stdout, stderr]
38
+ ensure
39
+ if log_path
40
+ header = ->(text) { ("=" * 10) + " #{text} " + ("=" * 10) }
41
+
42
+ File.open(log_path, "w") do |f|
43
+ f << "#{header.call('COMMAND')}\n\n"
44
+ f << "#{[command, *args].join(' ')}\n\n"
45
+
46
+ f << "#{header.call('EXIT CODE')}\n\n"
47
+ f << "#{exit_code}\n\n"
48
+
49
+ f << "#{header.call('STDOUT')}\n\n"
50
+ f << "#{stdout}\n\n"
51
+
52
+ f << "#{header.call('STDERR')}\n\n"
53
+ f << "#{stderr}\n"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ class Sound
5
+ # The requested sound file path.
6
+ # @return [Pathname]
7
+ attr_reader :path
8
+
9
+ # The volume level for the sound.
10
+ # @return [Float]
11
+ attr_reader :volume
12
+
13
+ # Initializes a new Sound instance.
14
+ #
15
+ # @param input [Pathname|FalseClass|Hash] The sound configuration. It will
16
+ # be denormalized to all the proper attributes.
17
+ # @return [void]
18
+ def initialize(input:, source:)
19
+ @volume = 1.0
20
+
21
+ case input
22
+ when FalseClass, nil
23
+ @path = ScreenKit.root_dir.join("screenkit/resources/mute.mp3")
24
+ when Hash
25
+ {path: nil, volume: 1.0}.merge(input) => {path:, volume:}
26
+ @path = Pathname(path)
27
+ @volume = volume
28
+ else
29
+ @path = Pathname(input)
30
+ end
31
+
32
+ return if @path.absolute?
33
+ return if @path.file?
34
+
35
+ candidate = source.search(@path)
36
+
37
+ @path = if candidate.file?
38
+ candidate
39
+ else
40
+ candidate.glob("**/*.{#{ContentType.audio.join(',')}}").sample
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ class Spacing
5
+ attr_reader :top, :right, :bottom, :left
6
+
7
+ def initialize(value)
8
+ @top, @right, @bottom, @left = (Array(value) * 4).take(4)
9
+ end
10
+
11
+ def horizontal
12
+ right + left
13
+ end
14
+
15
+ def vertical
16
+ top + bottom
17
+ end
18
+
19
+ def as_json(*)
20
+ [top, right, bottom, left]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ class Spinner
5
+ attr_reader :phrase
6
+
7
+ def initialize(phrases: [])
8
+ @phrases = phrases
9
+ @shell = Thor::Shell::Color.new
10
+ @phrase = @phrases.sample
11
+ @spinner = create_spinner
12
+ update(@phrase)
13
+ end
14
+
15
+ def create_spinner
16
+ TTY::Spinner.new(
17
+ " :spinner :title",
18
+ frames: [
19
+ @shell.set_color("◉", :white),
20
+ @shell.set_color("◎", :white),
21
+ @shell.set_color("∙", :white)
22
+ ],
23
+ interval: 3,
24
+ clear: true,
25
+ hide_cursor: true
26
+ ).tap(&:auto_spin)
27
+ end
28
+
29
+ def update(phrase)
30
+ @spinner ||= create_spinner
31
+ @spinner.update(title: @shell.set_color(phrase, :white))
32
+ end
33
+
34
+ def stop
35
+ @spinner&.stop
36
+ @spinner = nil
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ class Transition
5
+ attr_reader :duration, :sound
6
+
7
+ def initialize(duration:, sound:)
8
+ @duration = duration
9
+ @sound = sound
10
+ end
11
+
12
+ def as_json(*)
13
+ {duration:, sound:}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ module VoiceEngines
5
+ class TTS
6
+ extend SchemaValidator
7
+
8
+ def self.schema_path
9
+ ScreenKit.root_dir
10
+ .join("screenkit/schemas/tts/elevenlabs.json")
11
+ end
12
+
13
+ # The Eleven Labs API key.
14
+ attr_reader :api_key
15
+
16
+ # Additional options for the Eleven Labs voice engine.
17
+ attr_reader :options
18
+
19
+ def initialize(api_key:, **options)
20
+ @api_key = api_key
21
+ @options = options
22
+ end
23
+
24
+ def generate(output_path:, text:, log_path: nil)
25
+ self.class.validate!(options)
26
+ voice_id = options.delete(:voice_id)
27
+
28
+ if log_path
29
+ File.open(log_path, "w") do |f|
30
+ f << JSON.pretty_generate(options.merge(text:))
31
+ end
32
+ end
33
+
34
+ require "aitch"
35
+
36
+ response = Aitch.post(
37
+ url: "https://api.elevenlabs.io/v1/text-to-speech/#{voice_id}",
38
+ body: JSON.dump(options.merge(text:)),
39
+ options: {expect: 200},
40
+ headers: {
41
+ "content-type": "application/json",
42
+ "user-agent": "ScreenKit/#{ScreenKit::VERSION}",
43
+ "xi-api-key": api_key
44
+ }
45
+ )
46
+
47
+ File.binwrite(output_path, response.body)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ module TTS
5
+ class Say
6
+ include Shell
7
+ extend SchemaValidator
8
+
9
+ def self.schema_path
10
+ ScreenKit.root_dir.join("screenkit/schemas/tts/say.json")
11
+ end
12
+
13
+ def initialize(**options)
14
+ @options = options
15
+ end
16
+
17
+ def generate(text:, output_path:, log_path: nil)
18
+ self.class.validate!(@options)
19
+
20
+ {voice: nil, rate: nil}.merge(@options) => {voice:, rate:}
21
+
22
+ run_command "say",
23
+ (["-v", voice] if voice),
24
+ (["-r", rate] if rate),
25
+ "-o", output_path.sub_ext(".aiff"),
26
+ text,
27
+ log_path:
28
+ end
29
+ end
30
+ end
31
+ end