-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup-guide.html
More file actions
518 lines (455 loc) · 23.8 KB
/
Copy pathsetup-guide.html
File metadata and controls
518 lines (455 loc) · 23.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>casting-call: New-Machine Setup, End to End</title>
<style>
/* ===== THEME CHROME — DO NOT EDIT (keep identical across all docs) ===== */
:root {
--bg: #fff;
--text: #1a1a1a;
--heading: #111;
--muted: #555;
--muted2: #444;
--faint: #666;
--rule: #ddd;
--dot: #e2e2e2;
--code-bg: #f2f2f2;
--setup-bg: #f6f6f4;
--setup-border: #e0e0dc;
--watch-bg: #fbf7ec;
--watch-border: #b58900;
--btn-bg: #f0f0ee;
--btn-border: #ccc;
--accent: #c9a227;
}
html[data-theme="dark"] {
--bg: #17191d;
--text: #cfd2d6;
--heading: #f1f2f4;
--muted: #9aa0a6;
--muted2: #b3b8bd;
--faint: #888e94;
--rule: #34383e;
--dot: #2b2f34;
--code-bg: #262a30;
--setup-bg: #1e2126;
--setup-border: #34383e;
--watch-bg: #241f14;
--watch-border: #c9a227;
--btn-bg: #23262b;
--btn-border: #3a3f45;
}
#theme-toggle {
position: fixed;
top: 1rem;
right: 1rem;
font-family: system-ui, sans-serif;
font-size: 0.85rem;
color: var(--text);
background: var(--btn-bg);
border: 1px solid var(--btn-border);
border-radius: 6px;
padding: 0.4rem 0.7rem;
cursor: pointer;
z-index: 10;
}
#theme-toggle:hover { border-color: var(--faint); }
/* ===== END THEME CHROME ===== */
/* ===== BASE DOCUMENT STYLE (Sam's standing conventions) ===== */
body {
font-family: Georgia, 'Times New Roman', serif;
font-size: 16px;
line-height: 1.55;
color: var(--text);
max-width: 46rem;
margin: 2.5rem auto;
padding: 0 1.5rem;
background: var(--bg);
transition: background 0.15s ease, color 0.15s ease;
}
h1, h2, h3 {
font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
line-height: 1.25;
color: var(--heading);
}
h1 { font-size: 1.7rem; margin: 0 0 0.25rem; }
h2 {
font-size: 1.2rem;
margin: 2.2rem 0 0.6rem;
padding-bottom: 0.3rem;
border-bottom: 1px solid var(--rule);
page-break-after: avoid;
}
h3 { font-size: 1.02rem; margin: 1.5rem 0 0.4rem; }
p, li { color: var(--text); }
a { color: inherit; text-decoration: underline; text-decoration-color: var(--accent); }
code {
font-family: 'SF Mono', ui-monospace, Menlo, monospace;
font-size: 0.85em;
background: var(--code-bg);
padding: 0.1em 0.35em;
border-radius: 3px;
}
pre {
background: var(--code-bg);
padding: 0.9rem 1.1rem;
border-radius: 6px;
overflow-x: auto;
page-break-inside: avoid;
}
pre code { background: none; padding: 0; }
blockquote {
border-left: 3px solid var(--watch-border);
background: var(--watch-bg);
margin: 0.8rem 0;
padding: 0.7rem 0.9rem;
page-break-inside: avoid;
}
table { border-collapse: collapse; width: 100%; margin: 0.8rem 0; page-break-inside: avoid; }
th, td { border: 1px solid var(--rule); padding: 0.45rem 0.7rem; text-align: left; font-size: 0.95rem; }
th { font-family: system-ui, sans-serif; }
.meta { margin-top: 2.5rem; color: var(--faint); font-size: 0.85rem; font-family: system-ui, sans-serif; }
/* ===== END BASE ===== */
/* ===== PRINT: force clean light, hide the toggle ===== */
@media print {
:root { --bg:#fff; --text:#1a1a1a; --heading:#111; --setup-bg:#f6f6f4; --watch-bg:#fbf7ec; --code-bg:#f2f2f2; }
body { margin: 0; font-size: 12pt; }
blockquote, pre { background: none; }
#theme-toggle { display: none; }
}
/* ===== Document-specific styles ===== */
.tag { display: inline-block; font-family: system-ui, sans-serif; font-size: 0.7rem;
font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
padding: 0.1rem 0.45rem; border-radius: 4px; vertical-align: middle; margin-left: 0.4rem; }
.tag.cli { background: var(--code-bg); color: var(--muted2); border: 1px solid var(--rule); }
.tag.gui { background: var(--watch-bg); color: var(--watch-border); border: 1px solid var(--watch-border); }
ol li { margin: 0.35rem 0; }
</style>
</head>
<body>
<button id="theme-toggle" type="button" aria-label="Toggle dark mode">Light mode</button>
<!-- ===== CONTENT START ===== -->
<h1>casting-call: New-Machine Setup, End to End</h1>
<p>Everything it takes to go from a fresh Mac to a working call-recording rig: install the tools,
record a screen call as three separate audio tracks (you, the far side, and a marker track), and
turn it into a transcript with your flagged moments dropped in at the right spot. Follow it top to
bottom the first time.</p>
<p>Sections are tagged <span class="tag cli">terminal</span> when they are copy-paste commands
(safe to hand to an agent) or <span class="tag gui">in the app</span> when they are clicks in OBS
or Stream Deck that only a human can do. The flow the whole thing produces is in
<code>docs/pipeline-flow.html</code> if you want the picture first.</p>
<h2>What you end up with</h2>
<p>OBS records your screen plus three audio tracks into one <code>.mkv</code>. Track 1 is your mic,
Track 2 is the far side (Chrome, where Meet runs), and Track 3 is silent except for the short "mark
flag" / "mark action" clips a Stream Deck button fires. After the call, one command splits the
tracks, transcribes them, cleans up the junk Whisper invents on silence, and folds your markers in
as <code>[MARKER]</code> lines. If the far side ever drops out, the captions on screen are the backup.</p>
<h2>Part 1: The command line <span class="tag cli">terminal</span></h2>
<p>This whole part is safe to run start to finish. It installs the tools, pulls the repo, and
regenerates the marker clips locally (the <code>.wav</code> files are gitignored, so they do not
come down with the clone).</p>
<pre><code># Homebrew, if this machine does not have it yet
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Tools: ffmpeg + whisper for audio, tesseract for the caption fallback, the apps
brew install ffmpeg whisper-cpp tesseract
brew install --cask obs elgato-stream-deck
# Python deps (Homebrew's python3; --break-system-packages is required on it)
pip3 install --break-system-packages pillow numpy pytesseract pytest
# Whisper model, ~3 GB, one time
mkdir -p ~/whisper-models
curl -o ~/whisper-models/ggml-large-v3.bin -L \
'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin?download=true'
# Silero VAD model (~1 MB, one time) so Whisper only transcribes real speech and
# stops inventing looped lines on non-speech audio
curl -sL -o ~/whisper-models/ggml-silero-v5.1.2.bin \
https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v5.1.2.bin
# The repo
git clone https://github.com/samgutentag/casting-call.git ~/Developer/casting-call
cd ~/Developer/casting-call
# Your roster (real names stay local; this file is gitignored)
cp speakers_roster.example.json speakers_roster.json
# then edit speakers_roster.json: set "self" to your name, list the people you call
# Marker clips (regenerated here; not committed)
# name|phrase, because the two action clips say more than their filename.
# The 0.25s of trailing silence keeps whisper from clipping the last word
# ("...for me" / "...for them") at a segment boundary.
mkdir -p obs/marker-clips
for spec in "topic|mark topic" \
"action-me|mark action for me" \
"action-them|mark action for them" \
"important|mark important" \
"question|mark question" \
"quote|mark quote" \
"video|mark video"; do
name="${spec%%|*}"; phrase="${spec#*|}"
say -o /tmp/_m.aiff "$phrase"
ffmpeg -loglevel error -i /tmp/_m.aiff -ar 16000 -ac 1 -c:a pcm_s16le \
-af "apad=pad_dur=0.25" "obs/marker-clips/mark-$name.wav" -y
done
rm -f /tmp/_m.aiff
# Confirm the toolchain is sane
python3 -m pytest -q</code></pre>
<p>If the tests pass, the software side is done. The clips now live in
<code>~/Developer/casting-call/obs/marker-clips/</code>, which you will point OBS at in Part 3.</p>
<blockquote>
If <code>python3</code> offers to install "command line developer tools," let it, then rerun the
last two commands. The scripts and Whisper only need what you just installed.
</blockquote>
<h2>Part 2: OBS, recording three tracks <span class="tag gui">in the app</span></h2>
<p>OBS records MKV on purpose. If a recording ever crashes, an MKV is still readable where an MP4
would be corrupt. You never remux it; the pipeline reads the MKV directly.</p>
<ol>
<li>Open OBS. Skip or click through the auto-config wizard.</li>
<li><strong>Settings, Output.</strong> Set <strong>Output Mode</strong> to <strong>Advanced</strong>.
On the <strong>Recording</strong> tab set <strong>Recording Format</strong> to
<strong>Matroska Video (.mkv)</strong>, and under <strong>Audio Track</strong> check
<strong>1, 2, and 3</strong>.</li>
<li><strong>Sources.</strong> Add these to your scene:
<ol>
<li><strong>Display Capture</strong> (or Window Capture) for the screen. This is the video.</li>
<li><strong>Audio Input Capture</strong> pointed at your mic (the Elgato Wave). This is you.</li>
<li><strong>Application Audio Capture</strong> targeting <strong>Google Chrome</strong>, not the
Meet PWA. Chrome routes call audio through one process that captures reliably; the PWA often
records silent.</li>
</ol>
</li>
<li><strong>Assign tracks.</strong> Click the gear in the Audio Mixer, choose
<strong>Advanced Audio Properties</strong>. For each source, use the <strong>Tracks</strong>
checkboxes on the right:
<ul>
<li>Mic (you): Track <strong>1</strong> only.</li>
<li>Chrome (far side): Track <strong>2</strong> only.</li>
</ul>
</li>
</ol>
<blockquote>
Do not switch your Mac's audio devices to route the call. That was the old rig, and it dropped the
mic to Meet mid-call. OBS only reads audio, so Meet keeps your real mic and headphones. Leave them
alone.
</blockquote>
<h2>Part 3: The marker track <span class="tag gui">in the app</span></h2>
<p>This is the piece that makes markers work without any syncing. Seven clips, each on its own
media source, all routed to Track 3, none of them ever heard by anyone live.</p>
<p><strong>Already have the old five- or seven-key rig?</strong> Do not rebuild it. Skip to
<a href="#migrate">Migrating an existing rig</a> below, which is three renames and nothing else.</p>
<p>Build each source exactly once, then repeat six times. The source names below are not cosmetic:
Stream Deck binds its keys to the source <em>name</em>, so a typo here means a dead key later.</p>
<ol>
<li>In the <strong>Sources</strong> panel, click <strong>+</strong> and choose
<strong>Media Source</strong>.</li>
<li>Leave <strong>Create new</strong> selected. Type the source name from the table below, exactly
as written. Click <strong>OK</strong>.</li>
<li>In the properties window that opens:
<ul>
<li><strong>Local File</strong>: click Browse and pick the matching <code>.wav</code> from
<code>obs/marker-clips/</code>.</li>
<li><strong>Uncheck Loop.</strong> If this stays checked the clip repeats for the whole call and
every marker in the transcript is garbage.</li>
<li><strong>Check "Restart playback when source becomes active."</strong></li>
<li>Leave everything else at its default. Click <strong>OK</strong>.</li>
</ul>
</li>
<li>Repeat for all seven rows.</li>
</ol>
<table>
<tr><th>Source name (type it exactly)</th><th>File to browse to</th></tr>
<tr><td><code>Topic Switch</code></td><td><code>obs/marker-clips/mark-topic.wav</code></td></tr>
<tr><td><code>Action For Me</code></td><td><code>obs/marker-clips/mark-action-me.wav</code></td></tr>
<tr><td><code>Action For Them</code></td><td><code>obs/marker-clips/mark-action-them.wav</code></td></tr>
<tr><td><code>Mark Important</code></td><td><code>obs/marker-clips/mark-important.wav</code></td></tr>
<tr><td><code>Mark Question</code></td><td><code>obs/marker-clips/mark-question.wav</code></td></tr>
<tr><td><code>Mark Quote</code></td><td><code>obs/marker-clips/mark-quote.wav</code></td></tr>
<tr><td><code>Mark Video</code></td><td><code>obs/marker-clips/mark-video.wav</code></td></tr>
</table>
<p>Now route all seven to Track 3 in one pass:</p>
<ol>
<li>Right-click any source, choose <strong>Advanced Audio Properties</strong>. Every source in the
scene is listed as a row.</li>
<li>For each of the seven marker rows:
<ul>
<li><strong>Tracks</strong>: check <strong>3</strong>, and uncheck <strong>1</strong> and
<strong>2</strong>. Markers must never touch the voice tracks.</li>
<li><strong>Audio Monitoring</strong>: <strong>Monitor Off</strong>. This is what keeps the clip
out of your headphones and, more importantly, out of anything Meet can pick up. It still records
to Track 3.</li>
</ul>
</li>
<li>Close the window. There is no Save button; the settings apply as you set them.</li>
</ol>
<blockquote>
<strong>Why nobody hears it:</strong> an OBS media source plays inside OBS. With Monitor Off, OBS
records it to Track 3 but never sends it to an output device, and OBS never feeds audio back into
your mic or Meet. So the marker is silent to you and to the caller, and still lands in the file.
</blockquote>
<h2>Part 4: Stream Deck buttons <span class="tag gui">in the app</span></h2>
<ol>
<li>Open the Stream Deck app and install the official <strong>OBS Studio</strong> plugin from its
Marketplace.</li>
<li>In OBS, enable <strong>Tools, WebSocket Server Settings, Enable WebSocket server</strong> so
Stream Deck can talk to it. If the Stream Deck plugin shows a red or grey status dot, it is this
setting.</li>
<li><strong>Start / Stop.</strong> Drag the OBS <strong>Start Recording</strong> action onto a key,
and <strong>Stop Recording</strong> onto another.</li>
<li><strong>The seven marker keys.</strong> For each one:
<ul>
<li>Search <strong>media</strong> in the actions list and drag <strong>Media Source Control</strong>
onto an empty key.</li>
<li><strong>Media Source</strong>: pick the source name from the table below. If the dropdown is
empty, OBS is not connected; fix step 2 first.</li>
<li><strong>Action</strong>: <strong>Restart</strong>. This is the one that works. "Source
Visibility" does not trigger playback, so do not use it here.</li>
<li><strong>Title</strong>: the key label from the table, so you can read the deck mid-call
without thinking.</li>
</ul>
</li>
</ol>
<table>
<tr><th>Key label</th><th>Media Source</th><th>Action</th></tr>
<tr><td>Topic</td><td><code>Topic Switch</code></td><td>Restart</td></tr>
<tr><td>Me</td><td><code>Action For Me</code></td><td>Restart</td></tr>
<tr><td>Them</td><td><code>Action For Them</code></td><td>Restart</td></tr>
<tr><td>Important</td><td><code>Mark Important</code></td><td>Restart</td></tr>
<tr><td>Question</td><td><code>Mark Question</code></td><td>Restart</td></tr>
<tr><td>Quote</td><td><code>Mark Quote</code></td><td>Restart</td></tr>
<tr><td>Video</td><td><code>Mark Video</code></td><td>Restart</td></tr>
</table>
<p>Put <strong>Me</strong> and <strong>Them</strong> next to each other on the deck. They are the
pair you will fumble between under pressure, and a wrong press puts someone else's task on your
list.</p>
<h3>What the seven keys mean</h3>
<table>
<tr><th>Key</th><th>Clip says</th><th>Transcript type</th><th>Press it when</th></tr>
<tr><td>Topic Switch</td><td>"mark topic"</td><td><code>topic</code></td><td>the conversation moves to a new subject. These are the dividers the digest reads to cut the call into sections.</td></tr>
<tr><td>Action For Me</td><td>"mark action for me"</td><td><code>action-me</code></td><td>you just picked up something you have to do.</td></tr>
<tr><td>Action For Them</td><td>"mark action for them"</td><td><code>action-them</code></td><td>someone on the far side owes you something, or you need to chase it later.</td></tr>
<tr><td>Mark Important</td><td>"mark important"</td><td><code>important</code></td><td>it matters and does not fit any other key.</td></tr>
<tr><td>Mark Question</td><td>"mark question"</td><td><code>question</code></td><td>you have a follow-up question. Say it into your muted mic right after the press so the digest captures the wording.</td></tr>
<tr><td>Mark Quote</td><td>"mark quote"</td><td><code>quote</code></td><td>somebody said something worth repeating verbatim.</td></tr>
<tr><td>Mark Video</td><td>"mark video"</td><td><code>video</code></td><td>something is on screen worth keeping. This is the only key that makes the digest pull a screenshot.</td></tr>
</table>
<blockquote>
<strong>The eighth type you never press.</strong> Both action clips say "action", so the parser
checks "for them" and "for me" before it falls back to a bare <code>action</code> type. That
fallback has no key. It only catches a press where whisper dropped the trailing word, and it shows
up in the digest as owner-unknown rather than being quietly filed onto the wrong side.
</blockquote>
<h3 id="migrate">Migrating an existing rig</h3>
<p>If you already ran the flag / important / action / question / quote / video / follow set, you do
not add or delete a single source. Four of the seven are unchanged, and the other three are a rename
plus a new file. The clips were regenerated in place, so
<code>mark-important.wav</code>, <code>mark-question.wav</code>, <code>mark-quote.wav</code> and
<code>mark-video.wav</code> keep their names and their sources need no attention at all.</p>
<p>Regenerate the clips first (the loop in Part 1), then in OBS:</p>
<table>
<tr><th>Old source</th><th>Rename to</th><th>Repoint Local File to</th></tr>
<tr><td><code>Mark Flag</code></td><td><code>Topic Switch</code></td><td><code>mark-topic.wav</code></td></tr>
<tr><td><code>Mark Action</code></td><td><code>Action For Me</code></td><td><code>mark-action-me.wav</code></td></tr>
<tr><td><code>Mark Follow Up</code></td><td><code>Action For Them</code></td><td><code>mark-action-them.wav</code></td></tr>
</table>
<ol>
<li>Double-click the old source in the <strong>Sources</strong> panel to open its properties, click
<strong>Browse</strong>, and pick the new <code>.wav</code>. Click <strong>OK</strong>.</li>
<li>Right-click the source, choose <strong>Rename</strong>, and type the new name exactly.</li>
<li>Repeat for the other two rows.</li>
<li>The three old files (<code>mark-flag.wav</code>, <code>mark-action.wav</code>,
<code>mark-follow.wav</code>) are gone after a regenerate. Nothing points at them any more.</li>
</ol>
<blockquote>
<strong>Renaming in OBS can orphan the Stream Deck key.</strong> The plugin binds to the source
name, and it does not reliably follow a rename. After the three renames, open each of those three
keys in the Stream Deck app and re-pick the <strong>Media Source</strong> from the dropdown, even if
it looks correct. Then press all seven keys once and confirm in Part 5 that seven marker lines come
back with seven distinct types.
</blockquote>
<blockquote>
<strong>Old transcripts are not rewritten.</strong> Anything recorded before this change still has
<code>[MARKER] flag</code> and <code>[MARKER] follow</code> lines in it. The parser no longer
produces those types, and the digest will list them in its trailing "unrecognized" group rather than
dropping them. That is deliberate: old calls stay readable, and nothing silently disappears.
</blockquote>
<blockquote>
<strong>The one gotcha that will silently eat your markers:</strong> the marker sources live on
Track 3, so the recording output has to actually record Track 3. Confirm Settings, Output,
Recording has Audio Tracks 1, 2, <em>and</em> 3 checked. If Track 3 is off, the buttons fire, the
clips play, and none of it ends up in the file. Sanity check a test recording with
<code>ffprobe recording.mkv</code> and count three audio streams.
</blockquote>
<h2>Part 5: Prove it works before a real call <span class="tag cli">terminal</span> + <span class="tag gui">in the app</span></h2>
<ol>
<li>In OBS, press <strong>Start</strong>, talk for a few seconds, then press <strong>all seven
marker keys</strong> with a beat between each, and press <strong>Stop</strong>. Press all seven, not
a couple: the point of the test is that each key produces a <em>different</em> type, which is
exactly what a mis-bound key breaks. Move the resulting <code>.mkv</code> into a folder, say
<code>~/calls/test/</code>.</li>
<li>Confirm the file really has three audio tracks:
<pre><code>ffprobe -v error -show_entries stream=index,codec_type -of csv ~/calls/test/*.mkv
# expect one 'video' line and three 'audio' lines</code></pre>
</li>
<li>Run the pipeline and read the result:
<pre><code>cd ~/Developer/casting-call
bash bin/extract_audio_stereo.sh ~/calls/test
cat ~/calls/test/*/*.txt</code></pre>
You should see <code>[You]</code> and <code>[Caller]</code> lines with <code>[MARKER]</code> lines
sitting inline wherever you pressed a key.</li>
<li>Check that all seven types came back distinct:
<pre><code>grep '\[MARKER\]' ~/calls/test/*/*.txt | awk '{print $NF}' | sort | uniq -c
# expect exactly one each of:
# action-me action-them important question quote topic video</code></pre>
A missing type means that key is bound to the wrong source or set to something other than Restart.
A stray <code>action</code> with no owner means whisper clipped the trailing word on that press;
re-record that clip with a longer trailing pad.</li>
</ol>
<p>If all seven types come back distinct, they land in the right place, and nobody heard them during
the test, the rig is done.</p>
<h2>Part 6: The daily loop</h2>
<ol>
<li>Start recording in OBS (or via the Stream Deck key). Tap the marker keys during the call
whenever something matters.</li>
<li>Stop. Drop the <code>.mkv</code> in a folder.</li>
<li><code>bash bin/extract_audio_stereo.sh <folder></code>. That is the transcript, markers and all,
ready to hand to an LLM.</li>
<li>Optional, only when you want to watch it back:
<code>bash bin/convert_video.sh <folder></code> renders a playable <code>.mp4</code> with you on the
left channel and the caller on the right.</li>
<li>Optional, to put real names on the far-side lines:
<code>bash bin/extract_speakers.sh <recording> --region x,y,w,h --transcript <transcript></code>.</li>
</ol>
<h2>Part 7: When the far side drops out</h2>
<p>Chrome's audio capture can die mid-call and record silence from that point on. The pipeline
watches for it: if Track 2 goes quiet for a sustained stretch, it prints a warning with the
timestamp and points you at the recovery tool. The lines Whisper would have invented on that dead
audio are stripped automatically, so the transcript does not fill up with fake "thank you" and
"subtitles by" junk.</p>
<p>To get the lost words back, the Meet captions on screen are the source:</p>
<pre><code>bash bin/stitch_captions.sh <recording> --region x,y,w,h</code></pre>
<p>The one catch, learned the hard way: the caption region is fixed, so the Meet window has to stay
in one place and size for the whole call. If you move or resize it, a single region will not cover
the recording, and you are into recovering one layout at a time. Park the Meet window and leave it.</p>
<p class="meta">casting-call · new-machine setup · 2026-07-24</p>
<!-- ===== CONTENT END ===== -->
<script>
/* ===== THEME SCRIPT — DO NOT EDIT (keep identical across all docs) ===== */
(function () {
var root = document.documentElement;
var btn = document.getElementById('theme-toggle');
var stored = null;
try { stored = localStorage.getItem('doc-theme'); } catch (e) {}
var systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = stored || (systemDark ? 'dark' : 'light');
function apply(t) {
root.setAttribute('data-theme', t);
btn.textContent = t === 'dark' ? 'Light mode' : 'Dark mode';
}
apply(theme);
btn.addEventListener('click', function () {
theme = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
apply(theme);
try { localStorage.setItem('doc-theme', theme); } catch (e) {}
});
})();
/* ===== END THEME SCRIPT ===== */
</script>
</body>
</html>