<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Diarisation on Boris Dupoux</title><link>https://borisdupoux.com/tags/diarisation/</link><description>Recent content in Diarisation 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/diarisation/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. This job was transcribed with WhisperX (with pyannote diarisation), not
faster-whisper. Edit quality has not yet been evaluated against a human edit
(n = 1 conference).&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); 10 of the 16 tracks were near-digital silence, 6 carried signal (≈ 9 h 17 min)&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;head/tail trimmed and one internal 16 s gap removed (36.9 s total); 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;p&gt;The silent tracks still produced stock Whisper hallucinations in their raw
transcripts; they are excluded from the edit by track classification.&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;, either through WhisperX (used for the
case study above) or via faster-whisper (CTranslate2). The faster-whisper path runs 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, per 100 ms frame, the microphone using each speech
segment&amp;rsquo;s signal-to-noise ratio above the track&amp;rsquo;s 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 from a separate
seven-track recording, distinct from the 16-track case study above: 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;p&gt;Real-time factors (4 × 1 min, single run; decoding settings differ per pipeline):&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
(pseudo-reference, 4 × 1-minute clips of the separate 7-track recording). The
metric strips case and punctuation, so on manual inspection the divergence
is largely word segmentation, spelling of names and clip-edge effects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetitions:&lt;/strong&gt; 1.1–2.4 % 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;Edit quality has not yet been evaluated against a human edit (n = 1
conference).&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>