<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Whisper on Boris Dupoux</title><link>https://borisdupoux.com/tags/whisper/</link><description>Recent content in Whisper on Boris Dupoux</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Mon, 01 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://borisdupoux.com/tags/whisper/index.xml" rel="self" type="application/rss+xml"/><item><title>From Multitrack Recordings to an Editable Timeline: Transcription, Diarisation and AAF Export</title><link>https://borisdupoux.com/research/speech-to-edit/</link><pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate><guid>https://borisdupoux.com/research/speech-to-edit/</guid><description>&lt;h2 id="context"&gt;&lt;a href="#context" class="header-anchor"&gt;&lt;/a&gt;Context
&lt;/h2&gt;&lt;p&gt;A conference recorded on several microphones (speakers, room, music) produces
hours of raw multitrack audio. Most editing time goes into locating material —
who speaks when, on which track, where the silences are — before any cutting.&lt;/p&gt;
&lt;p&gt;Open-source speech tooling (Whisper and its derivatives, pyannote, Silero VAD)
solves transcription and &amp;ldquo;who spoke when&amp;rdquo; well, but stops at &lt;strong&gt;text&lt;/strong&gt;. The
question addressed here is whether that analysis can be delivered as an
&lt;strong&gt;editing object&lt;/strong&gt;: a multitrack timeline in AAF, the interchange format that
audio workstations open natively, with tracks qualified and dead time removed.&lt;/p&gt;
&lt;h2 id="before--after"&gt;&lt;a href="#before--after" class="header-anchor"&gt;&lt;/a&gt;Before → after
&lt;/h2&gt;&lt;p&gt;One real job processed end to end: a conference recorded on &lt;strong&gt;16 tracks&lt;/strong&gt;.
Everything on the right-hand side was produced automatically, with no manual
cut; figures are read from the job&amp;rsquo;s analysis database and from the delivered
AAF.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;&lt;/th&gt;
					&lt;th&gt;Input (raw recording)&lt;/th&gt;
					&lt;th&gt;Generated output (AAF timeline)&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Tracks&lt;/td&gt;
					&lt;td&gt;16 × 1 h 32 min 54 s (≈ 24 h 46 min of audio to review)&lt;/td&gt;
					&lt;td&gt;16 tracks kept in the AAF, 6 carrying clips&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Track roles&lt;/td&gt;
					&lt;td&gt;4 speech · 2 music (one auto-flagged as ambience) · 10 silent&lt;/td&gt;
					&lt;td&gt;10 silent tracks left empty, no manual triage&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Material&lt;/td&gt;
					&lt;td&gt;204 speech regions and 5 music passages detected&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;41 clips&lt;/strong&gt; (37 speech, 4 music)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Cuts&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;40 hard cuts&lt;/strong&gt; between sources, of which 33 microphone switches&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Transitions&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;0 crossfades: straight cuts, each speech clip extended by a 0.3 s tail&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Dead time&lt;/td&gt;
					&lt;td&gt;—&lt;/td&gt;
					&lt;td&gt;3 gaps ≥ 4.5 s rippled out (36.9 s); timeline 1 h 32 min 17 s&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img alt="Multitrack before/after: 16 input tracks coloured by role over time, and the generated edit showing which track is kept per segment, with source switches marked" class="gallery-image" data-flex-basis="333px" data-flex-grow="138" height="1152" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://borisdupoux.com/research/speech-to-edit/multitrack-before-after.png" srcset="https://borisdupoux.com/research/speech-to-edit/multitrack-before-after_hu_1f55e6879ef891c6.png 800w, https://borisdupoux.com/research/speech-to-edit/multitrack-before-after.png 1600w" width="1600"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Top: the 16 input tracks over the full recording, coloured by the detected
role and activity; the four speech microphones are active almost all the time
because each one also picks up its neighbours. Bottom: the generated edit, one
lane per track as in the AAF — a single source is kept at any moment, ticks mark
the 40 source switches, hatched areas in the top panel are the removed dead
time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Speech clips are few and long (median 79 s, longest 10 min 11 s): the montage
follows the active speaker rather than chopping on every breath, and
micro-switches shorter than 0.8 s are absorbed by the anti-flicker smoothing.
Ten clips are still shorter than 6 s — short interjections, the first place to
look when checking the edit.&lt;/p&gt;
&lt;h2 id="approach"&gt;&lt;a href="#approach" class="header-anchor"&gt;&lt;/a&gt;Approach
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TB
 A["Multitrack WAV recording"] --&gt; B["Decode to 16 kHz mono + RMS energy per 100 ms"]
 B --&gt; C["Voice activity detection (Silero)"]
 C --&gt; D["Transcription (Whisper large-v3, chunked)"]
 D --&gt; E["Forced word alignment (WhisperX / wav2vec2)"]
 C --&gt; F["Speaker diarisation (pyannote)"]
 E --&gt; G["Speaker assignment + segment post-processing"]
 F --&gt; G
 B --&gt; H["Track role: speech / music / silent"]
 G --&gt; H
 H --&gt; I["Montage: active microphone per 100 ms frame"]
 I --&gt; J["AAF export (linked to original WAV names)"]
 I --&gt; K["Mono mixdown WAV"]&lt;/pre&gt;&lt;h2 id="methods"&gt;&lt;a href="#methods" class="header-anchor"&gt;&lt;/a&gt;Methods
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Transcription.&lt;/strong&gt; Whisper &lt;code&gt;large-v3&lt;/code&gt; via faster-whisper (CTranslate2), run on
fixed-length chunks inside an isolated, persistent worker process: the model is
loaded once, and a native crash only loses one chunk, which is reported rather
than silently dropped. Decoding uses beam search with Silero VAD filtering and
&lt;code&gt;condition_on_previous_text&lt;/code&gt; disabled to limit repetition loops.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Word timing and speakers.&lt;/strong&gt; Word-level timestamps come from WhisperX forced
alignment (wav2vec2). Speaker turns come from pyannote diarisation and are
assigned to words by temporal overlap.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-processing.&lt;/strong&gt; Pure filters remove known Whisper hallucinations
(stock subtitle credits, &amp;ldquo;thanks for watching&amp;rdquo;-type phrases, music-only
segments), non-Latin output on French audio and duplicates, then merge
consecutive same-speaker segments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Track qualification and montage.&lt;/strong&gt; Each track is classified as speech, music
or silent by combining frame energy, VAD coverage and transcript statistics.
The montage selects, for every 100 ms frame, the microphone with the highest
signal-to-noise ratio above its own noise floor (reducing cross-talk
&amp;ldquo;bleed&amp;rdquo;), applies anti-flicker smoothing and ripples out global dead time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronisation.&lt;/strong&gt; Separate recorders are aligned by matching distinctive
words across transcripts (robust median of offsets), after a coarse
clock-based placement corrected for clock drift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Export.&lt;/strong&gt; The edit list is written as a multitrack AAF with &lt;code&gt;pyaaf2&lt;/code&gt;, either
embedding the audio or linking to it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deployment.&lt;/strong&gt; A web portal accepts jobs and a separate compute worker polls
it over HTTPS. Clients upload only compressed analysis copies together with a
manifest of their original files; the delivered AAF references the
&lt;strong&gt;original WAV names&lt;/strong&gt;, so the editor relinks full-quality sources locally and
the originals never leave the client&amp;rsquo;s machine.&lt;/p&gt;
&lt;h2 id="results"&gt;&lt;a href="#results" class="header-anchor"&gt;&lt;/a&gt;Results
&lt;/h2&gt;&lt;p&gt;Benchmark (&lt;code&gt;bench_stt.py&lt;/code&gt;) on real conference audio: one-minute speech windows
selected by VAD on four speech tracks, Whisper &lt;code&gt;large-v3&lt;/code&gt; throughout, Apple
Silicon laptop, warm timings excluding model loading. No ground-truth
transcript was available, so quality figures are &lt;strong&gt;divergence from a
pseudo-reference&lt;/strong&gt; (WhisperX output), not absolute error.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Pipeline (CPU)&lt;/th&gt;
					&lt;th style="text-align: right"&gt;Real-time factor&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;openai-whisper&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.43&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WhisperX&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.51&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WhisperX + forced alignment&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.54&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;openai-whisper + word timestamps&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.57&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;faster-whisper (beam 5 + VAD)&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.66&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;faster-whisper + word timestamps&lt;/td&gt;
					&lt;td style="text-align: right"&gt;0.70&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Word timing:&lt;/strong&gt; native word timestamps deviate from forced alignment by a
median of ~205 ms (faster-whisper) and ~210 ms (openai-whisper); forced
alignment adds about 1.2 s per minute of audio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transcript divergence:&lt;/strong&gt; 7–11 % word-level divergence between pipelines,
mostly punctuation, segmentation and normalisation on manual inspection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetitions:&lt;/strong&gt; 1–2 % repeated content; no hallucinated passages observed on
these clips.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Track detection:&lt;/strong&gt; the three music/silence tracks of the seven-track
recording were identified by VAD and skipped automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="limitations--next-steps"&gt;&lt;a href="#limitations--next-steps" class="header-anchor"&gt;&lt;/a&gt;Limitations &amp;amp; next steps
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Quality is measured against a pseudo-reference; a manually transcribed test
set is needed to report true word error rate and diarisation error rate.&lt;/li&gt;
&lt;li&gt;Benchmark clips are short (one minute per track) and all single-speaker
windows, so diarisation accuracy on multi-speaker overlap is not yet
quantified.&lt;/li&gt;
&lt;li&gt;CTranslate2 has no Metal backend: on Apple Silicon, faster-whisper and
WhisperX run on CPU, while CUDA is used where available. Device choice per
stage was benchmarked rather than assumed; a CUDA comparison remains to be
measured.&lt;/li&gt;
&lt;li&gt;AAF export targets Logic Pro import; other editors have not been
systematically tested.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="stack"&gt;&lt;a href="#stack" class="header-anchor"&gt;&lt;/a&gt;Stack
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Python&lt;/code&gt; · &lt;code&gt;faster-whisper&lt;/code&gt; · &lt;code&gt;CTranslate2&lt;/code&gt; · &lt;code&gt;WhisperX&lt;/code&gt; · &lt;code&gt;pyannote.audio&lt;/code&gt; · &lt;code&gt;Silero VAD&lt;/code&gt; · &lt;code&gt;PyTorch (MPS / CUDA)&lt;/code&gt; · &lt;code&gt;ffmpeg&lt;/code&gt; · &lt;code&gt;pyaaf2&lt;/code&gt; · &lt;code&gt;SQLite&lt;/code&gt; · &lt;code&gt;PHP&lt;/code&gt; · &lt;code&gt;MySQL&lt;/code&gt;&lt;/p&gt;</description></item></channel></rss>