screenkit 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +46 -0
  3. data/.github/workflows/ruby-tests.yml +21 -1
  4. data/Brewfile +5 -0
  5. data/CHANGELOG.md +11 -0
  6. data/CONTRIBUTING.md +25 -0
  7. data/DOCUMENTATION.md +136 -41
  8. data/Dockerfile +107 -0
  9. data/lib/screen_kit.rb +15 -16
  10. data/lib/screenkit/animation_filters.rb +16 -0
  11. data/lib/screenkit/callout/styles/base.rb +12 -0
  12. data/lib/screenkit/callout/styles/file_copy.rb +26 -0
  13. data/lib/screenkit/callout/styles/inline_block.rb +6 -9
  14. data/lib/screenkit/callout/styles/{default.rb → shadow_block.rb} +9 -11
  15. data/lib/screenkit/callout.rb +1 -1
  16. data/lib/screenkit/cli/episode.rb +2 -3
  17. data/lib/screenkit/cli/root.rb +1 -1
  18. data/lib/screenkit/config/episode.rb +6 -0
  19. data/lib/screenkit/config/project.rb +5 -2
  20. data/lib/screenkit/exporter/demotape.rb +8 -0
  21. data/lib/screenkit/exporter/episode.rb +37 -17
  22. data/lib/screenkit/exporter/segment.rb +262 -71
  23. data/lib/screenkit/generators/episode/callouts/001.yml +12 -0
  24. data/lib/screenkit/generators/episode/config.yml.erb +8 -8
  25. data/lib/screenkit/generators/project/screenkit.yml +46 -15
  26. data/lib/screenkit/schema_validator.rb +1 -1
  27. data/lib/screenkit/schemas/callout_styles/file_copy.json +8 -0
  28. data/lib/screenkit/schemas/callout_styles/inline_block.json +20 -0
  29. data/lib/screenkit/schemas/{callouts/default.json → callout_styles/shadow_block.json} +2 -2
  30. data/lib/screenkit/schemas/callouts/inline_block.json +20 -11
  31. data/lib/screenkit/schemas/callouts/shadow_block.json +39 -0
  32. data/lib/screenkit/schemas/episode.json +6 -43
  33. data/lib/screenkit/schemas/project.json +4 -5
  34. data/lib/screenkit/schemas/refs/anchor.json +1 -1
  35. data/lib/screenkit/schemas/refs/animation.json +1 -1
  36. data/lib/screenkit/schemas/refs/background.json +1 -1
  37. data/lib/screenkit/schemas/refs/{callout.json → callout_style.json} +6 -8
  38. data/lib/screenkit/schemas/refs/color.json +1 -1
  39. data/lib/screenkit/schemas/refs/demotape.json +1 -1
  40. data/lib/screenkit/schemas/refs/demotape_themes.json +1 -1
  41. data/lib/screenkit/schemas/refs/directory.json +1 -1
  42. data/lib/screenkit/schemas/refs/duration.json +1 -1
  43. data/lib/screenkit/schemas/refs/intro.json +1 -1
  44. data/lib/screenkit/schemas/refs/logo.json +1 -1
  45. data/lib/screenkit/schemas/refs/outro.json +1 -1
  46. data/lib/screenkit/schemas/refs/position.json +1 -1
  47. data/lib/screenkit/schemas/refs/scenes.json +1 -1
  48. data/lib/screenkit/schemas/refs/size.json +1 -1
  49. data/lib/screenkit/schemas/refs/sound.json +1 -1
  50. data/lib/screenkit/schemas/refs/spacing.json +1 -1
  51. data/lib/screenkit/schemas/refs/text_style.json +1 -1
  52. data/lib/screenkit/schemas/refs/transition.json +1 -1
  53. data/lib/screenkit/schemas/refs/tts.json +4 -41
  54. data/lib/screenkit/schemas/refs/tts_builtin.json +23 -0
  55. data/lib/screenkit/schemas/refs/watermark.json +1 -1
  56. data/lib/screenkit/schemas/tts/elevenlabs.json +11 -2
  57. data/lib/screenkit/schemas/tts/espeak.json +26 -0
  58. data/lib/screenkit/schemas/tts/say.json +11 -1
  59. data/lib/screenkit/shell.rb +6 -0
  60. data/lib/screenkit/time_formatter.rb +14 -0
  61. data/lib/screenkit/tts/base.rb +21 -0
  62. data/lib/screenkit/tts/eleven_labs.rb +8 -9
  63. data/lib/screenkit/tts/espeak.rb +30 -0
  64. data/lib/screenkit/tts/say.rb +5 -6
  65. data/lib/screenkit/utils.rb +6 -0
  66. data/lib/screenkit/version.rb +1 -1
  67. metadata +21 -42
  68. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Bold.ttf +0 -0
  69. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-BoldItalic.ttf +0 -0
  70. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf +0 -0
  71. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Italic.ttf +0 -0
  72. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Light.ttf +0 -0
  73. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-LightItalic.ttf +0 -0
  74. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Medium.ttf +0 -0
  75. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-MediumItalic.ttf +0 -0
  76. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Regular.ttf +0 -0
  77. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-SemiBoldItalic.ttf +0 -0
  78. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Bold.ttf +0 -0
  79. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-BoldItalic.ttf +0 -0
  80. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-ExtraBold.ttf +0 -0
  81. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  82. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Italic.ttf +0 -0
  83. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Light.ttf +0 -0
  84. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-LightItalic.ttf +0 -0
  85. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Medium.ttf +0 -0
  86. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-MediumItalic.ttf +0 -0
  87. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Regular.ttf +0 -0
  88. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-SemiBold.ttf +0 -0
  89. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  90. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Bold.ttf +0 -0
  91. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  92. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  93. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  94. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Italic.ttf +0 -0
  95. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Light.ttf +0 -0
  96. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  97. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Medium.ttf +0 -0
  98. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  99. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Regular.ttf +0 -0
  100. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  101. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-SemiBoldItalic.ttf +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b105910b064ec4639068b441398fe95290b31a26bc19d0745d96970956a338d
4
- data.tar.gz: 9e84a6b711c03fd3f1765d0fd1dd3cd7fd53857197fb82b480d9ff16c6b08d4b
3
+ metadata.gz: 6cf85172336bdd8b19097835329c4deef29f9d2b57ab5b3caa1f1f2cb5681226
4
+ data.tar.gz: 69ba53b85b77fea9292a1833a3195467a3e782481a67b94b75983c47d89afb24
5
5
  SHA512:
6
- metadata.gz: 98cb59b8738b6bbae13f4a7f0815214975272c967e2b7c7153e1618f61e0e95e16e9fc707802b810d38ec36d9ea0092d88163b472fe6b414c7c2db5c41d249be
7
- data.tar.gz: 6a4679359994481516de5393ca439e867f040f6974fc2b9ded318b37ff341574cb646dc6f2b65a89c8896c5d38f067fdccf4ac29c0688ae0d961080cc996e653
6
+ metadata.gz: 1d98ebece57b893b59766b61ba359f0ef44038e17f08d812eaa870f94f502662a23a18e6582674fd494630b492b3cf5d44f256735ddf690ed5604cf775fcd1f2
7
+ data.tar.gz: be1ad9cddd204eb4ecff40f9eb2d1c2490f713770cb7d3957e7fe870b9bad14838d2fa4abca53f9ebc6207d07fbdd9773d53e4a76d48bea82a9343635754faf9
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: docker-image
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - Dockerfile
9
+ tags:
10
+ - v*
11
+ workflow_dispatch:
12
+ inputs:
13
+ ref:
14
+ description: "Git ref to build the image from"
15
+ required: false
16
+ default: "main"
17
+
18
+ concurrency:
19
+ group: image-workflow
20
+ cancel-in-progress: true
21
+
22
+ jobs:
23
+ build:
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ ref: ${{ github.event.inputs.ref }}
29
+
30
+ - run: >
31
+ echo "IMAGE_TAG=${{ (inputs.ref || github.ref_name) == 'main' &&
32
+ 'latest' || (inputs.ref || github.ref_name) }}" >> $GITHUB_ENV
33
+
34
+ - name: Build Docker Image
35
+ run: |
36
+ docker build -t fnando/screenkit:${{ env.IMAGE_TAG }} .
37
+
38
+ - name: Login to Docker Hub
39
+ uses: docker/login-action@v3
40
+ with:
41
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
42
+ password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
43
+
44
+ - name: Push Docker Image
45
+ run: |
46
+ docker push fnando/screenkit:${{ env.IMAGE_TAG }}
@@ -24,7 +24,7 @@ jobs:
24
24
  - Gemfile
25
25
 
26
26
  steps:
27
- - uses: actions/checkout@v5
27
+ - uses: actions/checkout@v6
28
28
 
29
29
  - uses: actions/cache@v4
30
30
  with:
@@ -36,6 +36,26 @@ jobs:
36
36
  with:
37
37
  ruby-version: ${{ matrix.ruby }}
38
38
 
39
+ - name: Install system dependencies
40
+ run: |
41
+ # Install ImageMagick and zsh
42
+ sudo apt-get update
43
+ sudo apt-get install -y imagemagick zsh espeak
44
+
45
+ # Download and install FFmpeg 8
46
+ wget --quiet https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz
47
+ tar -xf ffmpeg-master-latest-linux64-gpl.tar.xz
48
+ sudo cp ffmpeg-master-latest-linux64-gpl/bin/ffmpeg /usr/local/bin/
49
+ sudo cp ffmpeg-master-latest-linux64-gpl/bin/ffprobe /usr/local/bin/
50
+
51
+ # Download and install ttyd
52
+ wget --quiet https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64
53
+ chmod +x ttyd.x86_64
54
+ sudo mv ttyd.x86_64 /usr/local/bin/ttyd
55
+
56
+ # Install ffmpeg-normalize
57
+ pip3 install ffmpeg-normalize
58
+
39
59
  - name: Install gem dependencies
40
60
  env:
41
61
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
data/Brewfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ brew "ffmpeg"
4
+ brew "espeak"
5
+ brew "imagemagick"
data/CHANGELOG.md CHANGED
@@ -11,6 +11,17 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
+ ## v0.0.3
15
+
16
+ - [Changed] Do not expand path when creating project/episode.
17
+
18
+ ## v0.0.2
19
+
20
+ - [Added] Add file copy callout style, to copy files to the final output path.
21
+ - [Changed] Copy only two OpenSans font files.
22
+ - [Changed] Rename the default callout to `shadow_block`.
23
+ - [Fixed] Changed all JSON Schema files to use a consistent url.
24
+
14
25
  ## v0.0.1
15
26
 
16
27
  - [Added] Allow configuring Demo Tape using episode or project configuration.
data/CONTRIBUTING.md CHANGED
@@ -32,6 +32,31 @@ by [opening an issue on Github](https://github.com/fnando/screenkit/issues).
32
32
  When you're ready to make your pull request, follow checklist below to make sure
33
33
  your contribution is according to how this project works.
34
34
 
35
+ ### Setting up your environment
36
+
37
+ This project requires the following dependencies:
38
+
39
+ - ffmpeg
40
+ - imagemagick
41
+ - espeak
42
+ - ruby
43
+ - ffmpeg-normalize
44
+
45
+ If you are using macOS, you can install them using [Homebrew](https://brew.sh).
46
+ There's a Brewfile at the root directory with the required dependencies:
47
+
48
+ ```sh
49
+ brew bundle install
50
+ ```
51
+
52
+ For ffmpeg-normalize, you can use pip:
53
+
54
+ ```sh
55
+ pip install ffmpeg-normalize --user
56
+ ```
57
+
58
+ ### Guidelines
59
+
35
60
  1. [Fork](https://help.github.com/forking/) screenkit
36
61
  2. Create a topic branch - `git checkout -b my_branch`
37
62
  3. Make your changes using [descriptive commit messages](#commit-messages)
data/DOCUMENTATION.md CHANGED
@@ -36,6 +36,17 @@ Or add to your Gemfile:
36
36
  gem "screenkit"
37
37
  ```
38
38
 
39
+ ### Docker
40
+
41
+ ```bash
42
+ docker run --shm-size=2g -v $PWD:/source --rm -it docker.io/fnando/screenkit
43
+ ```
44
+
45
+ Notice that Chrome requires a lot of memory, so you need `--shm-size=2g` (or
46
+ more).
47
+
48
+ To create a new project:
49
+
39
50
  ---
40
51
 
41
52
  ## Quick Start
@@ -215,7 +226,7 @@ Define reusable callout styles:
215
226
 
216
227
  ```yaml
217
228
  callouts:
218
- default:
229
+ shadow_block:
219
230
  background_color: "#ffff00"
220
231
  shadow: "#2242d3" # Color string or false
221
232
 
@@ -311,7 +322,6 @@ callouts:
311
322
 
312
323
  - **Seconds**: `starts_at: 90` (90 seconds)
313
324
  - **HH:MM:SS**: `starts_at: "00:01:30"` (1 minute 30 seconds)
314
- - **Duration**: Always in seconds or time units (`5s`, `2m`, `1h`)
315
325
 
316
326
  ---
317
327
 
@@ -397,14 +407,15 @@ during the video.
397
407
 
398
408
  ScreenKit provides two built-in callout styles:
399
409
 
400
- #### Default Style
410
+ #### Shadow Block Style
401
411
 
402
- The default style displays a title and body in a box with optional shadow.
412
+ The shadow block style displays a title and body in a box with optional shadow.
403
413
 
404
414
  ```yaml
405
- callouts:
406
- info:
407
- style: default # Optional: defaults to "default"
415
+ callout_styles:
416
+ shadow_block: # This key can be anything.
417
+ style: shadow_block # The callout style name (will resolve to
418
+ # a ruby class).
408
419
  background_color: "#ffff00" # Background color (hex)
409
420
 
410
421
  # Shadow
@@ -445,11 +456,11 @@ callouts:
445
456
  volume: 0.7 # Volume (0.0 to 1.0)
446
457
  ```
447
458
 
448
- **Usage in episode:**
459
+ ##### Usage in episode
449
460
 
450
461
  ```yaml
451
462
  callouts:
452
- - type: info
463
+ - type: shadow_block
453
464
  title: "ScreenKit"
454
465
  body: "Visit https://github.com/fnando/screenkit"
455
466
  starts_at: 3
@@ -463,27 +474,24 @@ similar to syntax highlighting or code comments. Perfect for displaying code
463
474
  snippets, commands, or short inline text.
464
475
 
465
476
  ```yaml
466
- callouts:
467
- code:
468
- style: inline_block
477
+ callouts_styles:
478
+ code: # This key can be anything.
479
+ style: inline_block # Will resolve to a ruby class.
469
480
  background_color: "#000000" # Background color (hex)
470
481
 
471
- # Text Style (single style for all text)
472
482
  text_style:
473
483
  color: "#ffffff"
474
484
  size: 40
475
485
  font_path: open-sans/OpenSans-ExtraBold.ttf
476
486
 
477
487
  # Layout
478
- padding: 20 # Padding around text (pixels)
479
- margin: 100 # Margin from edge (pixels)
480
- anchor: [left, center] # Position anchor point
481
- width: 600 # Maximum width (pixels)
488
+ padding: 20
489
+ margin: 100
490
+ anchor: [left, center]
491
+ width: 600
482
492
 
483
- # Animation
484
- animation: fade # "fade" or "slide"
493
+ animation: fade
485
494
 
486
- # Transitions
487
495
  in_transition:
488
496
  duration: 0.4
489
497
  sound: false
@@ -493,7 +501,7 @@ callouts:
493
501
  sound: false
494
502
  ```
495
503
 
496
- **Usage in episode:**
504
+ ##### Usage in episode
497
505
 
498
506
  ```yaml
499
507
  callouts:
@@ -513,14 +521,91 @@ callouts:
513
521
  duration: 6
514
522
  ```
515
523
 
516
- **Key differences from default style:**
524
+ #### File Copy Style
525
+
526
+ This is not a callout style per se. Instead, you can copy a file to the final
527
+ output directory during episode export. Useful for adding custom assets that may
528
+ be produced outside of ScreenKit.
517
529
 
518
- - Uses `text` instead of `title` and `body`
519
- - Only one `text_style` (no separate title/body styles)
520
- - No `shadow` option
521
- - Each line gets its own background rectangle
522
- - Text can include manual line breaks (`\n`)
523
- - Auto-wraps based on `width` if no line breaks present
530
+ ```yaml
531
+ callouts_styles:
532
+ file_copy: # This key can be anything.
533
+ style: file_copy
534
+
535
+ # Layout
536
+ padding: 20
537
+ margin: 100
538
+ anchor: [left, center]
539
+ width: 600
540
+
541
+ animation: fade
542
+
543
+ in_transition:
544
+ duration: 0.4
545
+ sound: false
546
+
547
+ out_transition:
548
+ duration: 0.3
549
+ sound: false
550
+ ```
551
+
552
+ ##### Usage in episode
553
+
554
+ ```yaml
555
+ callouts:
556
+ - type: file_copy
557
+ file_path: images/social_card.png
558
+ ```
559
+
560
+ ```yaml
561
+ callouts:
562
+ - type: file_copy
563
+ file_path: videos/social_card.mov
564
+ ```
565
+
566
+ > [!NOTE]
567
+ >
568
+ > When copying a video file, the video will be used as an overlay, without
569
+ > resizing or repositioning. That means your overlay needs to be exactly
570
+ > 1920x1080 and 24 FPS. If the video has sound, it will be kept on the final
571
+ > output.
572
+
573
+ #### Custom Callout Styles
574
+
575
+ You can create custom callout styles by placing them in the
576
+ `ScreenKit::Callout::Styles` namespace.
577
+
578
+ ```ruby
579
+ module ScreenKit
580
+ class Callout
581
+ module Styles
582
+ class SomeStyle < Base
583
+ extend SchemaValidator
584
+
585
+ def self.schema_path
586
+ "some/path/to/your/schema.json"
587
+ end
588
+
589
+ # `source` is the way you search for resources.
590
+ # `output_path` is where you must save the generated callout file.
591
+ # `log_path` is where you can write logs (optional).
592
+ def initialize(source:, output_path:, log_path:, **options)
593
+ self.class.validate!(options)
594
+
595
+ @source = source
596
+ @output_path = output_path
597
+ @log_path = log_path
598
+ @options = options
599
+ end
600
+
601
+ def render
602
+ # Generate the image/video that will be used as a callout.
603
+ end
604
+ end
605
+ end
606
+ end
607
+ end
608
+ ```
524
609
 
525
610
  ### Anchor Positions
526
611
 
@@ -607,28 +692,33 @@ module. Custom engines must implement the `generate` method:
607
692
  ```ruby
608
693
  module ScreenKit
609
694
  module TTS
610
- class CustomEngine
695
+ class CustomEngine < Base
611
696
  include Shell
612
- extend SchemaValidator
613
697
 
614
698
  # Optional: Define schema path for validation
615
699
  def self.schema_path
616
700
  ScreenKit.root_dir.join("screenkit/schemas/tts/custom_engine.json")
617
701
  end
618
702
 
619
- def initialize(**options)
620
- @options = options
621
- # Validate options against schema if defined
622
- self.class.validate!(@options) if respond_to?(:validate!)
703
+ # This method is required.
704
+ def available?
705
+ enabled? && command_exist?("some-command")
623
706
  end
624
707
 
708
+ # This method is required.
625
709
  def generate(text:, output_path:, log_path: nil)
710
+ # Optional: validate options against JSON schema.
711
+ self.class.validate!(options)
712
+
626
713
  # Generate audio file from text
627
714
  # Write output to output_path
628
715
  # Optionally log to log_path
629
716
 
630
717
  # Example implementation:
631
- # File.write(output_path, generated_audio_data)
718
+ # run_command "some-command",
719
+ # "-o", output_path.sub_ext(".wav"),
720
+ # text,
721
+ # log_path:
632
722
  end
633
723
  end
634
724
  end
@@ -700,8 +790,12 @@ out_transition:
700
790
  ```
701
791
  episodes/001-episode-name/
702
792
  ├── config.yml # Episode configuration
793
+ ├── callouts/ # Callout definitions
794
+ │ ├── 001.yml # Callouts that will appear on segment 001
795
+ │ ├── 002.yml
796
+ │ └── ...
703
797
  ├── content/ # Terminal recordings
704
- │ ├── 001.tape # VHS tape files
798
+ │ ├── 001.tape # Demo Tape files
705
799
  │ ├── 002.tape
706
800
  │ └── ...
707
801
  ├── scripts/ # Voiceover scripts
@@ -718,9 +812,9 @@ episodes/001-episode-name/
718
812
  └── fonts/
719
813
  ```
720
814
 
721
- ### VHS Tape Files
815
+ ### Demo Tape Files
722
816
 
723
- ScreenKit uses [VHS](https://github.com/charmbracelet/vhs) tape files for
817
+ ScreenKit uses [Demo Tape](https://github.com/fnando/demotape) tape files for
724
818
  terminal recordings:
725
819
 
726
820
  ```tape
@@ -879,7 +973,8 @@ When exporting an episode, ScreenKit:
879
973
 
880
974
  1. **Validates** project and episode configurations
881
975
  2. **Generates voiceovers** from script files (if TTS enabled)
882
- 3. **Renders terminal recordings** from tape files using VHS
976
+ 3. **Renders terminal recordings** from tape files using
977
+ [Demo Tape](https://github.com/fnando/demotape)
883
978
  4. **Combines segments** with crossfade transitions
884
979
  5. **Adds intro/outro** scenes
885
980
  6. **Overlays callouts** with animations
@@ -917,7 +1012,7 @@ be processed).
917
1012
  ### Visuals
918
1013
 
919
1014
  - Use consistent branding across callouts
920
- - Test callout timing with `screenkit callout` command
1015
+ - Test callout formats with `screenkit callout` command
921
1016
  - PNG images with transparency work best for logos and watermarks
922
1017
 
923
1018
  ### Organization
@@ -953,7 +1048,7 @@ bundle exec screenkit ...
953
1048
 
954
1049
  **TTS not working:**
955
1050
 
956
- - For ElevenLabs: Set `--voice-api-key` or `ELEVENLABS_API_KEY` env variable
1051
+ - For ElevenLabs: Set `--voice-api-key`
957
1052
  - For macOS `say`: Verify voice name with `say -v ?`
958
1053
 
959
1054
  ---
data/Dockerfile ADDED
@@ -0,0 +1,107 @@
1
+ # Stage 1: Download and extract fonts
2
+ FROM --platform=linux/amd64 alpine:latest AS fonts
3
+ RUN apk add --no-cache curl unzip
4
+ RUN mkdir /fonts && \
5
+ curl -sSL -o JetBrainsMono.zip https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/JetBrainsMono.zip && \
6
+ unzip -j JetBrainsMono.zip 'JetBrainsMonoNerdFontPropo-*.ttf' -d /fonts
7
+
8
+ # Stage 2: Download binaries
9
+ FROM --platform=linux/amd64 alpine:latest AS binaries
10
+ ARG SLIDES_VERSION=0.9.0
11
+ ARG TTYD_VERSION=1.7.7
12
+ ARG LL_VERSION=0.0.11
13
+ ARG BAT_VERSION=0.26.0
14
+ RUN apk add --no-cache curl
15
+ RUN mkdir /bin-download && cd /bin-download && \
16
+ curl -sSL https://github.com/maaslalani/slides/releases/download/v${SLIDES_VERSION}/slides_${SLIDES_VERSION}_linux_amd64.tar.gz | tar xz && \
17
+ curl -sSL https://github.com/tsl0922/ttyd/releases/download/${TTYD_VERSION}/ttyd.x86_64 > ttyd && \
18
+ curl -sSL https://github.com/fnando/ll/releases/download/v${LL_VERSION}/ll-x86_64-unknown-linux-gnu.tar.gz | tar xz && \
19
+ curl -sSL https://github.com/sharkdp/bat/releases/download/v${BAT_VERSION}/bat-v${BAT_VERSION}-x86_64-unknown-linux-musl.tar.gz | tar xz && \
20
+ mv bat-v${BAT_VERSION}-x86_64-unknown-linux-musl/bat . && \
21
+ chmod +x slides ttyd ll bat
22
+
23
+ # Stage 3: Final image
24
+ FROM --platform=linux/amd64 ruby:3.4-alpine
25
+
26
+ # Install runtime dependencies and build tools
27
+ RUN apk add --no-cache \
28
+ --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main \
29
+ --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \
30
+ bash \
31
+ bash-completion \
32
+ build-base \
33
+ ca-certificates \
34
+ chromium \
35
+ chromium-chromedriver \
36
+ curl \
37
+ fish \
38
+ ffmpeg \
39
+ font-liberation \
40
+ font-noto \
41
+ font-noto-emoji \
42
+ freetype \
43
+ git \
44
+ gtk+3.0 \
45
+ harfbuzz \
46
+ imagemagick \
47
+ jq \
48
+ less \
49
+ mesa-gl \
50
+ nss \
51
+ python3 \
52
+ py3-pip \
53
+ py3-virtualenv \
54
+ sudo \
55
+ ttf-dejavu \
56
+ ttf-freefont \
57
+ udev \
58
+ zsh \
59
+ && fc-cache -f
60
+
61
+ ARG USER=screenkit
62
+ ENV TERM=xterm-256color
63
+ ENV PATH="/venv/bin:/source/bin:/${USER}/bin:$PATH"
64
+ ENV CHROME_BIN=/usr/bin/chromium-browser
65
+ ENV CHROME_PATH=/usr/lib/chromium/
66
+
67
+ # Copy binaries and fonts from builder stages
68
+ COPY --from=binaries /bin-download/slides /usr/local/bin/slides
69
+ COPY --from=binaries /bin-download/ttyd /usr/local/bin/ttyd
70
+ COPY --from=binaries /bin-download/ll /usr/local/bin/ll
71
+ COPY --from=binaries /bin-download/bat /usr/local/bin/bat
72
+ COPY --from=fonts /fonts /usr/local/share/fonts
73
+
74
+ # Update font cache
75
+ RUN fc-cache -f
76
+
77
+ # Create user
78
+ RUN adduser -D -h /${USER} -s /bin/zsh -u 1001 ${USER} \
79
+ && chown -R ${USER}:${USER} /${USER} \
80
+ && echo "${USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USER} \
81
+ && chmod 0440 /etc/sudoers.d/${USER}
82
+
83
+ RUN mkdir -p /venv && chown -R ${USER}:${USER} /venv
84
+ RUN mkdir -p /${USER}-local && chown -R ${USER}:${USER} /${USER}-local
85
+
86
+ # Install screenkit gem
87
+ RUN gem install screenkit && \
88
+ mkdir -p /usr/share/bash-completion/completions && \
89
+ mkdir -p /usr/share/zsh/site-functions && \
90
+ mkdir -p /usr/share/fish/vendor_completions.d && \
91
+ screenkit completion --shell bash > /usr/share/bash-completion/completions/screenkit && \
92
+ screenkit completion --shell zsh > /usr/share/zsh/site-functions/_screenkit && \
93
+ screenkit completion --shell fish > /usr/share/fish/vendor_completions.d/screenkit.fish && \
94
+ echo 'autoload -Uz compinit && compinit' >> /etc/zsh/zshrc && \
95
+ echo 'source /usr/share/bash-completion/bash_completion' >> /etc/bash/bashrc && \
96
+ apk del build-base
97
+
98
+ USER ${USER}
99
+ WORKDIR /${USER}
100
+
101
+ # Create Python virtual environment
102
+ RUN python3 -m venv /venv
103
+ RUN /venv/bin/pip install ffmpeg-normalize
104
+
105
+ WORKDIR /source
106
+
107
+ ENTRYPOINT [ "screenkit" ]
data/lib/screen_kit.rb CHANGED
@@ -19,6 +19,7 @@ module ScreenKit
19
19
  require_relative "screenkit/content_type"
20
20
  require_relative "screenkit/anchor"
21
21
  require_relative "screenkit/banner"
22
+ require_relative "screenkit/time_formatter"
22
23
  require_relative "screenkit/spacing"
23
24
  require_relative "screenkit/watermark"
24
25
  require_relative "screenkit/spinner"
@@ -31,17 +32,15 @@ module ScreenKit
31
32
  require_relative "screenkit/config/episode"
32
33
  require_relative "screenkit/callout"
33
34
  require_relative "screenkit/callout/text_style"
35
+ require_relative "screenkit/callout/styles/base"
34
36
  require_relative "screenkit/transition"
35
37
  require_relative "screenkit/parallel_processor"
36
- require_relative "screenkit/callout/styles/base"
37
- require_relative "screenkit/callout/styles/default"
38
38
  require_relative "screenkit/cli"
39
39
  require_relative "screenkit/cli/base"
40
40
  require_relative "screenkit/cli/episode"
41
41
  require_relative "screenkit/cli/root"
42
- require_relative "screenkit/tts/say"
43
- require_relative "screenkit/tts/eleven_labs"
44
42
  require_relative "screenkit/animation_filters"
43
+ require_relative "screenkit/tts/base"
45
44
  require_relative "screenkit/path_lookup"
46
45
  require_relative "screenkit/sound"
47
46
  require_relative "screenkit/utils"
@@ -54,6 +53,18 @@ module ScreenKit
54
53
  require_relative "screenkit/exporter/image"
55
54
  require_relative "screenkit/exporter/video"
56
55
 
56
+ require_files = lambda do |pattern|
57
+ Gem.find_files_from_load_path(pattern).each do |path|
58
+ next if path.include?("test")
59
+
60
+ require(path)
61
+ end
62
+ end
63
+
64
+ # Load all files that may be available as plugins.
65
+ require_files.call("screenkit/callout/styles/*.rb")
66
+ require_files.call("screenkit/tts/*.rb")
67
+
57
68
  def self.root_dir
58
69
  @root_dir ||= Pathname(__dir__)
59
70
  end
@@ -66,16 +77,4 @@ module ScreenKit
66
77
 
67
78
  # Raised when a file entry is not found in the lookup.
68
79
  FileEntryNotFoundError = Class.new(StandardError)
69
-
70
- require_files = lambda do |pattern|
71
- Gem.find_files_from_load_path(pattern).each do |path|
72
- next if path.include?("test")
73
-
74
- require(path)
75
- end
76
- end
77
-
78
- # Load all files that may be available as plugins.
79
- require_files.call("screenkit/callout/styles/*.rb")
80
- require_files.call("screenkit/callout/tts/*.rb")
81
80
  end
@@ -110,5 +110,21 @@ module ScreenKit
110
110
 
111
111
  {video: filters, out_start:}
112
112
  end
113
+
114
+ def video
115
+ filters = []
116
+
117
+ # For video callouts:
118
+ # Add transparent padding at start to delay, then overlay
119
+ filters <<
120
+ "[#{callout_index}:v]tpad=start_duration=#{starts_at}:" \
121
+ "color=black@0.0[callout#{index}_delayed]"
122
+
123
+ filters <<
124
+ "[#{input_stream}][callout#{index}_delayed]overlay=x=#{x}:y=#{y}:" \
125
+ "eof_action=pass[#{output_stream}]"
126
+
127
+ {video: filters, out_start:}
128
+ end
113
129
  end
114
130
  end
@@ -4,6 +4,18 @@ module ScreenKit
4
4
  class Callout
5
5
  module Styles
6
6
  class Base
7
+ attr_reader :source, :output_path, :log_path
8
+ attr_accessor :options
9
+
10
+ extend SchemaValidator
11
+
12
+ def initialize(source:, output_path:, log_path: nil, **options)
13
+ @source = source
14
+ @output_path = output_path
15
+ @log_path = log_path
16
+ @options = options
17
+ end
18
+
7
19
  def text_wrap(text, max_width:, font_size:)
8
20
  words = text.to_s.split(/\s+/)
9
21
  width_factor = 0.6