<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kelvaro]]></title><description><![CDATA[Kelvaro]]></description><link>https://kelvaro.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Thu, 12 Nov 2026 07:20:49 GMT</lastBuildDate><atom:link href="https://kelvaro.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Luau patterns I keep re-explaining to my AI assistant (and how I stopped)]]></title><description><![CDATA[If you use Claude or ChatGPT to write Luau in Roblox Studio, you have probably corrected the same mistakes more than once. Invented APIs are the obvious…]]></description><link>https://kelvaro.hashnode.dev/luau-patterns-i-keep-re-explaining-to-my-ai-assistant-and-how-i-stopped</link><guid isPermaLink="true">https://kelvaro.hashnode.dev/luau-patterns-i-keep-re-explaining-to-my-ai-assistant-and-how-i-stopped</guid><category><![CDATA[roblox]]></category><category><![CDATA[Lua]]></category><category><![CDATA[AI]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[Kelvaro]]></dc:creator><pubDate>Wed, 11 Nov 2026 23:51:56 GMT</pubDate><content:encoded><![CDATA[<p>If you use Claude or ChatGPT to write Luau in Roblox Studio, you have probably corrected the same mistakes more than once. Invented APIs are the obvious one. The quieter failures are architectural: a ModuleScript treated as shared memory between client and server, a RemoteEvent that trusts the client, <code>wait()</code> and <code>spawn()</code> copied from old Lua tutorials, connections that leak when a character respawns, a second DataStore wrapper because yesterday's chat is gone.</p>
<p>I got tired of teaching these in every new session. The fix was not a longer system prompt. It was a short patterns file that lives <em>outside</em> the chat, plus a rule that the assistant has to follow it or say it is uncertain.</p>
<h2>1. The client is a request, not a source of truth</h2>
<p>A RemoteEvent fired from the client can claim anything: "I bought this item," "I dealt 999 damage," "give me the reward." The server has to decide. Check that the player can actually afford it, that the item exists, that the cooldown has elapsed, that the character is in a state where the action is legal. Then apply the result on the server and replicate it back.</p>
<p>This is standard Creator Hub security guidance, and it is still the first pattern models drop when you ask for "a simple shop." I keep a one-line rule in the patterns file: <em>client input is a request; the server validates and writes state.</em> If a generated shop script awards currency on the client, it is not done.</p>
<h2>2. ModuleScripts are not a shared database</h2>
<p>A ModuleScript required on the server and required on the client is two separate copies. Mutating a table in one does not update the other. Shared state belongs in the DataModel — attributes, value objects, instances — or it is sent through remotes.</p>
<p>I have watched models "fix" a desync by stuffing more fields into a module that only one side can see. The bug looks like a timing issue. It is a memory-model issue. The patterns file says so in one paragraph, because I was tired of diagnosing it as if it were new.</p>
<h2>3. Prefer the task library</h2>
<p>Old Lua tutorials still teach <code>wait()</code>, <code>spawn()</code>, and <code>delay()</code>. In Luau, new code should use <code>task.wait</code>, <code>task.spawn</code>, and <code>task.delay</code> (and <code>task.defer</code> when the work should run after the current resumption). This is not a style preference. The deprecated globals are less predictable, especially under load, and they keep reappearing because training data is full of them.</p>
<p>My rule is mechanical: new code uses the <code>task</code> library; if you see <code>wait()</code>, replace it. I do not want a debate about it in the middle of a shop script.</p>
<h2>4. Clean up connections</h2>
<p>Characters respawn. Tools get unequipped. UI is destroyed and created again. A connection on <code>Humanoid.Died</code> or a <code>RenderStepped</code> loop that is never disconnected will pile up. The script may "still work" for five minutes and then stutter.</p>
<p>I ask the assistant to disconnect, or to use a Maid/Janitor-style helper, and to treat "this still runs after <code>Destroy</code>" as a bug. "I'll leave the connection, it is fine" is not an acceptable close to a task.</p>
<h2>5. Do not invent APIs — and do not silently upgrade them</h2>
<p>If a service method, property, or enum is not in a project note with a source and a date, the assistant must look it up or mark it uncertain. It may not invent <code>MarketplaceService:PromptSomething()</code>, and it may not "modernize" a working call because a 2019 blog post used a different name.</p>
<p>This rule alone cuts the paste-error-apologize loop. A confident paragraph about an API that does not exist looks identical to a verified one. The pattern is: cite, or say "uncertain" and how to check in Studio.</p>
<h2>6. One module, one job — do not open a second shop</h2>
<p>Before writing a new currency, inventory, or save path, the assistant reads the current project status and the code index. If a DataStore wrapper already exists, extend it. Duplicate systems are how a weekend project becomes two games in one folder.</p>
<p>This is the architectural twin of the invented API: not a wrong method name, but a second source of truth. Both come from a chat that has no memory of yesterday.</p>
<h2>How I stopped re-explaining all of this</h2>
<p>I keep a <code>luau-patterns.md</code> (the name does not matter) in a project vault, next to a short <code>status.md</code> for the game I am actually building. The patterns file holds the six rules above and a few snippets I have already run in Studio. The master prompt is short: read the patterns and the current status first; do not invent APIs or numbers; do not call a task done without a playtest; when uncertain, say so and how to check. After a session I add one line — exact error, cause, fix — even if the session was aborted.</p>
<p>I still have to playtest, including on a phone-sized viewport. A patterns file will not catch a jump that is unusable on touch. It just stops me from teaching the same six rules to a blank chat every morning.</p>
<h2>If you want this structure ready-made</h2>
<p>I packed this working method into a German-language kit called <strong>Roblox Second Brain</strong>: a copy-paste master prompt, a 292-page knowledge base (PDF and Markdown, dated 31 October 2026), setup notes, a Luau course, genre blueprints, and an Obsidian-friendly vault with the before/after routine above. It is 19 € once. There is no success claim, and it is not an official Roblox or Anthropic product.</p>
<p>A free sample — 12 patterns shared by many hit games, a second-by-second first-minute plan, and a 30-day zero-budget launch template — is here:</p>
<p><a href="https://whop.com/kelvaro-creator-kits/leseprobe-roblox-second-brain">https://whop.com/kelvaro-creator-kits/leseprobe-roblox-second-brain</a></p>
<p>The full kit is here:</p>
<p><a href="https://whop.com/kelvaro-creator-kits/roblox-second-brain">https://whop.com/kelvaro-creator-kits/roblox-second-brain</a></p>
<p>Launch offer: code <strong>LAUNCH40</strong> = 40 % off, for the first 10 buyers only, until 16 November 2026.</p>
<p><em>Disclosure: the linked kit is my own paid product. The sample is free. I am not affiliated with Roblox Corporation or Anthropic.</em></p>
]]></content:encoded></item><item><title><![CDATA[Auto-captioning short clips locally with Whisper and FFmpeg: a practical guide]]></title><description><![CDATA[Short-form captions are not a decoration. On mute, they are the audio. A YouTube Short or TikTok clip that highlights the current word, stays inside the…]]></description><link>https://kelvaro.hashnode.dev/auto-captioning-short-clips-locally-with-whisper-and-ffmpeg-a-practical-guide</link><guid isPermaLink="true">https://kelvaro.hashnode.dev/auto-captioning-short-clips-locally-with-whisper-and-ffmpeg-a-practical-guide</guid><category><![CDATA[Python]]></category><category><![CDATA[FFmpeg]]></category><category><![CDATA[whisper]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[Kelvaro]]></dc:creator><pubDate>Wed, 11 Nov 2026 22:00:54 GMT</pubDate><content:encoded><![CDATA[<p>Short-form captions are not a decoration. On mute, they <em>are</em> the audio. A YouTube Short or TikTok clip that highlights the current word, stays inside the safe area, and does not cover the face will usually outperform a burned-in paragraph that arrives late and sits too high. You do not need a cloud editor for that. A local pipeline with <code>faster-whisper</code> and FFmpeg is enough to caption a talking-head clip on your own machine, including German speech.</p>
<p>This guide walks through a working path: extract audio, get word timestamps, turn them into an ASS file with a live-word highlight, and burn the result into a 9:16 clip. It is the caption stage only. Cutting, silence removal, and face framing are separate problems; I will point at them, not pretend they are solved here.</p>
<h2>What “good enough” captions look like on a Short</h2>
<p>A useful default for a 9:16 talking-head:</p>
<ul>
<li><strong>Two lines at most</strong>, roughly three to five words per line, centered in the lower third.</li>
<li>The <strong>current word</strong> is a different colour or weight. The rest of the line stays readable.</li>
<li>Leave a margin above the platform UI (the caption block often sits around 70–80% down the frame, not on the very bottom edge).</li>
<li>Do not cover the mouth. If you already reframed the face into the upper two thirds, the lower third is free.</li>
<li>Timing follows the word, not the sentence. A word that appears 400 ms late feels sluggish; one that appears early feels like a leak.</li>
</ul>
<p>SRT can do the job if you only need sentences. Word-level highlight is much easier in <strong>ASS</strong>.</p>
<h2>Step 1: extract a clean mono track</h2>
<p>Whisper does not need a 48 kHz stereo mix. A 16 kHz mono WAV is smaller, faster, and honest enough for speech.</p>
<pre><code class="language-bash">ffmpeg -y -i input.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le audio.wav
</code></pre>
<p>If the source is already a clip you will publish, use that same file as <code>input.mp4</code>. If you caption a long podcast first and cut later, word times will still be correct for the long file; you will have to shift them when you cut. It is usually simpler to caption <em>after</em> you have the 20–60 second clip.</p>
<h2>Step 2: transcribe with word timestamps</h2>
<p><code>faster-whisper</code> is the practical local choice: same word timings as Whisper, less RAM, good CPU performance with <code>int8</code>. A first script:</p>
<pre><code class="language-python">from faster_whisper import WhisperModel

model = WhisperModel("small", device="auto", compute_type="int8")
segments, info = model.transcribe(
    "audio.wav",
    language="de",          # or "en"; omit to let it detect
    word_timestamps=True,
    vad_filter=False,
    beam_size=5,
)

words = []
for seg in segments:
    for w in seg.words:
        token = (w.word or "").strip()
        if not token:
            continue
        words.append({"start": w.start, "end": w.end, "text": token})

print(info.language, len(words), "words")
</code></pre>
<p>A few honest limits:</p>
<ul>
<li><strong><code>small</code></strong> is a sensible default on a laptop. <code>medium</code> is cleaner on messy German audio and proper names; it is also slower. <code>tiny</code> will caption a demo, but it will miss names.</li>
<li>Set <code>language</code> if you know it. Auto-detect on a short clip can flip to the wrong language after a loanword or a title in English.</li>
<li>For files longer than a few minutes, transcribe in chunks of about 60 seconds and cut at a quiet spot, then stitch the word lists. A single pass on a long episode can drift.</li>
<li>Do not treat the text as final. Whisper will invent a plausible name. Keep a small replacements file (<code>"Müller"</code>, <code>"Kelvaro"</code>, product names) and apply it before you render.</li>
</ul>
<p>Times are in seconds, float, relative to the start of <code>audio.wav</code>.</p>
<h2>Step 3: wrap words into lines, then into ASS events</h2>
<p>Do not emit one event per word if you can avoid it. Build a line of three to five words, then, for each word in that line, write an ASS event that covers only that word’s time span and highlights just that word. The line stays on screen; the highlight moves.</p>
<p>A compact way to wrap: accumulate words until the next one would exceed about 24 characters or a five-word cap, and break before a new clause if you can (<code>und</code>, <code>aber</code>, <code>weil</code>, <code>and</code>, <code>but</code>). Avoid ending a line on a tiny function word if the next word is the payload.</p>
<p>ASS needs timestamps as <code>H:MM:SS.cc</code> (centiseconds). A highlight can be a colour override:</p>
<pre><code class="language-ass">[Script Info]
ScriptType: v4.00+
PlayResX: 1080
PlayResY: 1920
WrapStyle: 0

[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: Word,Poppins ExtraBold,72,&amp;H00FFFFFF,&amp;H00000000,&amp;H00101010,&amp;H80000000,-1,0,0,0,100,100,0,0,1,6,0,2,80,80,280,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.20,0:00:01.55,Word,,0,0,0,,Clips {\c&amp;H00E6C600&amp;}automatisch{\c&amp;H00FFFFFF&amp;} schneiden
</code></pre>
<p>Notes that save pain later:</p>
<ul>
<li>In ASS, colours are <code>&amp;HBBGGRR&amp;</code>, not HTML. The example highlight is a yellow.</li>
<li><code>Alignment=2</code> is bottom-centre. <code>MarginV=280</code> on a 1920-tall frame lifts the block above typical UI chrome; tweak it on a real screenshot of YouTube Shorts or TikTok, not by taste alone.</li>
<li>Use a font you actually ship (Poppins, Inter, or a bundled OFL font). A missing font silently falls back and changes the layout.</li>
<li>Escape ASS special characters in the spoken text: <code>{</code>, <code>}</code>, and backslashes.</li>
</ul>
<p>Generate the <code>Dialogue</code> lines in Python from your word list. Keep the full line visible in every event of that group; only the active word gets the <code>\c</code> override.</p>
<h2>Step 4: burn the captions in with FFmpeg</h2>
<p>Once you have <code>captions.ass</code> and a 9:16 <code>clip.mp4</code>:</p>
<pre><code class="language-bash">ffmpeg -y -i clip.mp4 -vf "ass=captions.ass" -c:a copy captioned.mp4
</code></pre>
<p>If the clip is not 9:16 yet, do the scale/crop first, then caption, so the <code>PlayResX/PlayResY</code> of the ASS file match the frame you actually burn into. Mixing a 1920×1080 source with a 1080×1920 ASS style is the usual reason text lands in the wrong place.</p>
<p>The <code>subtitles=</code> filter can also read ASS, but it goes through a slightly different font-config path. If a font you installed does not appear, <code>ass=</code> plus an explicit fonts directory (FFmpeg’s <code>fontsdir</code> option on the filter) is the more predictable route.</p>
<h2>A short quality checklist</h2>
<p>Watch the file once, with sound off, then once with sound on.</p>
<ul>
<li>Names, numbers, and product words: fix them in the word list, do not “paint over” in a timeline.</li>
<li>Words that start mid-breath: shift the start forward a little rather than letting a caption flash on a pause.</li>
<li>Lines that sit on the face or on a hook title: drop <code>MarginV</code> or reserve the top of the frame for the title (about the first 2.5 seconds).</li>
<li>German compounds and capitalization: Whisper sometimes splits or lowercases them. A replacements file is faster than a second model.</li>
</ul>
<p>And the non-technical constraint: only caption recordings you or your client have the rights to. Running the model locally does not change copyright.</p>
<h2>Where this stops</h2>
<p>This path captions a clip you already have. It does not pick the six best moments in a 20-minute talk, drop silences, or keep a face in the middle of a 9:16 crop. Those are earlier stages, and they are worth automating only after the captions look right — because a wrong word, highlighted, is more visible than a slightly late cut.</p>
<p>If you want that full local pipeline instead of wiring only the caption stage, I packaged it as a pay-once kit (the Auto-Clipping-System): Python source, a German step-by-step guide, word-level captions (including German), silence removal, face framing, hook titles, and optional zooms or B-roll. It is 29 € once, no subscription. Windows is the tested baseline. Preview every file before you post. The kit is here: <a href="https://whop.com/kelvaro-creator-kits/auto-clipping-system">https://whop.com/kelvaro-creator-kits/auto-clipping-system</a></p>
<p><em>Disclosure: that kit is my own paid product. No guarantee of views or revenue.</em></p>
]]></content:encoded></item><item><title><![CDATA[Why your AI assistant hallucinates Roblox APIs — and how a project knowledge vault fixes it]]></title><description><![CDATA[If you have ever asked Claude or ChatGPT to write Luau for Roblox Studio, you have probably watched it invent an API. A service method that does not exist…]]></description><link>https://kelvaro.hashnode.dev/why-your-ai-assistant-hallucinates-roblox-apis-and-how-a-project-knowledge-vault-fixes-it</link><guid isPermaLink="true">https://kelvaro.hashnode.dev/why-your-ai-assistant-hallucinates-roblox-apis-and-how-a-project-knowledge-vault-fixes-it</guid><category><![CDATA[roblox]]></category><category><![CDATA[Lua]]></category><category><![CDATA[AI]]></category><category><![CDATA[GameDev]]></category><dc:creator><![CDATA[Kelvaro]]></dc:creator><pubDate>Wed, 11 Nov 2026 20:47:20 GMT</pubDate><content:encoded><![CDATA[<p>If you have ever asked Claude or ChatGPT to write Luau for Roblox Studio, you have probably watched it invent an API. A service method that does not exist. A property with the wrong name. A “standard” pattern copied from five-year-old Lua tutorials that Studio will reject the moment you press Play.</p>
<p>This is not mainly because the model is “bad at coding.” It is because a chat window is a terrible place to keep a Roblox project.</p>
<h2>Why the model guesses</h2>
<p>A general assistant has three gaps at once.</p>
<p>First, Roblox is not generic Lua. The language is Luau, the APIs live in Studio services, and the official documentation changes. Training data mixes old forum posts, deprecated patterns, and generic Lua. When the model is unsure, it completes the sentence anyway.</p>
<p>Second, each new chat starts almost empty. It does not remember that you already decided to store data in one <code>DataService</code>, that a RemoteEvent failed last night with a specific message, or that your game must work on touch, mouse, and gamepad. So it cheerfully opens a second shop, a second currency, or a second save path.</p>
<p>Third, nothing in a default chat forces the model to mark uncertainty. A confident paragraph about <code>MarketplaceService:PromptSomething()</code> looks the same as a verified one.</p>
<p>The result is familiar: you paste the script, Studio errors, you paste the error back, the model apologizes and invents a slightly different API. A typical twin of that failure is architectural: the next session forgets that a DataStore wrapper already exists and generates a second one “just to be safe.” Both bugs come from missing memory, not from a lack of adjectives in the prompt.</p>
<h2>What a project knowledge vault actually is</h2>
<p>A vault, in this sense, is not a pile of bookmarks. It is a source of truth <em>outside</em> the chat, in three layers:</p>
<ol>
<li><strong>Rules.</strong> What the assistant must always do and must never do: answer in your language, keep code and API names in English, do not invent APIs or numbers, do not call a task “done” without a playtest, prefer official Roblox documentation over old tutorials.</li>
<li><strong>Knowledge.</strong> Sourced facts with a date: how a service works, a Luau pattern you have already tested, how you think about the first minute of a game. The model is not allowed to silently overwrite this layer because a new chat had “a better idea.”</li>
<li><strong>Memory.</strong> What <em>this</em> project has learned: one-line lessons, “exact error → cause → fix,” a short <code>status.md</code> with open tasks and decisions, an index of modules you already tested.</li>
</ol>
<p>Without layer 3, you repeat yesterday’s bug. Without layer 2, the model hallucinates. Without layer 1, every session has a different standard of “finished.”</p>
<p>A practical folder looks like this: an index the model reads first, a copy-paste master prompt for the project instructions, a <code>01-Wissen</code> folder for stable notes, a code library with a short index, and <code>05-Projekte/&lt;Game&gt;/status.md</code>. That status file can be short: current stage, the core loop in one sentence, open tasks, dated decisions, one next step, and a launch checklist. Obsidian is useful because the links work, but a plain folder is enough.</p>
<h2>The routine that makes the vault work</h2>
<p>The structure only helps if you use it the same way every time.</p>
<p><strong>Before a task:</strong> open the index, then only the notes you need — lessons, preferences, the current <code>status.md</code>, the code index. If an error appears, check the error log first. Reuse tested modules. Do not reread the entire vault.</p>
<p><strong>After a task, including an aborted one:</strong> add one new lesson as a single line, record solved errors in the “message → cause → fix” form, put tested snippets in the library, and update <code>status.md</code> plus a dated changelog line. Extend existing notes; do not duplicate them. Change sourced knowledge only when you have a source. If the assistant cannot write files itself, have it emit a short “learning update” block that you paste in.</p>
<p>Two rules keep the system honest. Uncertain APIs, asset IDs, or numbers get looked up or tested in Studio; otherwise the answer says “uncertain” and how to check. And “done” requires a playtest. A short report should separate what is finished from what is still open.</p>
<h2>Studio can be connected, but the vault still matters without it</h2>
<p>The cleanest loop today is Claude Desktop talking to Roblox Studio through Roblox’s official Studio MCP interface: list the open Studio session, build, run a playtest, read the console, take a screenshot, then fix what actually failed. That is optional. ChatGPT and Gemini work well as a knowledge base plus a fixed prompt; you stay in copy-paste mode, with complete scripts, a setup path, and a test checklist. The vault is what stops both setups from drifting.</p>
<h2>What this does not do</h2>
<p>A vault will not invent a good game idea, pass the Device Simulator for you, or guarantee players. Market numbers go stale; if they have no source and date, they do not belong in <code>01-Wissen</code>. An oversized vault that nobody updates is just slower chaos. A few notes that you actually read beat a 200-file archive.</p>
<p>What it does do is reduce invented APIs, duplicate systems, and the feeling that every morning is a new project.</p>
<h2>If you do not want to assemble this from scratch</h2>
<p>I put this structure into a German-language kit called <strong>Roblox Second Brain</strong>: a copy-paste master prompt, a 292-page knowledge base (PDF and Markdown, dated 31 October 2026), setup from zero for Windows and macOS, a Luau course, genre blueprints, 101 game briefs, a CSV catalog of 2,174 experiences (Roblox API snapshot from the same date), a code library, Blender scripts for your own low-poly models, and the Obsidian-friendly vault with the before/after routine above. It is 19 € once. There is no success claim, and it is not an official Roblox or Anthropic product.</p>
<p>A free sample — 12 patterns shared by many hit games, a second-by-second first-minute plan, and a 30-day zero-budget launch template — is here:</p>
<p><a href="https://whop.com/kelvaro-creator-kits/leseprobe-roblox-second-brain">https://whop.com/kelvaro-creator-kits/leseprobe-roblox-second-brain</a></p>
<p>The full kit is here:</p>
<p><a href="https://whop.com/kelvaro-creator-kits/roblox-second-brain">https://whop.com/kelvaro-creator-kits/roblox-second-brain</a></p>
<p><em>Disclosure: the linked kit is my own paid product. The sample is free.</em></p>
]]></content:encoded></item><item><title><![CDATA[Build a local Python pipeline that turns a long talk into six 9:16 Shorts]]></title><description><![CDATA[If you cut Shorts from podcasts or interviews, most of the clock time is not creative. It is the same checklist: find a 30-second stretch that stands on…]]></description><link>https://kelvaro.hashnode.dev/build-a-local-python-pipeline-that-turns-a-long-talk-into-six-9-16-shorts</link><guid isPermaLink="true">https://kelvaro.hashnode.dev/build-a-local-python-pipeline-that-turns-a-long-talk-into-six-9-16-shorts</guid><category><![CDATA[Python]]></category><category><![CDATA[FFmpeg]]></category><category><![CDATA[whisper]]></category><category><![CDATA[video]]></category><dc:creator><![CDATA[Kelvaro]]></dc:creator><pubDate>Wed, 11 Nov 2026 20:44:57 GMT</pubDate><content:encoded><![CDATA[<p>If you cut Shorts from podcasts or interviews, most of the clock time is not creative. It is the same checklist: find a 30-second stretch that stands on its own, drop the dead air, reframe to 9:16, put the face in the middle, burn in captions, add a hook title, maybe a zoom and a sound on the punch word. That checklist is regular enough to automate on your own machine.</p>
<p>This article walks through a local Python pipeline that does that. No cloud editor, no monthly seat. The blocks are ordinary: <code>faster-whisper</code> for a word-timed transcript, a small scorer (or an LLM that only sees text) to pick clips, OpenCV to track a face, and FFmpeg to render. I will be honest about what this replaces and what it does not.</p>
<h2>What the pipeline should emit</h2>
<p>A useful default is six clips, each 20–60 seconds (30–45 is the sweet spot), already in 9:16, with silence trimmed, a vertical frame, word-by-word captions, a hook title in the first couple of seconds, light zooms, optional B-roll and sound effects, a progress bar, and a clean end that can loop.</p>
<p>Support two inputs, because the media is different:</p>
<ul>
<li><strong>talk</strong> — one long file (podcast, interview, vlog). Find the best moments and cut each as its own Short.</li>
<li><strong>lifestyle</strong> — a folder of raw clips with little speech. Edit the folder to a beat: speed ramps, flash or whip transitions, kinetic type, a lower third, a simple grade.</li>
</ul>
<p>The rest of this article is talk mode. Lifestyle is a second renderer on the same command, not a second architecture.</p>
<p>A hard rule: only process video you or your client have the rights to. <code>yt-dlp</code> can fetch a URL, but downloading someone else's upload and reposting it breaks copyright and platform rules, automation or not.</p>
<h2>The command shape</h2>
<p>Keep one entry point:</p>
<pre><code class="language-bash"># heuristic selection, then render six Shorts
python clip.py podcast.mp4 --lang en

# German audio, more accurate captions (slower)
python clip.py interview.mp4 --lang de --model medium

# export a transcript for an LLM to pick clips, then render that file
python clip.py podcast.mp4 --analyze
python clip.py podcast.mp4 --clips work/podcast/clips.json --only 2 5
</code></pre>
<p>Useful flags: <code>--fast</code> (FFmpeg <code>veryfast</code>), <code>--no-sfx</code>, <code>--no-broll</code>, <code>--music default</code> for a quiet bed with ducking, and <code>--crop</code> for a full-bleed vertical crop. Cropping is fashionable; it also chops hands and slides. A safer default is to fit the whole landscape frame onto 9:16 and use a blurred copy of the same frame as background.</p>
<p>You need Python 3.12, FFmpeg and ffprobe on <code>PATH</code>, <code>faster-whisper</code>, OpenCV, NumPy, Pillow, SciPy, <code>soundfile</code>, and <code>librosa</code>. A GPU helps Whisper, but a CPU-only run with <code>--model base</code> and <code>--fast</code> is enough for a first pass.</p>
<h2>Step 1: a transcript with word timestamps</h2>
<p>Captions and clip boundaries both need <em>word</em> times, not paragraph times. <code>faster-whisper</code> (the CTranslate2 port of Whisper) writes <code>{word, start, end}</code> locally, including German with <code>--lang de</code>. From that list you can group sentences on punctuation, treat long gaps as silence, and later highlight the active caption word.</p>
<p>Write work files into <code>work/&lt;slug&gt;/</code>. A replayable transcript lets you re-render clip 3 without transcribing again.</p>
<h2>Step 2: find stretches that work as Shorts</h2>
<p>A Short has to survive without the rest of the episode. Score seven checks from 0–10 and skip anything with a mean under 6:</p>
<ol>
<li><strong>Hook in the first two seconds.</strong> A question, a hard claim, a number, a contradiction. Not "so, yeah…"</li>
<li><strong>Self-contained.</strong> No dangling "he" or "as I said."</li>
<li><strong>One thought.</strong> Setup, example, payoff.</li>
<li><strong>Payoff at the end</strong>, and a cut <em>on</em> the payoff, not after it trails off.</li>
<li><strong>Energy.</strong> A laugh, a pace spike, a personal story.</li>
<li><strong>Something to comment on.</strong> A take, a myth, a surprise.</li>
<li><strong>Length.</strong> Prefer 30–45 s; allow 20–60 s. Short and sharp beats long and soft.</li>
</ol>
<p>Start on the first word of the hook sentence. End on the last word of the payoff (keep the laugh if it comes right after). Do not let clips overlap.</p>
<p>A first version does not need an LLM. Slide a sentence window across the 20–60 s range. Boost hook words in the first sentence, questions, and emotion or controversy terms (English and German). Penalize openings that start mid-thought (<code>And</code>, <code>So</code>, <code>Und</code>, <code>Also</code>). Prefer a clean sentence end and a duration near 38 s. Measure RMS energy with FFmpeg (<code>f32le</code> mono at 8 kHz) and reward variance, plus a mild words-per-second bonus. Sort by score and greedily keep non-overlapping winners.</p>
<p>That already beats "cut every 45 seconds from the start." It still misses quiet jokes and can over-rank a segment that merely contains the word "secret."</p>
<p>The better, still cheap path is <code>--analyze</code>: export a timestamped sentence list plus a short briefing, and let Claude or ChatGPT apply the seven checks and write a <code>clips.json</code> (<code>start</code>, <code>end</code>, a hook title of at most six words, keywords to highlight, optional speaker name, optional B-roll search terms). The model never sees the pixels, only text. A 20-minute episode is roughly 5,000–8,000 tokens. Then render exactly that file.</p>
<h2>Step 3: reframe to 9:16 without a jumpy crop</h2>
<p>For each output frame, detect the face (OpenCV's YuNet model is small and MIT-licensed), aim a 9:16 window at it, and <em>smooth the window over time</em>. Without a low-pass, every head nod becomes a camera bump.</p>
<p>Two framing policies:</p>
<ul>
<li><strong>fit (default):</strong> letterbox the full frame onto 9:16, blur and scale the same frame as background. Nothing important is shaved off.</li>
<li><strong>crop:</strong> fill the frame and follow the face. Better for a tight talking-head; worse when two people share the shot or the speaker holds up a notebook.</li>
</ul>
<p>If two faces are in frame, prefer a window where one person is clearly speaking, or start from a file that is already vertical.</p>
<h2>Step 4: render in one FFmpeg pass</h2>
<p>Once you have <code>(start, end)</code> and the word list, the renderer is bookkeeping: cut the source, drop pauses above a threshold (around 0.35 s works), overlay word-by-word captions with the current word emphasized, show a hook title for about 2.5 seconds, punch-in zoom on three to eight keywords, optionally lay B-roll on those hits, add light SFX, duck a music bed, and draw a progress bar with a last-frame hold so the clip can loop.</p>
<p>For B-roll, a local folder named by keyword is the cleanest option. If you fall back to a stock API, write a credits file and put required CC BY attribution in the description.</p>
<p>Generating ASS or title/drawtext filters from the word timestamps is more reliable than typography frame-by-frame in Python, except for motion-graphics layers you pre-render with Pillow.</p>
<p>Watch every file before you post. The pipeline will caption a mis-heard name, start a cut on a breath, or zoom on the wrong word. A two-minute review saves a bad upload.</p>
<p>Lifestyle mode, briefly: skip Whisper, detect the beat with <code>librosa</code>, lay clips on the downbeats, and add ramps plus flash, glitch, zoom, or whip-pan transitions. This will not beat a careful CapCut or After Effects pass on tracking and music feel. Treat it as a rough cut.</p>
<h2>Rights, cost, and an honest ceiling</h2>
<p>Use client-cleared or your own recordings. Running locally means no subscription for the core path. Optional API keys (Pexels, Pixabay, or an LLM) are extras. Budget about fifteen minutes on Windows for Python, FFmpeg, a venv, and <code>pip install</code>. Linux and macOS should work with Python 3.12 and FFmpeg; I have only treated Windows as the tested baseline.</p>
<p>A pipeline like this can take over something like 70–80% of the repetitive cutting. It does not replace a good editor, and it will not invent a hook that is not in the tape. The win is that you spend your time on selection and a last pass, not on clicking the same five effects in a timeline.</p>
<p>I packaged this exact flow as a paid kit (the Auto-Clipping-System): the Python source, a German step-by-step guide, a Windows installer script, the clip-scoring rules, prompts for Claude, bundled CC0 sounds, OFL fonts, and demo clips. It is 29 € once, no subscription. If you want the boxed version instead of wiring the stages yourself, it is here: <a href="https://whop.com/kelvaro-creator-kits/auto-clipping-system">https://whop.com/kelvaro-creator-kits/auto-clipping-system</a></p>
<p><em>Disclosure: that kit is my own paid product. No guarantee of views or revenue. Please preview every clip before you publish it.</em></p>
]]></content:encoded></item></channel></rss>