@kolbo/kolbo-code-linux-arm64-musl 1.1.72 → 1.1.73
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.
- package/bin/kolbo +0 -0
- package/package.json +1 -1
- package/skills/color-grading/SKILL.md +152 -0
- package/skills/ffmpeg-patterns/SKILL.md +240 -0
- package/skills/image-prompting-guide/SKILL.md +143 -0
- package/skills/kolbo/SKILL.md +29 -0
- package/skills/music-prompting/SKILL.md +146 -0
- package/skills/production-review/SKILL.md +152 -0
- package/skills/short-form-video/SKILL.md +168 -0
- package/skills/sound-design/SKILL.md +154 -0
- package/skills/storytelling/SKILL.md +139 -0
- package/skills/subtitle-production/SKILL.md +244 -0
- package/skills/subtitle-production/reference/burn_to_video.py +222 -0
- package/skills/subtitle-production/reference/export_srts.py +127 -0
- package/skills/subtitle-production/reference/gen_srt.py +42 -0
- package/skills/typography-video/SKILL.md +182 -0
- package/skills/typography-video/reference/KineticTitleScene.tsx +345 -0
- package/skills/video-editing/SKILL.md +128 -0
- package/skills/video-production/SKILL.md +7 -8
- package/skills/video-prompting-guide/SKILL.md +268 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Export SRT subtitle files for each video and each individual chapter.
|
|
3
|
+
- Full video SRT: placed next to the edited MP4
|
|
4
|
+
- Per-chapter SRTs: placed in each chapter's subfolder, timestamps zeroed to chapter start
|
|
5
|
+
"""
|
|
6
|
+
import json, os
|
|
7
|
+
|
|
8
|
+
def fmt_srt_time(seconds):
|
|
9
|
+
"""Format seconds as SRT timestamp: HH:MM:SS,mmm"""
|
|
10
|
+
h = int(seconds // 3600)
|
|
11
|
+
m = int((seconds % 3600) // 60)
|
|
12
|
+
s = seconds % 60
|
|
13
|
+
return f"{h:02d}:{m:02d}:{s:06.3f}".replace('.', ',')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def sentences_to_srt(sentences, offset=0.0):
|
|
17
|
+
"""Convert sentence list to SRT text, subtracting offset from all timestamps."""
|
|
18
|
+
blocks = []
|
|
19
|
+
for i, s in enumerate(sentences, 1):
|
|
20
|
+
start = max(0.0, s['start'] - offset)
|
|
21
|
+
end = max(0.0, s['end'] - offset)
|
|
22
|
+
blocks.append(f"{i}\n{fmt_srt_time(start)} --> {fmt_srt_time(end)}\n{s['text']}\n")
|
|
23
|
+
return '\n'.join(blocks)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_sentences_in_range(sentences, start_time, end_time):
|
|
27
|
+
"""Get all sentences that overlap with the given time range."""
|
|
28
|
+
result = []
|
|
29
|
+
for s in sentences:
|
|
30
|
+
# Include sentence if it overlaps with the range
|
|
31
|
+
if s['end'] > start_time and s['start'] < end_time:
|
|
32
|
+
result.append(s)
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
JOBS = [
|
|
37
|
+
{
|
|
38
|
+
"name": "lior_course_01",
|
|
39
|
+
"transcript": r"G:\Projects\Master Agent\ytp_jobs\lior_course_01\transcript.json",
|
|
40
|
+
"chapters_json": r"G:\Projects\Master Agent\ytp_jobs\lior_course_01\chapters.json",
|
|
41
|
+
"full_srt_path": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\1 - היכרות עם Kolbo.AI - edited.srt",
|
|
42
|
+
"chapters_dir": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\1 - היכרות עם Kolbo.AI",
|
|
43
|
+
"video_duration": 540.2,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "lior_course_02",
|
|
47
|
+
"transcript": r"G:\Projects\Master Agent\ytp_jobs\lior_course_02\transcript.json",
|
|
48
|
+
"chapters_json": r"G:\Projects\Master Agent\ytp_jobs\lior_course_02\chapters.json",
|
|
49
|
+
"full_srt_path": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\2 - הסבר על פרוייקטים - edited.srt",
|
|
50
|
+
"chapters_dir": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\2 - הסבר על פרוייקטים",
|
|
51
|
+
"video_duration": 1643.0,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "lior_course_03",
|
|
55
|
+
"transcript": r"G:\Projects\Master Agent\ytp_jobs\lior_course_03\transcript.json",
|
|
56
|
+
"chapters_json": r"G:\Projects\Master Agent\ytp_jobs\lior_course_03\chapters.json",
|
|
57
|
+
"full_srt_path": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\3 - כלי הצאט - edited.srt",
|
|
58
|
+
"chapters_dir": r"G:\Projects\Kolbo.AI\Courses\Lior\Claude\3 - כלי הצאט",
|
|
59
|
+
"video_duration": 2789.4,
|
|
60
|
+
},
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for job in JOBS:
|
|
64
|
+
print(f"\n{'='*60}")
|
|
65
|
+
print(f"SRT export: {job['name']}")
|
|
66
|
+
print('='*60)
|
|
67
|
+
|
|
68
|
+
with open(job['transcript'], encoding='utf-8') as f:
|
|
69
|
+
transcript = json.load(f)
|
|
70
|
+
sentences = transcript['sentences']
|
|
71
|
+
|
|
72
|
+
with open(job['chapters_json'], encoding='utf-8') as f:
|
|
73
|
+
chapters = json.load(f)
|
|
74
|
+
|
|
75
|
+
# Compute end times
|
|
76
|
+
for i, ch in enumerate(chapters):
|
|
77
|
+
ch['end_time'] = chapters[i + 1]['start_time'] if i + 1 < len(chapters) else job['video_duration']
|
|
78
|
+
|
|
79
|
+
# ── Full video SRT ────────────────────────────────────────────────────
|
|
80
|
+
# Note: the burned video has 4s divider cards inserted before each chapter.
|
|
81
|
+
# We need to offset all timestamps to account for the accumulated divider time.
|
|
82
|
+
full_blocks = []
|
|
83
|
+
block_num = 1
|
|
84
|
+
accumulated_divider_time = 0.0
|
|
85
|
+
DIVIDER_DURATION = 4.0
|
|
86
|
+
|
|
87
|
+
for ch in chapters:
|
|
88
|
+
ch_start = ch['start_time']
|
|
89
|
+
ch_end = ch['end_time']
|
|
90
|
+
ch_sentences = get_sentences_in_range(sentences, ch_start, ch_end)
|
|
91
|
+
|
|
92
|
+
# Each chapter is preceded by a 4s divider
|
|
93
|
+
accumulated_divider_time += DIVIDER_DURATION
|
|
94
|
+
|
|
95
|
+
for s in ch_sentences:
|
|
96
|
+
start = s['start'] + accumulated_divider_time
|
|
97
|
+
end = s['end'] + accumulated_divider_time
|
|
98
|
+
full_blocks.append(
|
|
99
|
+
f"{block_num}\n{fmt_srt_time(start)} --> {fmt_srt_time(end)}\n{s['text']}\n"
|
|
100
|
+
)
|
|
101
|
+
block_num += 1
|
|
102
|
+
|
|
103
|
+
with open(job['full_srt_path'], 'w', encoding='utf-8') as f:
|
|
104
|
+
f.write('\n'.join(full_blocks))
|
|
105
|
+
print(f" Full SRT: {block_num - 1} blocks")
|
|
106
|
+
|
|
107
|
+
# ── Per-chapter SRTs ──────────────────────────────────────────────────
|
|
108
|
+
for ch in chapters:
|
|
109
|
+
n = ch['chapter_number']
|
|
110
|
+
title = ch['title']
|
|
111
|
+
safe_title = title.replace('/', '-').replace('\\', '-').replace(':', '-').replace('"', '').replace('?', '').replace('*', '').replace('<', '').replace('>', '').replace('|', '')
|
|
112
|
+
|
|
113
|
+
ch_start = ch['start_time']
|
|
114
|
+
ch_end = ch['end_time']
|
|
115
|
+
ch_sentences = get_sentences_in_range(sentences, ch_start, ch_end)
|
|
116
|
+
|
|
117
|
+
# Offset = chapter start time (zero the timestamps to chapter-local time)
|
|
118
|
+
# Add 4s for the divider card at the beginning of each chapter clip
|
|
119
|
+
srt_text = sentences_to_srt(ch_sentences, offset=ch_start - DIVIDER_DURATION)
|
|
120
|
+
|
|
121
|
+
srt_path = os.path.join(job['chapters_dir'], f"{n:02d} - {safe_title}.srt")
|
|
122
|
+
with open(srt_path, 'w', encoding='utf-8') as f:
|
|
123
|
+
f.write(srt_text)
|
|
124
|
+
|
|
125
|
+
print(f" Ch {n:02d}: {len(ch_sentences)} blocks")
|
|
126
|
+
|
|
127
|
+
print("\nAll SRTs exported!")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Generate SRT from transcript.json and analyze chapters via Claude."""
|
|
2
|
+
import json, os, sys
|
|
3
|
+
|
|
4
|
+
# ── SRT generation ────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
with open('ytp_jobs/sapir_test/transcript.json', encoding='utf-8') as f:
|
|
7
|
+
data = json.load(f)
|
|
8
|
+
|
|
9
|
+
words = data['words']
|
|
10
|
+
|
|
11
|
+
def fmt_time(s):
|
|
12
|
+
h = int(s // 3600)
|
|
13
|
+
m = int((s % 3600) // 60)
|
|
14
|
+
sec = s % 60
|
|
15
|
+
return f'{h:02d}:{m:02d}:{sec:06.3f}'.replace('.', ',')
|
|
16
|
+
|
|
17
|
+
lines, current, cur_start = [], [], None
|
|
18
|
+
for i, w in enumerate(words):
|
|
19
|
+
if not current:
|
|
20
|
+
cur_start = w['start']
|
|
21
|
+
current.append(w['text'])
|
|
22
|
+
gap = words[i + 1]['start'] - w['end'] if i + 1 < len(words) else 999
|
|
23
|
+
if len(current) >= 8 or gap > 1.5:
|
|
24
|
+
lines.append({'start': cur_start, 'end': w['end'], 'text': ' '.join(current)})
|
|
25
|
+
current, cur_start = [], None
|
|
26
|
+
|
|
27
|
+
if current:
|
|
28
|
+
lines.append({'start': cur_start, 'end': words[-1]['end'], 'text': ' '.join(current)})
|
|
29
|
+
|
|
30
|
+
srt_blocks = []
|
|
31
|
+
for i, l in enumerate(lines, 1):
|
|
32
|
+
srt_blocks.append(f"{i}\n{fmt_time(l['start'])} --> {fmt_time(l['end'])}\n{l['text']}\n")
|
|
33
|
+
|
|
34
|
+
srt_text = '\n'.join(srt_blocks)
|
|
35
|
+
|
|
36
|
+
with open('ytp_jobs/sapir_test/sapir.srt', 'w', encoding='utf-8') as f:
|
|
37
|
+
f.write(srt_text)
|
|
38
|
+
|
|
39
|
+
print(f"[srt] Written {len(lines)} subtitle blocks -> ytp_jobs/sapir_test/sapir.srt")
|
|
40
|
+
print(f"[srt] First 3 blocks:")
|
|
41
|
+
for l in lines[:3]:
|
|
42
|
+
print(f" [{fmt_time(l['start'])} --> {fmt_time(l['end'])}] {l['text'][:70]}")
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typography-video
|
|
3
|
+
description: >
|
|
4
|
+
Typography rules for video production: font selection and pairing, minimum readable sizes,
|
|
5
|
+
safe zones per platform, text animation timing, contrast requirements, subtitle styling.
|
|
6
|
+
Use when adding text overlays, titles, lower thirds, or captions to video.
|
|
7
|
+
Keywords: typography, font, text, title, lower third, caption, subtitle, safe zone, text size,
|
|
8
|
+
font pairing, readable, overlay, motion graphics
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Typography for Video Production
|
|
12
|
+
|
|
13
|
+
## Quick Reference
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
TITLE SIZE: 60-90px at 1080p | 120-180px at 4K
|
|
17
|
+
BODY SIZE: 40-60px at 1080p | 80-120px at 4K
|
|
18
|
+
SUBTITLE SIZE: 42px+ at 1080p | 3-5% of video height
|
|
19
|
+
MAX CHARS/LINE: 32-42 (subtitles) | 30 (overlays)
|
|
20
|
+
MAX LINES: 2 (subtitles) | 3 (overlays)
|
|
21
|
+
READING SPEED: 21 chars/sec | 160-200 WPM
|
|
22
|
+
TITLE SAFE: 80% of frame (192px margin at 1080p)
|
|
23
|
+
FONT FAMILIES: 1-2 per video maximum
|
|
24
|
+
CONTRAST: 4.5:1 minimum, 7:1 optimal
|
|
25
|
+
FADE DURATION: 0.3s opacity | 0.5-1.0s slide/scale
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Font Selection
|
|
29
|
+
|
|
30
|
+
### Recommended Video Fonts
|
|
31
|
+
|
|
32
|
+
| Category | Fonts | Use For |
|
|
33
|
+
|----------|-------|---------|
|
|
34
|
+
| **Body / Captions** | Inter, Open Sans, Roboto, Source Sans Pro, Lato, DM Sans | All body text, subtitles |
|
|
35
|
+
| **Headlines** | Montserrat Bold, Bebas Neue, Oswald Bold, Poppins Bold | Titles, section headers, key stats |
|
|
36
|
+
| **Editorial** | Playfair Display, Roboto Slab | Luxury, cinematic, documentary |
|
|
37
|
+
| **System Safe** | Helvetica Neue, Arial, Avenir Next | When custom fonts unavailable |
|
|
38
|
+
|
|
39
|
+
### Font Pairing Rules
|
|
40
|
+
|
|
41
|
+
- Limit to **1-2 font families** per video
|
|
42
|
+
- Pair a **display/bold heading** font with a **neutral body** font
|
|
43
|
+
- Size difference between title and body: at least **50% larger**
|
|
44
|
+
- **Sans-serif** for motion graphics and captions (holds up in motion)
|
|
45
|
+
- **Serif** only for cinematic title cards and editorial content
|
|
46
|
+
- **Script/decorative** fonts: hero titles only, never body, never in motion
|
|
47
|
+
|
|
48
|
+
### Proven Pairings
|
|
49
|
+
|
|
50
|
+
| Heading | Body | Style |
|
|
51
|
+
|---------|------|-------|
|
|
52
|
+
| Bebas Neue | Open Sans | High-impact, social ads |
|
|
53
|
+
| Montserrat Bold | Lato | Clean modern |
|
|
54
|
+
| Oswald Bold | Raleway | Strong contrast |
|
|
55
|
+
| Playfair Display | Inter | Editorial |
|
|
56
|
+
| Poppins Bold | Poppins Light | Single-family hierarchy |
|
|
57
|
+
|
|
58
|
+
## Text Sizing
|
|
59
|
+
|
|
60
|
+
| Element | 1080p (px) | 4K (px) |
|
|
61
|
+
|---------|-----------|---------|
|
|
62
|
+
| Title / Hero text | 60-90 | 120-180 |
|
|
63
|
+
| Body text | 40-60 | 80-120 |
|
|
64
|
+
| Subtitles | 42+ | 84+ |
|
|
65
|
+
| Lower third name | 48-60 | 96-120 |
|
|
66
|
+
| Lower third role | 36-44 | 72-88 |
|
|
67
|
+
|
|
68
|
+
## Safe Zones
|
|
69
|
+
|
|
70
|
+
### Broadcast Standard (1920x1080)
|
|
71
|
+
|
|
72
|
+
| Zone | Coverage | Margin |
|
|
73
|
+
|------|----------|--------|
|
|
74
|
+
| **Title Safe** | 80% of frame | 192px H, 108px V |
|
|
75
|
+
| **Action Safe** | 90% of frame | 96px H, 54px V |
|
|
76
|
+
|
|
77
|
+
At 1920x1080: Title Safe = inner 1536x864px
|
|
78
|
+
|
|
79
|
+
### Vertical (1080x1920) Platform-Specific
|
|
80
|
+
|
|
81
|
+
See the `short-form-video` skill for per-platform safe zones. Universal safe: 900x1400px centered.
|
|
82
|
+
|
|
83
|
+
## Reading Speed
|
|
84
|
+
|
|
85
|
+
- Average viewer reads **21 characters per second** / **160-200 WPM**
|
|
86
|
+
- Minimum display time: **0.5 seconds per text element**
|
|
87
|
+
- Maximum display time: **5 seconds** before it feels stale
|
|
88
|
+
- Formula: `display_seconds = character_count / 21 + 0.5`
|
|
89
|
+
|
|
90
|
+
## Contrast Requirements (WCAG)
|
|
91
|
+
|
|
92
|
+
| Element | Minimum Ratio |
|
|
93
|
+
|---------|--------------|
|
|
94
|
+
| Body text on background | 4.5:1 (AA) |
|
|
95
|
+
| Large text (>18pt) | 3:1 (AA) |
|
|
96
|
+
| Enhanced readability | 7:1 (AAA) |
|
|
97
|
+
|
|
98
|
+
**Practical rule:** After color grading, any text overlays must still meet 4.5:1 contrast against the graded background.
|
|
99
|
+
|
|
100
|
+
## Text Animation Timing
|
|
101
|
+
|
|
102
|
+
| Animation | Duration | Easing |
|
|
103
|
+
|-----------|----------|--------|
|
|
104
|
+
| Fade in/out | 0.3s | ease-in-out |
|
|
105
|
+
| Slide in | 0.5-0.8s | ease-out (cubic) |
|
|
106
|
+
| Scale up | 0.4-0.6s | ease-out with slight overshoot |
|
|
107
|
+
| Typewriter | 30-50ms per character | linear |
|
|
108
|
+
| Word-by-word | 80-120ms per word | step or ease |
|
|
109
|
+
|
|
110
|
+
### Animation Rules
|
|
111
|
+
- Text should be **fully readable for at least 1.5 seconds** after animation completes
|
|
112
|
+
- Entry animation + readable duration + exit animation = total display time
|
|
113
|
+
- **Never animate body text character-by-character** — only titles and keywords
|
|
114
|
+
- **Ease out, not ease in** — text should arrive quickly and settle, not start slow
|
|
115
|
+
|
|
116
|
+
## RTL (Hebrew/Arabic) Text — Proven Patterns
|
|
117
|
+
|
|
118
|
+
These patterns are battle-tested in Kolbo's video production pipeline.
|
|
119
|
+
|
|
120
|
+
**Reference implementation:** `./reference/KineticTitleScene.tsx` (346 lines, full RTL) — read this for a complete working example of every RTL flip listed below.
|
|
121
|
+
|
|
122
|
+
### Font Selection
|
|
123
|
+
- **Hebrew**: Heebo (Google Fonts, free) — use Bold weight for captions
|
|
124
|
+
- **Arabic**: Cairo (Google Fonts, free) — use Bold weight for captions
|
|
125
|
+
- **Never** use Poppins or English-first fonts for Hebrew/Arabic body text
|
|
126
|
+
|
|
127
|
+
### Remotion RTL Rules (proven)
|
|
128
|
+
|
|
129
|
+
Every component receiving text must handle RTL:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const isHebrew = language === "he" || language === "iw";
|
|
133
|
+
|
|
134
|
+
<div style={{
|
|
135
|
+
direction: isHebrew ? "rtl" : "ltr",
|
|
136
|
+
fontFamily: isHebrew ? "'Heebo', sans-serif" : "'Poppins', sans-serif",
|
|
137
|
+
textTransform: isHebrew ? "none" : "uppercase", // No uppercase in Hebrew
|
|
138
|
+
letterSpacing: isHebrew ? 0 : -2, // No negative kerning for Hebrew
|
|
139
|
+
}}>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**What you must flip for RTL:**
|
|
143
|
+
| Property | LTR (English) | RTL (Hebrew) |
|
|
144
|
+
|----------|--------------|--------------|
|
|
145
|
+
| `direction` | `ltr` | `rtl` |
|
|
146
|
+
| `paddingLeft` / `paddingRight` | normal | swapped |
|
|
147
|
+
| `transformOrigin` | `"top left"` | `"top right"` |
|
|
148
|
+
| Gradient direction | `90deg` | `270deg` |
|
|
149
|
+
| Position "left" | left side | **right side** (flip the logic) |
|
|
150
|
+
| `textTransform` | `uppercase` | `none` |
|
|
151
|
+
| `letterSpacing` | `-2` or custom | `0` (always) |
|
|
152
|
+
| Slide-in direction | from left | from right |
|
|
153
|
+
|
|
154
|
+
### FFmpeg RTL Burn-in
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Hebrew subtitles with Heebo font
|
|
158
|
+
ffmpeg -i input.mp4 -vf "subtitles=subs.srt:force_style='FontName=Heebo,FontSize=22,Bold=1,Encoding=177,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,Outline=2'" output.mp4
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
See `subtitle-production` skill for full RTL karaoke and per-word positioning patterns.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Kolbo MCP Integration
|
|
166
|
+
|
|
167
|
+
Typography rules apply to all Kolbo visual generation:
|
|
168
|
+
|
|
169
|
+
| Task | Kolbo MCP Tool | Typography Rule |
|
|
170
|
+
|------|---------------|----------------|
|
|
171
|
+
| Title cards | `generate_image` | Add text as Remotion overlay, NOT in AI image prompt |
|
|
172
|
+
| Caption generation | `transcribe_audio` | Word-level SRT → burn-in with FFmpeg |
|
|
173
|
+
| Lower thirds | Remotion component | Use proven font pairings above |
|
|
174
|
+
| Video with text overlay | `generate_video` + FFmpeg post | Render video first, add text in post |
|
|
175
|
+
|
|
176
|
+
**Key rule:** Never ask image/video generation models to render text — they can't do it reliably. Always add text as overlays in post-production (Remotion, FFmpeg, or video editor).
|
|
177
|
+
|
|
178
|
+
**Free fonts for video (Google Fonts, all free):**
|
|
179
|
+
- Body: Inter, Open Sans, Roboto, Lato, DM Sans
|
|
180
|
+
- Headlines: Montserrat, Bebas Neue, Oswald, Poppins
|
|
181
|
+
- Hebrew: Heebo, Assistant, Rubik
|
|
182
|
+
- Arabic: Cairo, Noto Sans Arabic
|