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.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +4 -0
- data/.github/FUNDING.yml +4 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- data/.github/ISSUE_TEMPLATE/config.yml +5 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +38 -0
- data/.github/dependabot.yml +15 -0
- data/.github/workflows/ruby-tests.yml +51 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +16 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +80 -0
- data/DOCUMENTATION.md +972 -0
- data/Gemfile +5 -0
- data/LICENSE.md +20 -0
- data/README.md +49 -0
- data/Rakefile +15 -0
- data/bin/console +16 -0
- data/bin/setup +10 -0
- data/exe/screenkit +5 -0
- data/lib/screen_kit.rb +79 -0
- data/lib/screenkit/anchor.rb +19 -0
- data/lib/screenkit/animation_filters.rb +114 -0
- data/lib/screenkit/banner.rb +46 -0
- data/lib/screenkit/callout/styles/base.rb +101 -0
- data/lib/screenkit/callout/styles/default.rb +144 -0
- data/lib/screenkit/callout/styles/inline_block.rb +123 -0
- data/lib/screenkit/callout/text_style.rb +44 -0
- data/lib/screenkit/callout.rb +98 -0
- data/lib/screenkit/cli/base.rb +24 -0
- data/lib/screenkit/cli/episode.rb +78 -0
- data/lib/screenkit/cli/root.rb +73 -0
- data/lib/screenkit/cli.rb +9 -0
- data/lib/screenkit/config/base.rb +51 -0
- data/lib/screenkit/config/episode.rb +42 -0
- data/lib/screenkit/config/project.rb +47 -0
- data/lib/screenkit/content_type.rb +12 -0
- data/lib/screenkit/core_ext/json.rb +47 -0
- data/lib/screenkit/core_ext/string.rb +28 -0
- data/lib/screenkit/exporter/demotape.rb +26 -0
- data/lib/screenkit/exporter/episode.rb +565 -0
- data/lib/screenkit/exporter/image.rb +31 -0
- data/lib/screenkit/exporter/intro.rb +261 -0
- data/lib/screenkit/exporter/outro.rb +183 -0
- data/lib/screenkit/exporter/segment.rb +258 -0
- data/lib/screenkit/exporter/video.rb +33 -0
- data/lib/screenkit/generators/episode/config.yml.erb +106 -0
- data/lib/screenkit/generators/episode/content/001.tape +4 -0
- data/lib/screenkit/generators/episode/scripts/001.txt +1 -0
- data/lib/screenkit/generators/episode.rb +31 -0
- data/lib/screenkit/generators/project/Gemfile.erb +7 -0
- data/lib/screenkit/generators/project/resources/backtracks/default.aac +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OFL.txt +93 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Bold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-BoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-ExtraBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Italic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Light.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-LightItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Medium.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-MediumItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-Regular.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-SemiBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans-SemiBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Bold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-BoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Italic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Light.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-LightItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Medium.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-MediumItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-Regular.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Bold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Italic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Light.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Medium.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-Regular.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
- data/lib/screenkit/generators/project/resources/fonts/opensans/README.txt +100 -0
- data/lib/screenkit/generators/project/resources/images/logo.png +0 -0
- data/lib/screenkit/generators/project/resources/images/watermark.png +0 -0
- data/lib/screenkit/generators/project/resources/sounds/chime.mp3 +0 -0
- data/lib/screenkit/generators/project/resources/sounds/pop.mp3 +0 -0
- data/lib/screenkit/generators/project/resources/sounds/whoosh.mp3 +0 -0
- data/lib/screenkit/generators/project/screenkit.yml +189 -0
- data/lib/screenkit/generators/project.rb +34 -0
- data/lib/screenkit/logfile.rb +33 -0
- data/lib/screenkit/parallel_processor.rb +50 -0
- data/lib/screenkit/path_lookup.rb +27 -0
- data/lib/screenkit/resources/mute.mp3 +0 -0
- data/lib/screenkit/resources/transparent.png +0 -0
- data/lib/screenkit/schema_validator.rb +14 -0
- data/lib/screenkit/schemas/callouts/default.json +44 -0
- data/lib/screenkit/schemas/callouts/inline_block.json +20 -0
- data/lib/screenkit/schemas/episode.json +74 -0
- data/lib/screenkit/schemas/project.json +37 -0
- data/lib/screenkit/schemas/refs/anchor.json +17 -0
- data/lib/screenkit/schemas/refs/animation.json +7 -0
- data/lib/screenkit/schemas/refs/background.json +14 -0
- data/lib/screenkit/schemas/refs/callout.json +27 -0
- data/lib/screenkit/schemas/refs/color.json +7 -0
- data/lib/screenkit/schemas/refs/directory.json +20 -0
- data/lib/screenkit/schemas/refs/intro.json +30 -0
- data/lib/screenkit/schemas/refs/logo.json +26 -0
- data/lib/screenkit/schemas/refs/outro.json +26 -0
- data/lib/screenkit/schemas/refs/position.json +38 -0
- data/lib/screenkit/schemas/refs/scenes.json +27 -0
- data/lib/screenkit/schemas/refs/size.json +19 -0
- data/lib/screenkit/schemas/refs/sound.json +36 -0
- data/lib/screenkit/schemas/refs/spacing.json +22 -0
- data/lib/screenkit/schemas/refs/text_style.json +18 -0
- data/lib/screenkit/schemas/refs/transition.json +15 -0
- data/lib/screenkit/schemas/refs/tts.json +47 -0
- data/lib/screenkit/schemas/refs/watermark.json +18 -0
- data/lib/screenkit/schemas/tts/elevenlabs.json +67 -0
- data/lib/screenkit/schemas/tts/say.json +16 -0
- data/lib/screenkit/shell.rb +58 -0
- data/lib/screenkit/sound.rb +44 -0
- data/lib/screenkit/spacing.rb +23 -0
- data/lib/screenkit/spinner.rb +39 -0
- data/lib/screenkit/transition.rb +16 -0
- data/lib/screenkit/tts/eleven_labs.rb +51 -0
- data/lib/screenkit/tts/say.rb +31 -0
- data/lib/screenkit/utils.rb +87 -0
- data/lib/screenkit/version.rb +5 -0
- data/lib/screenkit/watermark.rb +34 -0
- data/lib/screenkit.rb +3 -0
- data/screenkit.gemspec +56 -0
- 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
|