screenkit 0.0.5 → 0.0.7
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 +4 -4
- data/.github/workflows/docker.yml +2 -2
- data/CHANGELOG.md +12 -4
- data/DOCUMENTATION.md +134 -39
- data/Dockerfile +1 -0
- data/action.yml +140 -0
- data/lib/screenkit/callout/styles/base.rb +16 -0
- data/lib/screenkit/cli/episode.rb +10 -3
- data/lib/screenkit/content_type.rb +1 -1
- data/lib/screenkit/core_ext/json.rb +2 -0
- data/lib/screenkit/core_ext/string.rb +6 -0
- data/lib/screenkit/core_ext.rb +4 -0
- data/lib/screenkit/exporter/demotape.rb +2 -1
- data/lib/screenkit/exporter/episode.rb +21 -8
- data/lib/screenkit/exporter/segment.rb +7 -3
- data/lib/screenkit/exporter/video.rb +7 -2
- data/lib/screenkit/generators/episode/config.yml.erb +8 -103
- data/lib/screenkit/generators/project/.github/screenkit.yml +48 -0
- data/lib/screenkit/generators/project/screenkit.yml +31 -3
- data/lib/screenkit/generators/project.rb +7 -0
- data/lib/screenkit/schemas/refs/tts_builtin.json +5 -1
- data/lib/screenkit/schemas/tts/elevenlabs.json +4 -0
- data/lib/screenkit/schemas/tts/espeak.json +4 -0
- data/lib/screenkit/schemas/tts/say.json +4 -0
- data/lib/screenkit/shell.rb +2 -2
- data/lib/screenkit/tts/base.rb +34 -4
- data/lib/screenkit/tts/eleven_labs.rb +22 -11
- data/lib/screenkit/tts/espeak.rb +11 -11
- data/lib/screenkit/tts/say.rb +9 -9
- data/lib/screenkit/utils.rb +1 -1
- data/lib/screenkit/version.rb +1 -1
- data/lib/screenkit.rb +77 -1
- metadata +8 -6
- data/lib/screen_kit.rb +0 -80
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3dc4a6ebfa5fb6725aa40e4bbe699bcafca860135ef8cca01fc48ab66731392f
|
|
4
|
+
data.tar.gz: 42df45d24c11f800e20fff4dfc030102bef5747f758ee47378d98a56dd8732f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0eac47f990100559123217b0a54f1ae84629ea6ff7039c38d6212d72284cce07b10552f92b6504588ab74a013bb3242d8bc19fe66a7a06b0cb3a8014822248b
|
|
7
|
+
data.tar.gz: 5c43aae473d552c8f0d233c0ab19b8ca1a1eab91b26225b7533c53eb4caeb44e5909cbc5e20c04b66bad939acee49219d0a75f61edb1617e00e209e498546567
|
|
@@ -7,7 +7,7 @@ on:
|
|
|
7
7
|
paths:
|
|
8
8
|
- Dockerfile
|
|
9
9
|
tags:
|
|
10
|
-
- v
|
|
10
|
+
- v[0-9]+.[0-9]+.[0-9]+
|
|
11
11
|
workflow_dispatch:
|
|
12
12
|
inputs:
|
|
13
13
|
ref:
|
|
@@ -23,7 +23,7 @@ jobs:
|
|
|
23
23
|
build:
|
|
24
24
|
runs-on: ubuntu-latest
|
|
25
25
|
steps:
|
|
26
|
-
- uses: actions/checkout@
|
|
26
|
+
- uses: actions/checkout@v6
|
|
27
27
|
with:
|
|
28
28
|
ref: ${{ github.event.inputs.ref }}
|
|
29
29
|
|
data/CHANGELOG.md
CHANGED
|
@@ -11,18 +11,26 @@ Prefix your message with one of the following:
|
|
|
11
11
|
- [Security] in case of vulnerabilities.
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
|
+
## v0.0.7
|
|
15
|
+
|
|
16
|
+
- [Fixed] Improve support for extensions.
|
|
17
|
+
|
|
18
|
+
## v0.0.6
|
|
19
|
+
|
|
20
|
+
- [Added] Add `--tts-preset` option to select TTS preset when exporting
|
|
21
|
+
episodes.
|
|
22
|
+
|
|
14
23
|
## v0.0.5
|
|
15
24
|
|
|
16
|
-
- [Added]
|
|
17
|
-
projects.
|
|
25
|
+
- [Added] Add `--skip-bundler` option to skip bundler when generating projects.
|
|
18
26
|
|
|
19
27
|
## v0.0.4
|
|
20
28
|
|
|
21
|
-
- [Fixed]
|
|
29
|
+
- [Fixed] Fix Gemfile template that was pointing to a local path.
|
|
22
30
|
|
|
23
31
|
## v0.0.3
|
|
24
32
|
|
|
25
|
-
- [Changed]
|
|
33
|
+
- [Changed] Keep path as it is when creating project/episode.
|
|
26
34
|
|
|
27
35
|
## v0.0.2
|
|
28
36
|
|
data/DOCUMENTATION.md
CHANGED
|
@@ -39,7 +39,74 @@ gem "screenkit"
|
|
|
39
39
|
### Docker
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
docker run
|
|
42
|
+
$ docker run \
|
|
43
|
+
--platform=linux/amd64 \
|
|
44
|
+
--shm-size=2g \
|
|
45
|
+
-v $PWD:/source \
|
|
46
|
+
--rm -it \
|
|
47
|
+
docker.io/fnando/screenkit new --skip-bundler example
|
|
48
|
+
create Gemfile
|
|
49
|
+
create screenkit.yml
|
|
50
|
+
create resources
|
|
51
|
+
create resources/backtracks/default.aac
|
|
52
|
+
create resources/fonts/open-sans/OFL.txt
|
|
53
|
+
create resources/fonts/open-sans/OpenSans-ExtraBold.ttf
|
|
54
|
+
create resources/fonts/open-sans/OpenSans-SemiBold.ttf
|
|
55
|
+
create resources/fonts/open-sans/README.txt
|
|
56
|
+
create resources/images/logo.png
|
|
57
|
+
create resources/images/watermark.png
|
|
58
|
+
create resources/sounds/chime.mp3
|
|
59
|
+
create resources/sounds/pop.mp3
|
|
60
|
+
create resources/sounds/whoosh.mp3
|
|
61
|
+
|
|
62
|
+
$ cd example
|
|
63
|
+
|
|
64
|
+
$ docker run \
|
|
65
|
+
--platform=linux/amd64 \
|
|
66
|
+
--shm-size=2g \
|
|
67
|
+
-v $PWD:/source \
|
|
68
|
+
--rm -it \
|
|
69
|
+
docker.io/fnando/screenkit episode new --title 'Hello, world!'
|
|
70
|
+
create episodes/001-hello-world/config.yml
|
|
71
|
+
create episodes/001-hello-world/scripts
|
|
72
|
+
create episodes/001-hello-world/scripts/001.txt
|
|
73
|
+
create episodes/001-hello-world/content
|
|
74
|
+
create episodes/001-hello-world/content/001.tape
|
|
75
|
+
create episodes/001-hello-world/resources
|
|
76
|
+
create episodes/001-hello-world/voiceovers
|
|
77
|
+
create episodes/001-hello-world/resources/.keep
|
|
78
|
+
create episodes/001-hello-world/voiceovers/.keep
|
|
79
|
+
|
|
80
|
+
$ docker run \
|
|
81
|
+
--platform=linux/amd64 \
|
|
82
|
+
--shm-size=2g \
|
|
83
|
+
-v $PWD:/source \
|
|
84
|
+
--rm -it \
|
|
85
|
+
docker.io/fnando/screenkit episode export --dir episodes/001-hello-world
|
|
86
|
+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
87
|
+
┃ Terminal to screencast, simplified ┃═╗
|
|
88
|
+
┃ ███████╗ ██████╗██████╗ ███████╗███████╗███╗ ██╗██╗ ██╗██╗████████╗ ┃ ║
|
|
89
|
+
┃ ██╔════╝██╔════╝██╔══██╗██╔════╝██╔════╝████╗ ██║██║ ██╔╝██║╚══██╔══╝ ┃ ║
|
|
90
|
+
┃ ███████╗██║ ██████╔╝█████╗ █████╗ ██╔██╗ ██║█████╔╝ ██║ ██║ ┃ ║
|
|
91
|
+
┃ ╚════██║██║ ██╔══██╗██╔══╝ ██╔══╝ ██║╚██╗██║██╔═██╗ ██║ ██║ ┃ ║
|
|
92
|
+
┃ ███████║╚██████╗██║ ██║███████╗███████╗██║ ╚████║██║ ██╗██║ ██║ ┃ ║
|
|
93
|
+
┃ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ┃ ║
|
|
94
|
+
┃ v0.0.5 ┃ ║
|
|
95
|
+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ║
|
|
96
|
+
╚══════════════════════════════════════════════════════════════════════════╝
|
|
97
|
+
|
|
98
|
+
info Project root dir: .
|
|
99
|
+
info Episode root dir: episodes/001-hello-world
|
|
100
|
+
info Matching all 1 segments
|
|
101
|
+
info Exported intro in 2.39s
|
|
102
|
+
info Exported outro in 2.11s
|
|
103
|
+
info Generated voiceover in 0.89s
|
|
104
|
+
info Exported videos in 0.00s
|
|
105
|
+
info Created callouts in 0.00s
|
|
106
|
+
info Created segments in 2.55s
|
|
107
|
+
info Merged videos in 8.31s
|
|
108
|
+
info Exported video to output/001-hello-world/001-hello-world.mp4
|
|
109
|
+
info Exported episode in 16.29s
|
|
43
110
|
```
|
|
44
111
|
|
|
45
112
|
Notice that Chrome requires a lot of memory, so you need `--shm-size=2g` (or
|
|
@@ -138,7 +205,8 @@ Export an episode to video.
|
|
|
138
205
|
**Options:**
|
|
139
206
|
|
|
140
207
|
- `--dir` (required) - Episode directory path
|
|
141
|
-
- `--
|
|
208
|
+
- `--tts-api-key` - API key for TTS service (e.g., ElevenLabs)
|
|
209
|
+
- `--tts-preset` - TTS preset name that will be used
|
|
142
210
|
- `--overwrite` - Overwrite existing exported files (default: `false`)
|
|
143
211
|
- `--match-segment` - Only export segments matching this string
|
|
144
212
|
- `--output-dir` - Custom output directory path
|
|
@@ -220,12 +288,12 @@ watermark:
|
|
|
220
288
|
watermark: false
|
|
221
289
|
```
|
|
222
290
|
|
|
223
|
-
### Callout
|
|
291
|
+
### Callout Styles
|
|
224
292
|
|
|
225
293
|
Define reusable callout styles:
|
|
226
294
|
|
|
227
295
|
```yaml
|
|
228
|
-
|
|
296
|
+
callout_styles:
|
|
229
297
|
shadow_block:
|
|
230
298
|
background_color: "#ffff00"
|
|
231
299
|
shadow: "#2242d3" # Color string or false
|
|
@@ -290,8 +358,9 @@ backtrack: false # Disable for this episode
|
|
|
290
358
|
|
|
291
359
|
# Override TTS settings
|
|
292
360
|
tts:
|
|
293
|
-
|
|
294
|
-
|
|
361
|
+
- id: eleven_labs
|
|
362
|
+
engine: eleven_labs
|
|
363
|
+
voice_id: custom_voice_id
|
|
295
364
|
|
|
296
365
|
# Override watermark
|
|
297
366
|
watermark: false
|
|
@@ -642,9 +711,10 @@ Uses the built-in macOS `say` command.
|
|
|
642
711
|
|
|
643
712
|
```yaml
|
|
644
713
|
tts:
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
714
|
+
- id: say
|
|
715
|
+
engine: say
|
|
716
|
+
voice: Alex # Optional: Voice name
|
|
717
|
+
rate: 150 # Words per minute (optional)
|
|
648
718
|
```
|
|
649
719
|
|
|
650
720
|
### ElevenLabs Engine
|
|
@@ -653,22 +723,23 @@ Professional AI voice synthesis.
|
|
|
653
723
|
|
|
654
724
|
```yaml
|
|
655
725
|
tts:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
726
|
+
- id: eleven_labs
|
|
727
|
+
engine: eleven_labs
|
|
728
|
+
voice_id: "56AoDkrOh6qfVPDXZ7Pt" # Required: ElevenLabs voice ID
|
|
729
|
+
language_code: en # 2-letter language code
|
|
730
|
+
|
|
731
|
+
# Optional: Voice settings
|
|
732
|
+
voice_settings:
|
|
733
|
+
speed: 0.9 # Speech speed (default: 1.0)
|
|
734
|
+
stability: 0.5 # Voice stability (0.0 - 1.0)
|
|
735
|
+
similarity: 0.75 # Voice similarity (0.0 - 1.0)
|
|
736
|
+
style: 0.0 # Speaking style (0.0+)
|
|
737
|
+
|
|
738
|
+
# Optional: Output format
|
|
739
|
+
output_format: mp3_44100_128
|
|
740
|
+
|
|
741
|
+
# Optional: Model ID
|
|
742
|
+
model_id: eleven_monolingual_v1
|
|
672
743
|
```
|
|
673
744
|
|
|
674
745
|
#### ElevenLabs Output Formats
|
|
@@ -693,7 +764,7 @@ module. Custom engines must implement the `generate` method:
|
|
|
693
764
|
module ScreenKit
|
|
694
765
|
module TTS
|
|
695
766
|
class CustomEngine < Base
|
|
696
|
-
|
|
767
|
+
extend Shell
|
|
697
768
|
|
|
698
769
|
# Optional: Define schema path for validation
|
|
699
770
|
def self.schema_path
|
|
@@ -701,8 +772,8 @@ module ScreenKit
|
|
|
701
772
|
end
|
|
702
773
|
|
|
703
774
|
# This method is required.
|
|
704
|
-
def available?
|
|
705
|
-
|
|
775
|
+
def self.available?(**)
|
|
776
|
+
command_exist?("some-command")
|
|
706
777
|
end
|
|
707
778
|
|
|
708
779
|
# This method is required.
|
|
@@ -710,15 +781,20 @@ module ScreenKit
|
|
|
710
781
|
# Optional: validate options against JSON schema.
|
|
711
782
|
self.class.validate!(options)
|
|
712
783
|
|
|
784
|
+
# If you need access to previous/next text, you can access the method
|
|
785
|
+
# `segments`.
|
|
786
|
+
# current_index = segments.index { it.script_content == text }
|
|
787
|
+
# next_text = segments[current_index + 1]&.script_content
|
|
788
|
+
|
|
713
789
|
# Generate audio file from text
|
|
714
790
|
# Write output to output_path
|
|
715
791
|
# Optionally log to log_path
|
|
716
792
|
|
|
717
793
|
# Example implementation:
|
|
718
|
-
# run_command "some-command",
|
|
719
|
-
#
|
|
720
|
-
#
|
|
721
|
-
#
|
|
794
|
+
# self.class.run_command "some-command",
|
|
795
|
+
# "-o", output_path.sub_ext(".wav"),
|
|
796
|
+
# text,
|
|
797
|
+
# log_path:
|
|
722
798
|
end
|
|
723
799
|
end
|
|
724
800
|
end
|
|
@@ -729,16 +805,28 @@ end
|
|
|
729
805
|
|
|
730
806
|
```yaml
|
|
731
807
|
tts:
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
808
|
+
- id: custom_engine
|
|
809
|
+
engine: custom_engine # Camelized to CustomEngine
|
|
810
|
+
enabled: true
|
|
811
|
+
# Add your custom options here
|
|
812
|
+
api_key: your_api_key
|
|
813
|
+
custom_option: value
|
|
736
814
|
```
|
|
737
815
|
|
|
738
816
|
The engine name is camelized (e.g., `custom_engine` → `CustomEngine`,
|
|
739
817
|
`google_cloud` → `GoogleCloud`) and loaded as
|
|
740
818
|
`ScreenKit::TTS::#{CamelizedName}`.
|
|
741
819
|
|
|
820
|
+
### 3rd-party TTS Engines
|
|
821
|
+
|
|
822
|
+
- [Search Github](https://github.com/topics/screenkit-tts)
|
|
823
|
+
- [Google Text to Speech](https://github.com/fnando/screenkit-tts-google)
|
|
824
|
+
|
|
825
|
+
> [!TIP]
|
|
826
|
+
>
|
|
827
|
+
> If you host your TTS engine on Github, use the topic `screekit-tts`, so other
|
|
828
|
+
> people can find it.
|
|
829
|
+
|
|
742
830
|
---
|
|
743
831
|
|
|
744
832
|
## Animations
|
|
@@ -825,6 +913,12 @@ Enter
|
|
|
825
913
|
Sleep 2s
|
|
826
914
|
```
|
|
827
915
|
|
|
916
|
+
When running tape files, the working directory will be the episode directory. If
|
|
917
|
+
you're importing anything from the parent directory, you must specify relative
|
|
918
|
+
paths accordingly. For instance, `episodes/001-episode-name/content/001.tape`
|
|
919
|
+
would need to reference `../../resources/some-file` to access
|
|
920
|
+
`resources/some-file` in the project's directory.
|
|
921
|
+
|
|
828
922
|
### Script Files
|
|
829
923
|
|
|
830
924
|
Plain text files for voiceover generation:
|
|
@@ -839,7 +933,7 @@ Today we'll learn how to create amazing screencasts.
|
|
|
839
933
|
|
|
840
934
|
Files are matched by number:
|
|
841
935
|
|
|
842
|
-
- `content/001.tape` → `scripts/001.txt` → `voiceovers/001
|
|
936
|
+
- `content/001.tape` → `scripts/001.txt` → `voiceovers/001.:ext`
|
|
843
937
|
- Segments are processed in numerical order
|
|
844
938
|
- Missing scripts create silent segments
|
|
845
939
|
|
|
@@ -962,7 +1056,7 @@ ScreenKit validates configurations against JSON schemas:
|
|
|
962
1056
|
Use the `yaml-language-server` comment for IDE support:
|
|
963
1057
|
|
|
964
1058
|
```yaml
|
|
965
|
-
# yaml-language-server: $schema
|
|
1059
|
+
# yaml-language-server: $schema=https://screenkit.dev/schemas/project.json
|
|
966
1060
|
```
|
|
967
1061
|
|
|
968
1062
|
---
|
|
@@ -1048,8 +1142,9 @@ bundle exec screenkit ...
|
|
|
1048
1142
|
|
|
1049
1143
|
**TTS not working:**
|
|
1050
1144
|
|
|
1051
|
-
- For ElevenLabs: Set `--
|
|
1145
|
+
- For ElevenLabs: Set `--tts-api-key`
|
|
1052
1146
|
- For macOS `say`: Verify voice name with `say -v ?`
|
|
1147
|
+
- For `espeak`: Ensure `espeak` is installed and in PATH
|
|
1053
1148
|
|
|
1054
1149
|
---
|
|
1055
1150
|
|
data/Dockerfile
CHANGED
data/action.yml
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ScreenKit Episode
|
|
3
|
+
description: Export a screencast episode using Docker
|
|
4
|
+
inputs:
|
|
5
|
+
episode:
|
|
6
|
+
description: "Episode number (e.g. 001)"
|
|
7
|
+
required: true
|
|
8
|
+
tts_preset:
|
|
9
|
+
description: "TTS preset name"
|
|
10
|
+
default: ""
|
|
11
|
+
required: false
|
|
12
|
+
tts_api_key:
|
|
13
|
+
description: "TTS API key"
|
|
14
|
+
required: false
|
|
15
|
+
overwrite:
|
|
16
|
+
description: "Regenerate files"
|
|
17
|
+
default: "false"
|
|
18
|
+
required: false
|
|
19
|
+
match:
|
|
20
|
+
description: "Match pattern for segments (e.g. 001)"
|
|
21
|
+
default: ""
|
|
22
|
+
required: false
|
|
23
|
+
github_token:
|
|
24
|
+
description: "GitHub token for cache operations"
|
|
25
|
+
required: true
|
|
26
|
+
retention:
|
|
27
|
+
description: "Retention days for artifacts"
|
|
28
|
+
default: "2"
|
|
29
|
+
|
|
30
|
+
runs:
|
|
31
|
+
using: composite
|
|
32
|
+
steps:
|
|
33
|
+
- name: Generate cache keys
|
|
34
|
+
shell: bash
|
|
35
|
+
run: |
|
|
36
|
+
VOICEOVER_HASH=$(find episodes/${{ inputs.episode }}-*/scripts/*.txt -type f 2>/dev/null | sort | xargs sha256sum 2>/dev/null | sha256sum | cut -d' ' -f1 || echo "none")
|
|
37
|
+
CONTENT_HASH=$(find episodes/${{ inputs.episode }}-*/*/*.* -type f 2>/dev/null | sort | xargs sha256sum 2>/dev/null | sha256sum | cut -d' ' -f1 || echo "none")
|
|
38
|
+
|
|
39
|
+
echo "VOICEOVER_CACHE_KEY=episode-voiceover-${{ inputs.episode }}-${VOICEOVER_HASH}" >> $GITHUB_ENV
|
|
40
|
+
echo "VOICEOVER_RESTORE_KEY=episode-voiceover-${{ inputs.episode }}-" >> $GITHUB_ENV
|
|
41
|
+
echo "VIDEO_CACHE_KEY=episode-${{ inputs.episode }}-${CONTENT_HASH}" >> $GITHUB_ENV
|
|
42
|
+
echo "VIDEO_RESTORE_KEY=episode-${{ inputs.episode }}-" >> $GITHUB_ENV
|
|
43
|
+
|
|
44
|
+
- name: Find episode dir
|
|
45
|
+
shell: bash
|
|
46
|
+
run: |
|
|
47
|
+
EPISODE_DIR=$(ls -1d episodes/${{ inputs.episode }}-* | head -n 1)
|
|
48
|
+
echo "EPISODE_DIR=$EPISODE_DIR" >> $GITHUB_ENV
|
|
49
|
+
|
|
50
|
+
if [[ ! -d "$EPISODE_DIR" ]]; then
|
|
51
|
+
echo "Episode directory not found!"
|
|
52
|
+
echo "Available episodes:"
|
|
53
|
+
ls -1 episodes
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
- name: Restore episode voice cache
|
|
58
|
+
id: cache-voice
|
|
59
|
+
uses: actions/cache/restore@v4
|
|
60
|
+
with:
|
|
61
|
+
path: episodes/${{ inputs.episode }}-*/voiceovers
|
|
62
|
+
key: ${{ env.VOICEOVER_CACHE_KEY }}
|
|
63
|
+
restore-keys: ${{ env.VOICEOVER_RESTORE_KEY }}
|
|
64
|
+
enableCrossOsArchive: true
|
|
65
|
+
|
|
66
|
+
- name: Restore videos cache
|
|
67
|
+
id: cache-videos
|
|
68
|
+
uses: actions/cache/restore@v4
|
|
69
|
+
with:
|
|
70
|
+
path: output/${{ inputs.episode }}-*/videos/*.mp4
|
|
71
|
+
key: ${{ env.VIDEO_CACHE_KEY }}
|
|
72
|
+
restore-keys: ${{ env.VIDEO_RESTORE_KEY }}
|
|
73
|
+
enableCrossOsArchive: true
|
|
74
|
+
|
|
75
|
+
- name: Export Episode
|
|
76
|
+
shell: bash
|
|
77
|
+
id: screenkit
|
|
78
|
+
run: |
|
|
79
|
+
set -e
|
|
80
|
+
docker run \
|
|
81
|
+
--rm \
|
|
82
|
+
--cap-add=SYS_ADMIN \
|
|
83
|
+
--shm-size=2g \
|
|
84
|
+
--security-opt seccomp=unconfined \
|
|
85
|
+
-v ${{ github.workspace }}:/source \
|
|
86
|
+
fnando/screenkit:latest \
|
|
87
|
+
episode export \
|
|
88
|
+
--dir="${{ env.EPISODE_DIR }}" \
|
|
89
|
+
${{ inputs.match != '' && format('--match {0}', inputs.match) || '' }} \
|
|
90
|
+
${{ inputs.overwrite == 'true' && '--overwrite' || '--no-overwrite' }} \
|
|
91
|
+
${{ inputs.tts_preset != '' && format('--tts-preset {0}', inputs.tts_preset) || '' }} \
|
|
92
|
+
${{ inputs.tts_api_key != '' && format('--tts-api-key {0}', inputs.tts_api_key) || '' }}
|
|
93
|
+
echo "result=true" >> $GITHUB_OUTPUT
|
|
94
|
+
|
|
95
|
+
- name: Delete voice cache
|
|
96
|
+
if: steps.screenkit.outputs.result == 'true'
|
|
97
|
+
shell: bash
|
|
98
|
+
run: gh cache delete "${{ env.VOICEOVER_CACHE_KEY }}" || true
|
|
99
|
+
env:
|
|
100
|
+
GH_TOKEN: ${{ inputs.github_token }}
|
|
101
|
+
|
|
102
|
+
- name: Delete videos cache
|
|
103
|
+
if: steps.screenkit.outputs.result == 'true'
|
|
104
|
+
shell: bash
|
|
105
|
+
run: gh cache delete "${{ env.VIDEO_CACHE_KEY }}" || true
|
|
106
|
+
env:
|
|
107
|
+
GH_TOKEN: ${{ inputs.github_token }}
|
|
108
|
+
|
|
109
|
+
- name: List cache entries
|
|
110
|
+
shell: bash
|
|
111
|
+
run: |
|
|
112
|
+
echo "= Voiceover Cache ="
|
|
113
|
+
ls -la episodes/${{ inputs.episode }}-*/voiceovers || true
|
|
114
|
+
echo
|
|
115
|
+
echo "= Videos Cache ="
|
|
116
|
+
ls -la output/${{ inputs.episode }}-*/videos || true
|
|
117
|
+
|
|
118
|
+
- name: Save episode voiceover cache
|
|
119
|
+
if: steps.screenkit.outputs.result == 'true'
|
|
120
|
+
uses: actions/cache/save@v4
|
|
121
|
+
with:
|
|
122
|
+
path: episodes/${{ inputs.episode }}-*/voiceovers
|
|
123
|
+
key: ${{ env.VOICEOVER_CACHE_KEY }}
|
|
124
|
+
enableCrossOsArchive: true
|
|
125
|
+
|
|
126
|
+
- name: Save video recordings cache
|
|
127
|
+
if: steps.screenkit.outputs.result == 'true'
|
|
128
|
+
uses: actions/cache/save@v4
|
|
129
|
+
with:
|
|
130
|
+
path: output/${{ inputs.episode }}-*/videos/*.mp4
|
|
131
|
+
key: ${{ env.VIDEO_CACHE_KEY }}
|
|
132
|
+
enableCrossOsArchive: true
|
|
133
|
+
|
|
134
|
+
- name: Upload output artifacts
|
|
135
|
+
uses: actions/upload-artifact@v5
|
|
136
|
+
if: always()
|
|
137
|
+
with:
|
|
138
|
+
name: output
|
|
139
|
+
path: output
|
|
140
|
+
retention-days: ${{ inputs.retention }}
|
|
@@ -4,6 +4,8 @@ module ScreenKit
|
|
|
4
4
|
class Callout
|
|
5
5
|
module Styles
|
|
6
6
|
class Base
|
|
7
|
+
require_relative "../../schema_validator"
|
|
8
|
+
|
|
7
9
|
attr_reader :source, :output_path, :log_path
|
|
8
10
|
attr_accessor :options
|
|
9
11
|
|
|
@@ -16,6 +18,11 @@ module ScreenKit
|
|
|
16
18
|
@options = options
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
# Wrap text to fit within the specified maximum width.
|
|
22
|
+
# @param text [String] The text to wrap.
|
|
23
|
+
# @param max_width [Integer] The maximum width in pixels.
|
|
24
|
+
# @param font_size [Integer] The font size in points.
|
|
25
|
+
# @return [Array<String>] The wrapped lines of text.
|
|
19
26
|
def text_wrap(text, max_width:, font_size:)
|
|
20
27
|
words = text.to_s.split(/\s+/)
|
|
21
28
|
width_factor = 0.6
|
|
@@ -42,10 +49,19 @@ module ScreenKit
|
|
|
42
49
|
text.gsub("'", "\\\\'")
|
|
43
50
|
end
|
|
44
51
|
|
|
52
|
+
# Remove a file if it exists.
|
|
53
|
+
# @param path [String] The file path to remove.
|
|
45
54
|
def remove_file(path)
|
|
46
55
|
File.unlink(path) if path && File.exist?(path)
|
|
47
56
|
end
|
|
48
57
|
|
|
58
|
+
# Render text into an image using MiniMagick.
|
|
59
|
+
# @param text [String] The text to render.
|
|
60
|
+
# @param style [TextStyle] The text style to apply.
|
|
61
|
+
# @param width [Integer] The width of the text image.
|
|
62
|
+
# @param type [String] The ImageMagick text type (e.g., "caption").
|
|
63
|
+
# @return [Array] The path to the generated text image, width, and
|
|
64
|
+
# height.
|
|
49
65
|
def render_text_image(text:, style:, width:, type:)
|
|
50
66
|
return [nil, 0, 0] if text.to_s.empty?
|
|
51
67
|
|
|
@@ -55,12 +55,19 @@ module ScreenKit
|
|
|
55
55
|
type: :array,
|
|
56
56
|
default: [],
|
|
57
57
|
desc: "Additional Ruby files to require"
|
|
58
|
+
option :tts_preset,
|
|
59
|
+
type: :string,
|
|
60
|
+
desc: "Preset voice configuration for TTS"
|
|
58
61
|
def export
|
|
59
62
|
puts Banner.banner if options.banner
|
|
60
63
|
|
|
61
|
-
episode_config =
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
+
episode_config = File.join(options.dir, "config.yml")
|
|
65
|
+
|
|
66
|
+
episode_config = if File.file?(episode_config)
|
|
67
|
+
Config::Episode.load_file(episode_config)
|
|
68
|
+
else
|
|
69
|
+
{}
|
|
70
|
+
end
|
|
64
71
|
|
|
65
72
|
options.require.each { require(it) }
|
|
66
73
|
|
|
@@ -4,7 +4,7 @@ module ScreenKit
|
|
|
4
4
|
module ContentType
|
|
5
5
|
def self.video = %w[mp4 webm mov]
|
|
6
6
|
def self.audio = %w[mp3 wav m4a aac aiff]
|
|
7
|
-
def self.image = %w[gif jpg jpeg png]
|
|
7
|
+
def self.image = %w[gif jpg jpeg png tiff]
|
|
8
8
|
def self.demotape = %w[tape]
|
|
9
9
|
|
|
10
10
|
def self.all = video + image + demotape
|
|
@@ -7,6 +7,8 @@ module ScreenKit
|
|
|
7
7
|
unicode_normalize(:nfkd)
|
|
8
8
|
.delete("'")
|
|
9
9
|
.gsub(/[^\x00-\x7F]/, "")
|
|
10
|
+
.gsub(/([a-z\d])([A-Z])/, '\1-\2')
|
|
11
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1-\2')
|
|
10
12
|
.gsub(/[^-\w]+/xim, "-")
|
|
11
13
|
.tr("_", "-")
|
|
12
14
|
.gsub(/-+/xm, "-")
|
|
@@ -14,6 +16,10 @@ module ScreenKit
|
|
|
14
16
|
.downcase
|
|
15
17
|
end
|
|
16
18
|
|
|
19
|
+
def underscore
|
|
20
|
+
dasherize.tr("-", "_")
|
|
21
|
+
end
|
|
22
|
+
|
|
17
23
|
def camelize(first_letter = :upper)
|
|
18
24
|
split(/_|-/).map.with_index do |part, index|
|
|
19
25
|
if index.zero? && first_letter == :lower
|
|
@@ -38,7 +38,7 @@ module ScreenKit
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def tts_available?
|
|
41
|
-
|
|
41
|
+
tts_engine
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def demotape_options
|
|
@@ -46,11 +46,11 @@ module ScreenKit
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def tts_engine
|
|
49
|
-
tts_engines.
|
|
49
|
+
tts_engines.first
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def
|
|
53
|
-
@
|
|
52
|
+
def tts_config
|
|
53
|
+
@tts_config ||= begin
|
|
54
54
|
project_tts = if project_config.tts.is_a?(Hash)
|
|
55
55
|
[project_config.tts]
|
|
56
56
|
else
|
|
@@ -63,10 +63,23 @@ module ScreenKit
|
|
|
63
63
|
Array(config.tts)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
episode_tts + project_tts
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def tts_engines
|
|
71
|
+
@tts_engines ||= tts_config.filter_map do |opts|
|
|
72
|
+
next unless opts[:enabled]
|
|
73
|
+
|
|
74
|
+
api_key = options.tts_api_key
|
|
75
|
+
tts_class = TTS.const_get(opts[:engine].camelize)
|
|
76
|
+
tts_preset = options.tts_preset.to_s
|
|
77
|
+
|
|
78
|
+
next if !tts_preset.empty? && tts_preset != opts[:id]
|
|
79
|
+
next unless tts_class.available?(api_key:, **opts)
|
|
80
|
+
|
|
81
|
+
opts = opts.except(:engine, :enabled)
|
|
82
|
+
tts_class.new(**opts, api_key:, segments:)
|
|
70
83
|
end
|
|
71
84
|
end
|
|
72
85
|
|