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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6686849b372a2cc81451fb2855e2d97704c6584b5fc5f6f119df6f49d3fadb60
4
- data.tar.gz: 585e7481be4aa3b27eed57b2ed62424ed809ade1f15975255e9a71b16d912ee3
3
+ metadata.gz: 3dc4a6ebfa5fb6725aa40e4bbe699bcafca860135ef8cca01fc48ab66731392f
4
+ data.tar.gz: 42df45d24c11f800e20fff4dfc030102bef5747f758ee47378d98a56dd8732f8
5
5
  SHA512:
6
- metadata.gz: 2c1016f94efb78c43c09854d0de46df93c99f45a0f9683d0b8f7d399fdeca891378fc5bf3b9e2f14be06ad467259dbb7c987622c4916bbce940832404b492571
7
- data.tar.gz: 92ed7474f18ee43689ff8d2977b04633193bd195a274483137fe0cfd103b213ab968e0cb3eafcdefa1a192fe62563aa402c0742b01dbc77425cc764dd8d2a9ae
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@v4
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] Added `--skip-bundler` option to skip bundler when generating
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] Fixed Gemfile template that was pointing to a local path.
29
+ - [Fixed] Fix Gemfile template that was pointing to a local path.
22
30
 
23
31
  ## v0.0.3
24
32
 
25
- - [Changed] Do not expand path when creating project/episode.
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 --shm-size=2g -v $PWD:/source --rm -it docker.io/fnando/screenkit
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
- - `--voice-api-key` - API key for TTS service (e.g., ElevenLabs)
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 Definitions
291
+ ### Callout Styles
224
292
 
225
293
  Define reusable callout styles:
226
294
 
227
295
  ```yaml
228
- callouts:
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
- engine: elevenlabs
294
- voice_id: custom_voice_id
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
- engine: say
646
- voice: Alex # Optional: Voice name
647
- rate: 150 # Words per minute (optional)
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
- engine: elevenlabs
657
- voice_id: "56AoDkrOh6qfVPDXZ7Pt" # Required: ElevenLabs voice ID
658
- language_code: en # 2-letter language code
659
-
660
- # Optional: Voice settings
661
- voice_settings:
662
- speed: 0.9 # Speech speed (default: 1.0)
663
- stability: 0.5 # Voice stability (0.0 - 1.0)
664
- similarity: 0.75 # Voice similarity (0.0 - 1.0)
665
- style: 0.0 # Speaking style (0.0+)
666
-
667
- # Optional: Output format
668
- output_format: mp3_44100_128
669
-
670
- # Optional: Model ID
671
- model_id: eleven_monolingual_v1
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
- include Shell
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
- enabled? && command_exist?("some-command")
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
- # "-o", output_path.sub_ext(".wav"),
720
- # text,
721
- # log_path:
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
- engine: custom_engine # Camelized to CustomEngine
733
- # Add your custom options here
734
- api_key: your_api_key
735
- custom_option: value
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.aiff`
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=../../schemas/project.json
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 `--voice-api-key`
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
@@ -34,6 +34,7 @@ RUN apk add --no-cache \
34
34
  chromium \
35
35
  chromium-chromedriver \
36
36
  curl \
37
+ espeak \
37
38
  fish \
38
39
  ffmpeg \
39
40
  font-liberation \
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 = Config::Episode.load_file(
62
- File.join(options.dir, "config.yml")
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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module ScreenKit
4
6
  module CoreExt
5
7
  refine JSON.singleton_class do
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "core_ext/string"
4
+ require_relative "core_ext/json"
@@ -27,7 +27,8 @@ module ScreenKit
27
27
  "--fps", 24,
28
28
  "--overwrite",
29
29
  "--output-path", output_path,
30
- log_path:
30
+ log_path:,
31
+ chdir: demotape_path.parent.parent
31
32
  end
32
33
 
33
34
  def options_to_args(options)
@@ -38,7 +38,7 @@ module ScreenKit
38
38
  end
39
39
 
40
40
  def tts_available?
41
- tts_engines.any?(&:available?)
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.find(&:available?)
49
+ tts_engines.first
50
50
  end
51
51
 
52
- def tts_engines
53
- @tts_engines ||= begin
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
- (episode_tts + project_tts).map do |opts|
67
- TTS.const_get(opts[:engine].camelize)
68
- .new(**opts.except(:engine), api_key: options.tts_api_key)
69
- end
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