screenkit 0.0.10 → 0.0.11
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/CHANGELOG.md +5 -0
- data/DOCUMENTATION.md +75 -9
- data/lib/screenkit/callout/styles/base.rb +1 -100
- data/lib/screenkit/cli/base.rb +1 -1
- data/lib/screenkit/cli/episode.rb +8 -4
- data/lib/screenkit/config.rb +92 -0
- data/lib/screenkit/content_type.rb +2 -1
- data/lib/screenkit/core_ext/hash.rb +27 -0
- data/lib/screenkit/core_ext/json.rb +6 -0
- data/lib/screenkit/core_ext.rb +1 -0
- data/lib/screenkit/exporter/episode.rb +26 -47
- data/lib/screenkit/exporter/intro.rb +94 -60
- data/lib/screenkit/exporter/playwright.rb +33 -0
- data/lib/screenkit/exporter/segment.rb +13 -1
- data/lib/screenkit/generators/episode/config.yml.erb +2 -2
- data/lib/screenkit/generators/project/screenkit.yml +12 -7
- data/lib/screenkit/image_magick.rb +106 -0
- data/lib/screenkit/schemas/{project.json → config.json} +9 -2
- data/lib/screenkit/schemas/refs/intro.json +7 -3
- data/lib/screenkit/schemas/refs/playwright.json +13 -0
- data/lib/screenkit/text_style.rb +42 -0
- data/lib/screenkit/version.rb +1 -1
- data/lib/screenkit.rb +4 -4
- metadata +13 -12
- data/lib/screenkit/callout/text_style.rb +0 -44
- data/lib/screenkit/config/base.rb +0 -58
- data/lib/screenkit/config/episode.rb +0 -48
- data/lib/screenkit/config/project.rb +0 -54
- data/lib/screenkit/schemas/episode.json +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b46db7b67fdb83851578e20864e338d1317eb0c86b2d9498cbde1163742ad0bc
|
|
4
|
+
data.tar.gz: 7216d1335b3031308fffb8d65f9cb64aed60736248f1028bb1dbe802d9ca6e41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66c8b5c9cca753d9096af780a12f95e5ac30fd46bd978e6070505565c780493eded3e6bad4bc6d300f69a20cee2c2dca94ab73f8690c02572b826befa8714139
|
|
7
|
+
data.tar.gz: 85951fb67485665ce3898d3e1130d8363467f896474e5ed901c3b177e7cfd059fd4ed5b47043a0c598f48b4a64d627abc1179ab2cf68d865a9fd7422a009ab6c
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,11 @@ Prefix your message with one of the following:
|
|
|
11
11
|
- [Security] in case of vulnerabilities.
|
|
12
12
|
-->
|
|
13
13
|
|
|
14
|
+
## v0.0.11
|
|
15
|
+
|
|
16
|
+
- [Added] Add support for playwright scripts, so you can have browser-based
|
|
17
|
+
videos.
|
|
18
|
+
|
|
14
19
|
## v0.0.10
|
|
15
20
|
|
|
16
21
|
- [Added] Add `--overwrite-content` and `--overwrite-voiceover` to individually
|
data/DOCUMENTATION.md
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
**Terminal to screencast, simplified**
|
|
4
4
|
|
|
5
5
|
ScreenKit is a Ruby-based tool for creating professional screencasts from
|
|
6
|
-
terminal recordings. It automates the process of combining
|
|
7
|
-
terminal recordings (via
|
|
6
|
+
terminal and browser recordings. It automates the process of combining
|
|
7
|
+
intro/outro scenes, terminal recordings (via
|
|
8
|
+
[demotapes](https://github.com/fnando/demotape)), browser recordings (via
|
|
9
|
+
[@fnando/playwright-video](https://github.com/fnando/playwright-video)),
|
|
8
10
|
voiceovers, background music, callouts (lower thirds), and watermarks into
|
|
9
11
|
polished video content.
|
|
10
12
|
|
|
@@ -141,7 +143,8 @@ screenkit episode new --title "My First Episode"
|
|
|
141
143
|
This creates an episode directory with:
|
|
142
144
|
|
|
143
145
|
- `config.yml` - Episode configuration
|
|
144
|
-
- `content/` -
|
|
146
|
+
- `content/` - Content files (`.tape` files for terminal,
|
|
147
|
+
`.playwright.js`/`.playwright.mjs` for browser)
|
|
145
148
|
- `scripts/` - Voiceover scripts (`.txt` files)
|
|
146
149
|
- `voiceovers/` - Generated voiceover audio
|
|
147
150
|
- `resources/` - Episode-specific resources
|
|
@@ -258,6 +261,18 @@ resources_dir:
|
|
|
258
261
|
- /usr/share/fonts
|
|
259
262
|
```
|
|
260
263
|
|
|
264
|
+
### Playwright Configuration
|
|
265
|
+
|
|
266
|
+
Configure options for Playwright-based browser recordings:
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
playwright:
|
|
270
|
+
color_scheme: dark # Color scheme: "light" or "dark"
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For more options, see
|
|
274
|
+
[@fnando/playwright-video documentation](https://github.com/fnando/playwright-video).
|
|
275
|
+
|
|
261
276
|
### Background Music
|
|
262
277
|
|
|
263
278
|
```yaml
|
|
@@ -524,7 +539,7 @@ callout_styles:
|
|
|
524
539
|
|
|
525
540
|
##### Usage in episode
|
|
526
541
|
|
|
527
|
-
ScreenKit comes with some presets where you don't need to set up anything, but
|
|
542
|
+
ScreenKit comes with some presets where you don't need to set up anything, but
|
|
528
543
|
you can also create custom callouts.
|
|
529
544
|
|
|
530
545
|
Available presets:
|
|
@@ -1001,9 +1016,9 @@ episodes/001-episode-name/
|
|
|
1001
1016
|
│ ├── 001.yml # Callouts that will appear on segment 001
|
|
1002
1017
|
│ ├── 002.yml
|
|
1003
1018
|
│ └── ...
|
|
1004
|
-
├── content/ #
|
|
1019
|
+
├── content/ # Content files (recordings/scripts)
|
|
1005
1020
|
│ ├── 001.tape # Demo Tape files
|
|
1006
|
-
│ ├── 002.
|
|
1021
|
+
│ ├── 002.playwright.js # Playwright scripts
|
|
1007
1022
|
│ └── ...
|
|
1008
1023
|
├── scripts/ # Voiceover scripts
|
|
1009
1024
|
│ ├── 001.txt # Text for TTS
|
|
@@ -1019,7 +1034,11 @@ episodes/001-episode-name/
|
|
|
1019
1034
|
└── fonts/
|
|
1020
1035
|
```
|
|
1021
1036
|
|
|
1022
|
-
###
|
|
1037
|
+
### Content Files
|
|
1038
|
+
|
|
1039
|
+
ScreenKit supports multiple content formats for creating screencast segments:
|
|
1040
|
+
|
|
1041
|
+
#### Demo Tape Files
|
|
1023
1042
|
|
|
1024
1043
|
ScreenKit uses [Demo Tape](https://github.com/fnando/demotape) tape files for
|
|
1025
1044
|
terminal recordings:
|
|
@@ -1038,6 +1057,38 @@ paths accordingly. For instance, `episodes/001-episode-name/content/001.tape`
|
|
|
1038
1057
|
would need to reference `../../resources/some-file` to access
|
|
1039
1058
|
`resources/some-file` in the project's directory.
|
|
1040
1059
|
|
|
1060
|
+
#### Playwright Scripts
|
|
1061
|
+
|
|
1062
|
+
For browser-based recordings, ScreenKit supports Playwright scripts via
|
|
1063
|
+
[@fnando/playwright-video](https://github.com/fnando/playwright-video):
|
|
1064
|
+
|
|
1065
|
+
```javascript
|
|
1066
|
+
// content/001.playwright.js
|
|
1067
|
+
export default async function ({ page }) {
|
|
1068
|
+
await page.goto("https://example.com");
|
|
1069
|
+
await page.click("#button");
|
|
1070
|
+
await page.waitForTimeout(2000);
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
**Requirements:**
|
|
1075
|
+
|
|
1076
|
+
- Install the npm package: `npm install -D @fnando/playwright-video`
|
|
1077
|
+
- Use `.playwright.js` or `.playwright.mjs` file extension
|
|
1078
|
+
|
|
1079
|
+
**Configuration:**
|
|
1080
|
+
|
|
1081
|
+
Add Playwright options to your project configuration:
|
|
1082
|
+
|
|
1083
|
+
```yaml
|
|
1084
|
+
# screenkit.yml
|
|
1085
|
+
playwright:
|
|
1086
|
+
color_scheme: dark # or "light"
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
When running Playwright scripts, the working directory will be the episode
|
|
1090
|
+
directory, similar to Demo Tape files.
|
|
1091
|
+
|
|
1041
1092
|
### Script Files
|
|
1042
1093
|
|
|
1043
1094
|
Plain text files for voiceover generation:
|
|
@@ -1053,8 +1104,11 @@ Today we'll learn how to create amazing screencasts.
|
|
|
1053
1104
|
Files are matched by number:
|
|
1054
1105
|
|
|
1055
1106
|
- `content/001.tape` → `scripts/001.txt` → `voiceovers/001.:ext`
|
|
1107
|
+
- `content/002.playwright.js` → `scripts/002.txt` → `voiceovers/002.:ext`
|
|
1056
1108
|
- Segments are processed in numerical order
|
|
1057
1109
|
- Missing scripts create silent segments
|
|
1110
|
+
- Content files can be `.tape` (Demo Tape), `.playwright.js`/`.playwright.mjs`
|
|
1111
|
+
(Playwright), or media files (video/image)
|
|
1058
1112
|
|
|
1059
1113
|
---
|
|
1060
1114
|
|
|
@@ -1186,8 +1240,13 @@ When exporting an episode, ScreenKit:
|
|
|
1186
1240
|
|
|
1187
1241
|
1. **Validates** project and episode configurations
|
|
1188
1242
|
2. **Generates voiceovers** from script files (if TTS enabled)
|
|
1189
|
-
3. **Renders
|
|
1190
|
-
[Demo Tape](https://github.com/fnando/demotape)
|
|
1243
|
+
3. **Renders content** from:
|
|
1244
|
+
- Terminal recordings via [Demo Tape](https://github.com/fnando/demotape)
|
|
1245
|
+
(`.tape` files)
|
|
1246
|
+
- Browser recordings via
|
|
1247
|
+
[@fnando/playwright-video](https://github.com/fnando/playwright-video)
|
|
1248
|
+
(`.playwright.js`/`.playwright.mjs` files)
|
|
1249
|
+
- Media files (video/image)
|
|
1191
1250
|
4. **Combines segments** with crossfade transitions
|
|
1192
1251
|
5. **Adds intro/outro** scenes
|
|
1193
1252
|
6. **Overlays callouts** with animations
|
|
@@ -1265,6 +1324,13 @@ bundle exec screenkit ...
|
|
|
1265
1324
|
- For macOS `say`: Verify voice name with `say -v ?`
|
|
1266
1325
|
- For `espeak`: Ensure `espeak` is installed and in PATH
|
|
1267
1326
|
|
|
1327
|
+
#### Playwright scripts not working
|
|
1328
|
+
|
|
1329
|
+
- Install the required npm package: `npm install -D @fnando/playwright-video`
|
|
1330
|
+
- Ensure `playwright-video` command is available in PATH
|
|
1331
|
+
- Check file extension is `.playwright.js` or `.playwright.mjs`
|
|
1332
|
+
- Verify Playwright browsers are installed: `npx playwright install`
|
|
1333
|
+
|
|
1268
1334
|
---
|
|
1269
1335
|
|
|
1270
1336
|
## Contributing
|
|
@@ -10,6 +10,7 @@ module ScreenKit
|
|
|
10
10
|
attr_accessor :options
|
|
11
11
|
|
|
12
12
|
extend SchemaValidator
|
|
13
|
+
include ImageMagick
|
|
13
14
|
|
|
14
15
|
def initialize(source:, output_path:, log_path: nil, **options)
|
|
15
16
|
@source = source
|
|
@@ -18,111 +19,11 @@ module ScreenKit
|
|
|
18
19
|
@options = options
|
|
19
20
|
end
|
|
20
21
|
|
|
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.
|
|
26
|
-
def text_wrap(text, max_width:, font_size:)
|
|
27
|
-
words = text.to_s.split(/\s+/)
|
|
28
|
-
width_factor = 0.6
|
|
29
|
-
|
|
30
|
-
[].tap do |lines|
|
|
31
|
-
words.each do |word|
|
|
32
|
-
line = lines.pop.to_s
|
|
33
|
-
word_width = word.size * (font_size * width_factor)
|
|
34
|
-
line_width = line.size * (font_size * width_factor)
|
|
35
|
-
|
|
36
|
-
if line_width + word_width <= max_width
|
|
37
|
-
line = [(line unless line.empty?), word].compact.join(" ")
|
|
38
|
-
lines << line
|
|
39
|
-
else
|
|
40
|
-
lines << line
|
|
41
|
-
lines << word
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Escape text for use in ImageMagick caption.
|
|
48
|
-
def escape_text(text)
|
|
49
|
-
text.gsub("'", "\\\\'")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
22
|
# Remove a file if it exists.
|
|
53
23
|
# @param path [String] The file path to remove.
|
|
54
24
|
def remove_file(path)
|
|
55
25
|
File.unlink(path) if path && File.exist?(path)
|
|
56
26
|
end
|
|
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.
|
|
65
|
-
def render_text_image(text:, style:, width:, type:)
|
|
66
|
-
return [nil, 0, 0] if text.to_s.empty?
|
|
67
|
-
|
|
68
|
-
image = MiniMagick::Image.open(
|
|
69
|
-
create_text_image(text:, style:, width:, type:)
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
[image.path, image.width, image.height]
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Convert values to high resolution (2x).
|
|
76
|
-
# @param value [Object] The value to convert.
|
|
77
|
-
# @return [Object] The converted value.
|
|
78
|
-
def hi_res(value)
|
|
79
|
-
case value
|
|
80
|
-
when Array
|
|
81
|
-
value.map { hi_res(it) }
|
|
82
|
-
when Hash
|
|
83
|
-
value.transform_values { hi_res(it) }
|
|
84
|
-
when Numeric
|
|
85
|
-
value * 2
|
|
86
|
-
else
|
|
87
|
-
value
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Create a text image using MiniMagick.
|
|
92
|
-
# @param text [String] The text to render.
|
|
93
|
-
# @param style [TextStyle] The text style to apply.
|
|
94
|
-
# @param width [Integer] The width of the text image.
|
|
95
|
-
# @param type [String] The ImageMagick text type (e.g., "caption").
|
|
96
|
-
# @return [Array] The path to the generated text image, and the actual
|
|
97
|
-
# `Tempfile` instance.
|
|
98
|
-
def create_text_image(text:, style:, width:, type:)
|
|
99
|
-
hash = SecureRandom.hex(10)
|
|
100
|
-
tmp_path = File.join(Dir.tmpdir, "callout-text-#{hash}.png")
|
|
101
|
-
FileUtils.mkdir_p(File.dirname(tmp_path))
|
|
102
|
-
|
|
103
|
-
MiniMagick.convert do |image|
|
|
104
|
-
unless type == "label"
|
|
105
|
-
image << "-size"
|
|
106
|
-
image << "#{width}x"
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
image << "-background"
|
|
110
|
-
image << "none"
|
|
111
|
-
image << "-fill"
|
|
112
|
-
image << style.color
|
|
113
|
-
image << "-font"
|
|
114
|
-
image << style.font_path.to_s
|
|
115
|
-
image << "-pointsize"
|
|
116
|
-
image << style.size.to_s
|
|
117
|
-
image << "#{type}:#{escape_text(text)}"
|
|
118
|
-
image << "PNG:#{tmp_path}"
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
tmp_path
|
|
122
|
-
rescue MiniMagick::Error => error
|
|
123
|
-
retry if error.message.include?("No such file or directory")
|
|
124
|
-
raise
|
|
125
|
-
end
|
|
126
27
|
end
|
|
127
28
|
end
|
|
128
29
|
end
|
data/lib/screenkit/cli/base.rb
CHANGED
|
@@ -69,10 +69,15 @@ module ScreenKit
|
|
|
69
69
|
def export
|
|
70
70
|
puts Banner.banner if options.banner
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
project_config = if File.file?(options.config)
|
|
73
|
+
Config.load_yaml_file(options.config)
|
|
74
|
+
else
|
|
75
|
+
{}
|
|
76
|
+
end
|
|
73
77
|
|
|
78
|
+
episode_config = File.join(options.dir, "config.yml")
|
|
74
79
|
episode_config = if File.file?(episode_config)
|
|
75
|
-
Config
|
|
80
|
+
Config.load_yaml_file(episode_config)
|
|
76
81
|
else
|
|
77
82
|
{}
|
|
78
83
|
end
|
|
@@ -85,8 +90,7 @@ module ScreenKit
|
|
|
85
90
|
!options.overwrite_voiceover
|
|
86
91
|
|
|
87
92
|
exporter = ScreenKit::Exporter::Episode.new(
|
|
88
|
-
|
|
89
|
-
config: episode_config,
|
|
93
|
+
config: Config.load(**project_config.deep_merge(**episode_config)),
|
|
90
94
|
options:
|
|
91
95
|
)
|
|
92
96
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ScreenKit
|
|
4
|
+
class Config
|
|
5
|
+
using CoreExt
|
|
6
|
+
extend SchemaValidator
|
|
7
|
+
|
|
8
|
+
# The episode title.
|
|
9
|
+
attr_reader :title
|
|
10
|
+
|
|
11
|
+
# The directory where episode source files are stored.
|
|
12
|
+
attr_reader :episode_dir
|
|
13
|
+
|
|
14
|
+
# The directory where resources files are stored.
|
|
15
|
+
attr_reader :resources_dir
|
|
16
|
+
|
|
17
|
+
# The output directory for exported files.
|
|
18
|
+
attr_reader :output_dir
|
|
19
|
+
|
|
20
|
+
# Callout styles
|
|
21
|
+
attr_reader :callout_styles
|
|
22
|
+
|
|
23
|
+
# Scene configurations
|
|
24
|
+
attr_reader :scenes
|
|
25
|
+
|
|
26
|
+
# TTS configuration
|
|
27
|
+
attr_reader :tts
|
|
28
|
+
|
|
29
|
+
# The backtrack music configuration.
|
|
30
|
+
attr_reader :backtrack
|
|
31
|
+
|
|
32
|
+
# The watermark configuration.
|
|
33
|
+
attr_reader :watermark
|
|
34
|
+
|
|
35
|
+
# The demotape configuration.
|
|
36
|
+
attr_reader :demotape
|
|
37
|
+
|
|
38
|
+
# The Playwright configuration.
|
|
39
|
+
attr_reader :playwright
|
|
40
|
+
|
|
41
|
+
def self.schema_path
|
|
42
|
+
@schema_path ||=
|
|
43
|
+
ScreenKit.root_dir.join("schemas/config.json")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.load_yaml_file(path)
|
|
47
|
+
unless File.file?(path)
|
|
48
|
+
raise FileNotFoundError, "Config file not found: #{path}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
template = File.read(path)
|
|
52
|
+
contents = ERB.new(template).result
|
|
53
|
+
|
|
54
|
+
YAML.load(contents, symbolize_names: true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.load_file(path)
|
|
58
|
+
load(load_yaml_file(path))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.load(config)
|
|
62
|
+
validate!(config)
|
|
63
|
+
|
|
64
|
+
new(**config)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(**kwargs)
|
|
68
|
+
kwargs.each do |key, value|
|
|
69
|
+
value = process(key, value)
|
|
70
|
+
instance_variable_set(:"@#{key}", value)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def to_h
|
|
75
|
+
instance_variables.each_with_object({}) do |var, hash|
|
|
76
|
+
key = var.to_s.delete_prefix("@").to_sym
|
|
77
|
+
hash[key] = instance_variable_get(var).as_json
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def process(key, value)
|
|
82
|
+
case key.to_sym
|
|
83
|
+
when :resources_dir
|
|
84
|
+
Array(value)
|
|
85
|
+
when /_(dir|path)$/
|
|
86
|
+
Pathname(value)
|
|
87
|
+
else
|
|
88
|
+
value
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -6,7 +6,8 @@ module ScreenKit
|
|
|
6
6
|
def self.audio = %w[mp3 wav m4a aac aiff]
|
|
7
7
|
def self.image = %w[gif jpg jpeg png tiff]
|
|
8
8
|
def self.demotape = %w[tape]
|
|
9
|
+
def self.playwright = %w[playwright.js playwright.mjs]
|
|
9
10
|
|
|
10
|
-
def self.all = video + image + demotape
|
|
11
|
+
def self.all = video + image + demotape + playwright
|
|
11
12
|
end
|
|
12
13
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ScreenKit
|
|
4
|
+
module CoreExt
|
|
5
|
+
refine NilClass do
|
|
6
|
+
def deep_merge(other)
|
|
7
|
+
{}.deep_merge(other)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
refine Hash do
|
|
12
|
+
def deep_merge(other)
|
|
13
|
+
other = {} if other.nil?
|
|
14
|
+
|
|
15
|
+
merger = lambda do |_key, v1, v2|
|
|
16
|
+
if v1.is_a?(Hash) && v2.is_a?(Hash)
|
|
17
|
+
v1.merge(v2, &merger)
|
|
18
|
+
else
|
|
19
|
+
v2
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
merge(other, &merger)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/screenkit/core_ext.rb
CHANGED
|
@@ -11,12 +11,8 @@ module ScreenKit
|
|
|
11
11
|
# Each file will be used as a segment in the episode.
|
|
12
12
|
CONTENT_PATTERN = "content/**/*.{#{ContentType.all.join(',')}}".freeze
|
|
13
13
|
|
|
14
|
-
# The
|
|
15
|
-
# @return [
|
|
16
|
-
attr_reader :project_config
|
|
17
|
-
|
|
18
|
-
# The episode configuration, usually the episode's config.yml file.
|
|
19
|
-
# @return [ScreenKit::Config::Episode]
|
|
14
|
+
# The merged result of project and episode configurations.
|
|
15
|
+
# @return [Hash]
|
|
20
16
|
attr_reader :config
|
|
21
17
|
|
|
22
18
|
# The export options.
|
|
@@ -29,8 +25,7 @@ module ScreenKit
|
|
|
29
25
|
# The logfile for logging export details.
|
|
30
26
|
attr_reader :logfile
|
|
31
27
|
|
|
32
|
-
def initialize(
|
|
33
|
-
@project_config = project_config
|
|
28
|
+
def initialize(config:, options:)
|
|
34
29
|
@config = config
|
|
35
30
|
@options = options
|
|
36
31
|
@mutex = Mutex.new
|
|
@@ -44,33 +39,23 @@ module ScreenKit
|
|
|
44
39
|
end
|
|
45
40
|
|
|
46
41
|
def demotape_options
|
|
47
|
-
|
|
42
|
+
config.demotape
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def playwright_options
|
|
46
|
+
config.playwright
|
|
48
47
|
end
|
|
49
48
|
|
|
50
49
|
def tts_engine
|
|
51
50
|
tts_engines.first
|
|
52
51
|
end
|
|
53
52
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
project_tts = if project_config.tts.is_a?(Hash)
|
|
57
|
-
[project_config.tts]
|
|
58
|
-
else
|
|
59
|
-
Array(project_config.tts)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
episode_tts = if config.tts.is_a?(Hash)
|
|
63
|
-
[config.tts]
|
|
64
|
-
else
|
|
65
|
-
Array(config.tts)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
episode_tts + project_tts
|
|
69
|
-
end
|
|
53
|
+
def tts_options
|
|
54
|
+
config.tts
|
|
70
55
|
end
|
|
71
56
|
|
|
72
57
|
def tts_engines
|
|
73
|
-
@tts_engines ||=
|
|
58
|
+
@tts_engines ||= tts_options.filter_map do |opts|
|
|
74
59
|
next unless opts[:enabled]
|
|
75
60
|
|
|
76
61
|
api_key = options.tts_api_key
|
|
@@ -197,7 +182,7 @@ module ScreenKit
|
|
|
197
182
|
crossfade_duration = Duration.parse(
|
|
198
183
|
scenes.dig(:segment, :crossfade_duration) || 0.5
|
|
199
184
|
)
|
|
200
|
-
watermark = Watermark.new(config.watermark
|
|
185
|
+
watermark = Watermark.new(config.watermark)
|
|
201
186
|
|
|
202
187
|
watermark_path = if watermark.path
|
|
203
188
|
source.search(watermark.path)
|
|
@@ -418,11 +403,7 @@ module ScreenKit
|
|
|
418
403
|
def prelude
|
|
419
404
|
logfile.json_log(
|
|
420
405
|
:config,
|
|
421
|
-
options.merge(
|
|
422
|
-
pwd: Dir.pwd,
|
|
423
|
-
episode_config: config.to_h,
|
|
424
|
-
project_config: project_config.to_h
|
|
425
|
-
)
|
|
406
|
+
options.merge(pwd: Dir.pwd, config:)
|
|
426
407
|
)
|
|
427
408
|
|
|
428
409
|
log(
|
|
@@ -485,7 +466,7 @@ module ScreenKit
|
|
|
485
466
|
def output_dir
|
|
486
467
|
@output_dir ||= Pathname(
|
|
487
468
|
format(
|
|
488
|
-
options.output_dir ||
|
|
469
|
+
options.output_dir || config.output_dir.to_s,
|
|
489
470
|
episode_dirname: root_dir.basename
|
|
490
471
|
)
|
|
491
472
|
).expand_path
|
|
@@ -504,11 +485,11 @@ module ScreenKit
|
|
|
504
485
|
spinner.update("Exporting intro…")
|
|
505
486
|
|
|
506
487
|
intro_config = scenes.fetch(:intro)
|
|
488
|
+
intro_config[:title][:text] = config.title
|
|
489
|
+
|
|
507
490
|
log_path = logfile.create(:intro)
|
|
508
491
|
|
|
509
|
-
Intro
|
|
510
|
-
.new(config: intro_config, text: config.title, source:, log_path:)
|
|
511
|
-
.export(intro_path)
|
|
492
|
+
Intro.new(config: intro_config, source:, log_path:).export(intro_path)
|
|
512
493
|
|
|
513
494
|
spinner.stop
|
|
514
495
|
end
|
|
@@ -542,7 +523,7 @@ module ScreenKit
|
|
|
542
523
|
end
|
|
543
524
|
|
|
544
525
|
def scenes
|
|
545
|
-
|
|
526
|
+
config.scenes
|
|
546
527
|
end
|
|
547
528
|
|
|
548
529
|
def project_root_dir
|
|
@@ -554,7 +535,7 @@ module ScreenKit
|
|
|
554
535
|
end
|
|
555
536
|
|
|
556
537
|
def resources_dir
|
|
557
|
-
@resources_dir ||=
|
|
538
|
+
@resources_dir ||= config.resources_dir.map do |dir|
|
|
558
539
|
path = dir
|
|
559
540
|
path = File.expand_path(dir) if dir.start_with?("~")
|
|
560
541
|
path = Pathname(format(path, episode_dir: root_dir))
|
|
@@ -564,7 +545,8 @@ module ScreenKit
|
|
|
564
545
|
end
|
|
565
546
|
|
|
566
547
|
def callout_styles
|
|
567
|
-
|
|
548
|
+
@callout_styles ||= project_config.callout_styles
|
|
549
|
+
.deep_merge(config.callout_styles)
|
|
568
550
|
end
|
|
569
551
|
|
|
570
552
|
def output_video_path
|
|
@@ -578,14 +560,11 @@ module ScreenKit
|
|
|
578
560
|
end
|
|
579
561
|
|
|
580
562
|
def backtrack
|
|
581
|
-
@backtrack ||=
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
else
|
|
587
|
-
Sound.new(input: mute_sound_path, source:)
|
|
588
|
-
end
|
|
563
|
+
@backtrack ||= if config.backtrack
|
|
564
|
+
Sound.new(input: config.backtrack, source:)
|
|
565
|
+
else
|
|
566
|
+
Sound.new(input: mute_sound_path, source:)
|
|
567
|
+
end
|
|
589
568
|
end
|
|
590
569
|
|
|
591
570
|
def segments
|