| 1 | import json, numpy as np, soundfile as sf |
| 2 | from kokoro import KPipeline |
| 3 | |
| 4 | SR = 24000 |
| 5 | VOICE = "af_heart" |
| 6 | SPEED = 0.95 |
| 7 | pipe = KPipeline(lang_code="a") |
| 8 | |
| 9 | segs = [ |
| 10 | ("s1", "This is Stepping.", 0.55), |
| 11 | ("s1", "The home for Divine Nine step show culture.", 0.70), |
| 12 | ("s1", "Step shows, strolls, and the people behind them, all in one place.", 0.90), |
| 13 | |
| 14 | ("s2", "Start by choosing your city.", 0.50), |
| 15 | ("s2", "Stepping is launching in Atlanta and Washington, D.C.", 0.85), |
| 16 | |
| 17 | ("s3", "Inside each city is The Line-Up.", 0.50), |
| 18 | ("s3", "Every step show, stroll, and competition coming up.", 0.65), |
| 19 | ("s3", "Tap any one to see the date, the time, and the details.", 0.85), |
| 20 | |
| 21 | ("s4", "On a show's page, you'll find everything you need to know.", 0.55), |
| 22 | ("s4", "And you can join the conversation in the comments.", 0.85), |
| 23 | |
| 24 | ("s5", "Then there's The Yard, the community.", 0.50), |
| 25 | ("s5", "Chapters, step teams, DJs, promoters, and classes.", 0.65), |
| 26 | ("s5", "Everyone who makes the culture move.", 0.85), |
| 27 | |
| 28 | ("s6", "Each one has its own page,", 0.35), |
| 29 | ("s6", "so you always know who's who.", 0.85), |
| 30 | |
| 31 | ("s7", "Traveling in for a show?", 0.45), |
| 32 | ("s7", "The Route helps you plan the trip.", 0.50), |
| 33 | ("s7", "Hotels, restaurants, places to go, and the nearest hospital, for every city.", 0.90), |
| 34 | |
| 35 | ("s8", "Stepping works right in your phone's browser.", 0.55), |
| 36 | ("s8", "No app store. No accounts to figure out.", 0.55), |
| 37 | ("s8", "Just open it, and start.", 0.90), |
| 38 | |
| 39 | ("s9", "And there's more on the way.", 0.50), |
| 40 | ("s9", "Live streaming of shows, more cities across the country, and video from every set.", 0.95), |
| 41 | |
| 42 | ("s10", "Stepping.", 0.45), |
| 43 | ("s10", "Built for the culture.", 0.0), |
| 44 | ] |
| 45 | |
| 46 | def synth(text): |
| 47 | parts = [a for _, _, a in pipe(text, voice=VOICE, speed=SPEED)] |
| 48 | return np.concatenate(parts).astype("float32") |
| 49 | |
| 50 | out = [] |
| 51 | t = 0.0 |
| 52 | scene_start = {} |
| 53 | seg_log = [] |
| 54 | for scene, text, gap in segs: |
| 55 | if scene not in scene_start: |
| 56 | scene_start[scene] = round(t, 3) |
| 57 | w = synth(text) |
| 58 | out.append(w) |
| 59 | seg_log.append({"scene": scene, "start": round(t, 3), "dur": round(len(w) / SR, 3), "text": text}) |
| 60 | t += len(w) / SR |
| 61 | if gap > 0: |
| 62 | out.append(np.zeros(int(gap * SR), dtype="float32")) |
| 63 | t += gap |
| 64 | |
| 65 | full = np.concatenate(out) |
| 66 | sf.write("stepping_vo.wav", full, SR) |
| 67 | meta = {"voice": VOICE, "sr": SR, "speed": SPEED, "total": round(len(full) / SR, 3), |
| 68 | "scene_start": scene_start, "segments": seg_log} |
| 69 | json.dump(meta, open("scene_times.json", "w"), indent=2) |
| 70 | print("TOTAL", meta["total"]) |
| 71 | for s, st in scene_start.items(): |
| 72 | print(f" {s}: {st}") |