screenkit 0.0.1 → 0.0.2

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +44 -0
  3. data/.github/workflows/ruby-tests.yml +21 -1
  4. data/Brewfile +5 -0
  5. data/CHANGELOG.md +7 -0
  6. data/CONTRIBUTING.md +25 -0
  7. data/DOCUMENTATION.md +125 -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 +1 -1
  17. data/lib/screenkit/config/episode.rb +6 -0
  18. data/lib/screenkit/config/project.rb +5 -2
  19. data/lib/screenkit/exporter/demotape.rb +8 -0
  20. data/lib/screenkit/exporter/episode.rb +37 -17
  21. data/lib/screenkit/exporter/segment.rb +262 -71
  22. data/lib/screenkit/generators/episode/callouts/001.yml +12 -0
  23. data/lib/screenkit/generators/episode/config.yml.erb +8 -8
  24. data/lib/screenkit/generators/project/screenkit.yml +46 -15
  25. data/lib/screenkit/schema_validator.rb +1 -1
  26. data/lib/screenkit/schemas/callout_styles/file_copy.json +8 -0
  27. data/lib/screenkit/schemas/callout_styles/inline_block.json +20 -0
  28. data/lib/screenkit/schemas/{callouts/default.json → callout_styles/shadow_block.json} +2 -2
  29. data/lib/screenkit/schemas/callouts/inline_block.json +20 -11
  30. data/lib/screenkit/schemas/callouts/shadow_block.json +39 -0
  31. data/lib/screenkit/schemas/episode.json +6 -43
  32. data/lib/screenkit/schemas/project.json +4 -5
  33. data/lib/screenkit/schemas/refs/anchor.json +1 -1
  34. data/lib/screenkit/schemas/refs/animation.json +1 -1
  35. data/lib/screenkit/schemas/refs/background.json +1 -1
  36. data/lib/screenkit/schemas/refs/{callout.json → callout_style.json} +6 -8
  37. data/lib/screenkit/schemas/refs/color.json +1 -1
  38. data/lib/screenkit/schemas/refs/demotape.json +1 -1
  39. data/lib/screenkit/schemas/refs/demotape_themes.json +1 -1
  40. data/lib/screenkit/schemas/refs/directory.json +1 -1
  41. data/lib/screenkit/schemas/refs/duration.json +1 -1
  42. data/lib/screenkit/schemas/refs/intro.json +1 -1
  43. data/lib/screenkit/schemas/refs/logo.json +1 -1
  44. data/lib/screenkit/schemas/refs/outro.json +1 -1
  45. data/lib/screenkit/schemas/refs/position.json +1 -1
  46. data/lib/screenkit/schemas/refs/scenes.json +1 -1
  47. data/lib/screenkit/schemas/refs/size.json +1 -1
  48. data/lib/screenkit/schemas/refs/sound.json +1 -1
  49. data/lib/screenkit/schemas/refs/spacing.json +1 -1
  50. data/lib/screenkit/schemas/refs/text_style.json +1 -1
  51. data/lib/screenkit/schemas/refs/transition.json +1 -1
  52. data/lib/screenkit/schemas/refs/tts.json +4 -41
  53. data/lib/screenkit/schemas/refs/tts_builtin.json +23 -0
  54. data/lib/screenkit/schemas/refs/watermark.json +1 -1
  55. data/lib/screenkit/schemas/tts/elevenlabs.json +11 -2
  56. data/lib/screenkit/schemas/tts/espeak.json +26 -0
  57. data/lib/screenkit/schemas/tts/say.json +11 -1
  58. data/lib/screenkit/shell.rb +6 -0
  59. data/lib/screenkit/time_formatter.rb +14 -0
  60. data/lib/screenkit/tts/base.rb +21 -0
  61. data/lib/screenkit/tts/eleven_labs.rb +8 -9
  62. data/lib/screenkit/tts/espeak.rb +30 -0
  63. data/lib/screenkit/tts/say.rb +5 -6
  64. data/lib/screenkit/utils.rb +6 -0
  65. data/lib/screenkit/version.rb +1 -1
  66. metadata +21 -42
  67. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Bold.ttf +0 -0
  68. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-BoldItalic.ttf +0 -0
  69. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-ExtraBoldItalic.ttf +0 -0
  70. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Italic.ttf +0 -0
  71. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Light.ttf +0 -0
  72. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-LightItalic.ttf +0 -0
  73. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Medium.ttf +0 -0
  74. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-MediumItalic.ttf +0 -0
  75. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-Regular.ttf +0 -0
  76. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans-SemiBoldItalic.ttf +0 -0
  77. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Bold.ttf +0 -0
  78. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-BoldItalic.ttf +0 -0
  79. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-ExtraBold.ttf +0 -0
  80. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-ExtraBoldItalic.ttf +0 -0
  81. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Italic.ttf +0 -0
  82. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Light.ttf +0 -0
  83. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-LightItalic.ttf +0 -0
  84. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Medium.ttf +0 -0
  85. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-MediumItalic.ttf +0 -0
  86. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-Regular.ttf +0 -0
  87. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-SemiBold.ttf +0 -0
  88. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_Condensed-SemiBoldItalic.ttf +0 -0
  89. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Bold.ttf +0 -0
  90. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-BoldItalic.ttf +0 -0
  91. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-ExtraBold.ttf +0 -0
  92. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  93. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Italic.ttf +0 -0
  94. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Light.ttf +0 -0
  95. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-LightItalic.ttf +0 -0
  96. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Medium.ttf +0 -0
  97. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-MediumItalic.ttf +0 -0
  98. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-Regular.ttf +0 -0
  99. data/lib/screenkit/generators/project/resources/fonts/open-sans/OpenSans_SemiCondensed-SemiBold.ttf +0 -0
  100. 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: a4b1ef8eb6ede0220cc3e26f1c038c6ec247a62de276bf5e4df486b01885dcff
4
+ data.tar.gz: 7df82eb1ab87719a2efa7b205c0265a383d93ef574ac30c8ea09d68349df008a
5
5
  SHA512:
6
- metadata.gz: 98cb59b8738b6bbae13f4a7f0815214975272c967e2b7c7153e1618f61e0e95e16e9fc707802b810d38ec36d9ea0092d88163b472fe6b414c7c2db5c41d249be
7
- data.tar.gz: 6a4679359994481516de5393ca439e867f040f6974fc2b9ded318b37ff341574cb646dc6f2b65a89c8896c5d38f067fdccf4ac29c0688ae0d961080cc996e653
6
+ metadata.gz: b3741fd30857808cbb6dd16db3b3ab80b401ddfe8c401d508eccb8b37b7cf4f1668858f9d462d3c9d5b30b7c88fb9bf3fd3c7eef6e6afa7c229a60fcf08a5ecb
7
+ data.tar.gz: b8b68adde08a2c44fad33181ef3604e955988cc21d8a2a24a33a39963ac3c437792ce96f3e990d76454e9ee6c6a198d885f868ab828c3c82b85f5db366b32b21
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: docker-image
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - Dockerfile
9
+ workflow_dispatch:
10
+ inputs:
11
+ ref:
12
+ description: "Git ref to build the image from"
13
+ required: false
14
+ default: "main"
15
+
16
+ concurrency:
17
+ group: image-workflow
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ with:
26
+ ref: ${{ github.event.inputs.ref }}
27
+
28
+ - run: >
29
+ echo "IMAGE_TAG=${{ (inputs.ref || github.ref_name) == 'main' &&
30
+ 'latest' || (inputs.ref || github.ref_name) }}" >> $GITHUB_ENV
31
+
32
+ - name: Build Docker Image
33
+ run: |
34
+ docker build -t fnando/screenkit:${{ env.IMAGE_TAG }} .
35
+
36
+ - name: Login to Docker Hub
37
+ uses: docker/login-action@v3
38
+ with:
39
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
40
+ password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
41
+
42
+ - name: Push Docker Image
43
+ run: |
44
+ 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
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,13 @@ Prefix your message with one of the following:
11
11
  - [Security] in case of vulnerabilities.
12
12
  -->
13
13
 
14
+ ## v0.0.2
15
+
16
+ - [Added] Add file copy callout style, to copy files to the final output path.
17
+ - [Changed] Copy only two OpenSans font files.
18
+ - [Changed] Rename the default callout to `shadow_block`.
19
+ - [Fixed] Changed all JSON Schema files to use a consistent url.
20
+
14
21
  ## v0.0.1
15
22
 
16
23
  - [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
@@ -215,7 +215,7 @@ Define reusable callout styles:
215
215
 
216
216
  ```yaml
217
217
  callouts:
218
- default:
218
+ shadow_block:
219
219
  background_color: "#ffff00"
220
220
  shadow: "#2242d3" # Color string or false
221
221
 
@@ -311,7 +311,6 @@ callouts:
311
311
 
312
312
  - **Seconds**: `starts_at: 90` (90 seconds)
313
313
  - **HH:MM:SS**: `starts_at: "00:01:30"` (1 minute 30 seconds)
314
- - **Duration**: Always in seconds or time units (`5s`, `2m`, `1h`)
315
314
 
316
315
  ---
317
316
 
@@ -397,14 +396,15 @@ during the video.
397
396
 
398
397
  ScreenKit provides two built-in callout styles:
399
398
 
400
- #### Default Style
399
+ #### Shadow Block Style
401
400
 
402
- The default style displays a title and body in a box with optional shadow.
401
+ The shadow block style displays a title and body in a box with optional shadow.
403
402
 
404
403
  ```yaml
405
- callouts:
406
- info:
407
- style: default # Optional: defaults to "default"
404
+ callout_styles:
405
+ shadow_block: # This key can be anything.
406
+ style: shadow_block # The callout style name (will resolve to
407
+ # a ruby class).
408
408
  background_color: "#ffff00" # Background color (hex)
409
409
 
410
410
  # Shadow
@@ -445,11 +445,11 @@ callouts:
445
445
  volume: 0.7 # Volume (0.0 to 1.0)
446
446
  ```
447
447
 
448
- **Usage in episode:**
448
+ ##### Usage in episode
449
449
 
450
450
  ```yaml
451
451
  callouts:
452
- - type: info
452
+ - type: shadow_block
453
453
  title: "ScreenKit"
454
454
  body: "Visit https://github.com/fnando/screenkit"
455
455
  starts_at: 3
@@ -463,27 +463,24 @@ similar to syntax highlighting or code comments. Perfect for displaying code
463
463
  snippets, commands, or short inline text.
464
464
 
465
465
  ```yaml
466
- callouts:
467
- code:
468
- style: inline_block
466
+ callouts_styles:
467
+ code: # This key can be anything.
468
+ style: inline_block # Will resolve to a ruby class.
469
469
  background_color: "#000000" # Background color (hex)
470
470
 
471
- # Text Style (single style for all text)
472
471
  text_style:
473
472
  color: "#ffffff"
474
473
  size: 40
475
474
  font_path: open-sans/OpenSans-ExtraBold.ttf
476
475
 
477
476
  # 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)
477
+ padding: 20
478
+ margin: 100
479
+ anchor: [left, center]
480
+ width: 600
482
481
 
483
- # Animation
484
- animation: fade # "fade" or "slide"
482
+ animation: fade
485
483
 
486
- # Transitions
487
484
  in_transition:
488
485
  duration: 0.4
489
486
  sound: false
@@ -493,7 +490,7 @@ callouts:
493
490
  sound: false
494
491
  ```
495
492
 
496
- **Usage in episode:**
493
+ ##### Usage in episode
497
494
 
498
495
  ```yaml
499
496
  callouts:
@@ -513,14 +510,91 @@ callouts:
513
510
  duration: 6
514
511
  ```
515
512
 
516
- **Key differences from default style:**
513
+ #### File Copy Style
517
514
 
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
515
+ This is not a callout style per se. Instead, you can copy a file to the final
516
+ output directory during episode export. Useful for adding custom assets that may
517
+ be produced outside of ScreenKit.
518
+
519
+ ```yaml
520
+ callouts_styles:
521
+ file_copy: # This key can be anything.
522
+ style: file_copy
523
+
524
+ # Layout
525
+ padding: 20
526
+ margin: 100
527
+ anchor: [left, center]
528
+ width: 600
529
+
530
+ animation: fade
531
+
532
+ in_transition:
533
+ duration: 0.4
534
+ sound: false
535
+
536
+ out_transition:
537
+ duration: 0.3
538
+ sound: false
539
+ ```
540
+
541
+ ##### Usage in episode
542
+
543
+ ```yaml
544
+ callouts:
545
+ - type: file_copy
546
+ file_path: images/social_card.png
547
+ ```
548
+
549
+ ```yaml
550
+ callouts:
551
+ - type: file_copy
552
+ file_path: videos/social_card.mov
553
+ ```
554
+
555
+ > [!NOTE]
556
+ >
557
+ > When copying a video file, the video will be used as an overlay, without
558
+ > resizing or repositioning. That means your overlay needs to be exactly
559
+ > 1920x1080 and 24 FPS. If the video has sound, it will be kept on the final
560
+ > output.
561
+
562
+ #### Custom Callout Styles
563
+
564
+ You can create custom callout styles by placing them in the
565
+ `ScreenKit::Callout::Styles` namespace.
566
+
567
+ ```ruby
568
+ module ScreenKit
569
+ class Callout
570
+ module Styles
571
+ class SomeStyle < Base
572
+ extend SchemaValidator
573
+
574
+ def self.schema_path
575
+ "some/path/to/your/schema.json"
576
+ end
577
+
578
+ # `source` is the way you search for resources.
579
+ # `output_path` is where you must save the generated callout file.
580
+ # `log_path` is where you can write logs (optional).
581
+ def initialize(source:, output_path:, log_path:, **options)
582
+ self.class.validate!(options)
583
+
584
+ @source = source
585
+ @output_path = output_path
586
+ @log_path = log_path
587
+ @options = options
588
+ end
589
+
590
+ def render
591
+ # Generate the image/video that will be used as a callout.
592
+ end
593
+ end
594
+ end
595
+ end
596
+ end
597
+ ```
524
598
 
525
599
  ### Anchor Positions
526
600
 
@@ -607,28 +681,33 @@ module. Custom engines must implement the `generate` method:
607
681
  ```ruby
608
682
  module ScreenKit
609
683
  module TTS
610
- class CustomEngine
684
+ class CustomEngine < Base
611
685
  include Shell
612
- extend SchemaValidator
613
686
 
614
687
  # Optional: Define schema path for validation
615
688
  def self.schema_path
616
689
  ScreenKit.root_dir.join("screenkit/schemas/tts/custom_engine.json")
617
690
  end
618
691
 
619
- def initialize(**options)
620
- @options = options
621
- # Validate options against schema if defined
622
- self.class.validate!(@options) if respond_to?(:validate!)
692
+ # This method is required.
693
+ def available?
694
+ enabled? && command_exist?("some-command")
623
695
  end
624
696
 
697
+ # This method is required.
625
698
  def generate(text:, output_path:, log_path: nil)
699
+ # Optional: validate options against JSON schema.
700
+ self.class.validate!(options)
701
+
626
702
  # Generate audio file from text
627
703
  # Write output to output_path
628
704
  # Optionally log to log_path
629
705
 
630
706
  # Example implementation:
631
- # File.write(output_path, generated_audio_data)
707
+ # run_command "some-command",
708
+ # "-o", output_path.sub_ext(".wav"),
709
+ # text,
710
+ # log_path:
632
711
  end
633
712
  end
634
713
  end
@@ -700,8 +779,12 @@ out_transition:
700
779
  ```
701
780
  episodes/001-episode-name/
702
781
  ├── config.yml # Episode configuration
782
+ ├── callouts/ # Callout definitions
783
+ │ ├── 001.yml # Callouts that will appear on segment 001
784
+ │ ├── 002.yml
785
+ │ └── ...
703
786
  ├── content/ # Terminal recordings
704
- │ ├── 001.tape # VHS tape files
787
+ │ ├── 001.tape # Demo Tape files
705
788
  │ ├── 002.tape
706
789
  │ └── ...
707
790
  ├── scripts/ # Voiceover scripts
@@ -718,9 +801,9 @@ episodes/001-episode-name/
718
801
  └── fonts/
719
802
  ```
720
803
 
721
- ### VHS Tape Files
804
+ ### Demo Tape Files
722
805
 
723
- ScreenKit uses [VHS](https://github.com/charmbracelet/vhs) tape files for
806
+ ScreenKit uses [Demo Tape](https://github.com/fnando/demotape) tape files for
724
807
  terminal recordings:
725
808
 
726
809
  ```tape
@@ -879,7 +962,8 @@ When exporting an episode, ScreenKit:
879
962
 
880
963
  1. **Validates** project and episode configurations
881
964
  2. **Generates voiceovers** from script files (if TTS enabled)
882
- 3. **Renders terminal recordings** from tape files using VHS
965
+ 3. **Renders terminal recordings** from tape files using
966
+ [Demo Tape](https://github.com/fnando/demotape)
883
967
  4. **Combines segments** with crossfade transitions
884
968
  5. **Adds intro/outro** scenes
885
969
  6. **Overlays callouts** with animations
@@ -917,7 +1001,7 @@ be processed).
917
1001
  ### Visuals
918
1002
 
919
1003
  - Use consistent branding across callouts
920
- - Test callout timing with `screenkit callout` command
1004
+ - Test callout formats with `screenkit callout` command
921
1005
  - PNG images with transparency work best for logos and watermarks
922
1006
 
923
1007
  ### Organization
@@ -953,7 +1037,7 @@ bundle exec screenkit ...
953
1037
 
954
1038
  **TTS not working:**
955
1039
 
956
- - For ElevenLabs: Set `--voice-api-key` or `ELEVENLABS_API_KEY` env variable
1040
+ - For ElevenLabs: Set `--voice-api-key`
957
1041
  - For macOS `say`: Verify voice name with `say -v ?`
958
1042
 
959
1043
  ---
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
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ScreenKit
4
+ class Callout
5
+ module Styles
6
+ class FileCopy < Base
7
+ def self.schema_path
8
+ ScreenKit.root_dir
9
+ .join("screenkit/schemas/callout_styles/file_copy.json")
10
+ end
11
+
12
+ def initialize(source:, **kwargs)
13
+ self.class.validate!(kwargs)
14
+ super
15
+ end
16
+
17
+ def render
18
+ ext = File.extname(options[:file_path])
19
+ FileUtils.mkdir_p(File.dirname(output_path))
20
+ FileUtils.cp source.search(options[:file_path]),
21
+ output_path.sub_ext(ext)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end