manus-skills/.kortix/opencode/skills/media/clipify/scripts/build_ass.py
zhukang 1c7abd81d1 All 310 Manus skill-hub community skills as a Kortix marketplace registry
- 310 registry:skill items, 1528 files, 7 categories (hub tags)
- registry.json generated by @kortix/registry buildRegistry
- nested SKILL.md in sn-da-* renamed SUBSKILL.md (no phantom skills)
- provenance + licenses: SOURCES.md
2026-09-15 13:37:20 +08:00

74 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Generate opus-clips style ASS subtitles from whisper word timestamps.
Usage: build_ass.py WHISPER.json OUT.ass [STYLE]
STYLE: 'opus' (default), 'karaoke', 'minimal'
"""
import json, sys
WHISPER_JSON = sys.argv[1]
OUT_ASS = sys.argv[2]
STYLE = sys.argv[3] if len(sys.argv) > 3 else "opus"
PLAY_W, PLAY_H = 1080, 1920
PRESETS = {
"opus": dict(font="Arial Black", size=100, chunk=3, highlight="&H0000FFFF&", outline=8, shadow=3),
"karaoke": dict(font="Arial Black", size=110, chunk=4, highlight="&H0000FF00&", outline=6, shadow=2),
"minimal": dict(font="Helvetica", size=70, chunk=6, highlight=None, outline=4, shadow=1),
}
P = PRESETS.get(STYLE, PRESETS["opus"])
WHITE = "&H00FFFFFF&"
OUTLINE = "&H00000000&"
def fmt_time(t):
h = int(t // 3600); m = int((t % 3600) // 60); s = t - h*3600 - m*60
return f"{h}:{m:02d}:{s:05.2f}"
data = json.load(open(WHISPER_JSON))
words = []
for seg in data["segments"]:
for w in seg.get("words", []):
words.append({"start": w["start"], "end": w["end"], "text": w["word"].strip()})
chunks = [words[i:i+P["chunk"]] for i in range(0, len(words), P["chunk"])]
header = f"""[Script Info]
ScriptType: v4.00+
PlayResX: {PLAY_W}
PlayResY: {PLAY_H}
WrapStyle: 2
ScaledBorderAndShadow: yes
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,{P["font"]},{P["size"]},{WHITE},&H000000FF,{OUTLINE},&H00000000,1,0,0,0,100,100,0,0,1,{P["outline"]},{P["shadow"]},2,60,60,280,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
events = []
for chunk in chunks:
chunk_end = chunk[-1]["end"]
for i, w in enumerate(chunk):
seg_start = w["start"]
seg_end = chunk[i+1]["start"] if i+1 < len(chunk) else chunk_end
if seg_end <= seg_start: seg_end = seg_start + 0.05
if P["highlight"]:
parts = []
for j, ww in enumerate(chunk):
if j == i:
parts.append(f"{{\\c{P['highlight']}}}{ww['text']}{{\\c{WHITE}}}")
else:
parts.append(ww["text"])
line = " ".join(parts)
else:
line = " ".join(ww["text"] for ww in chunk)
events.append(f"Dialogue: 0,{fmt_time(seg_start)},{fmt_time(seg_end)},Default,,0,0,0,,{line}")
with open(OUT_ASS, "w") as f:
f.write(header + "\n".join(events) + "\n")
print(f"wrote {OUT_ASS}: {len(chunks)} chunks, {len(events)} events", file=sys.stderr)