Wenlan
Wenlan은 소스 기반 AI 지식베이스·LLM 위키로, 문서·결정을 페이지화해 검색·검토·리뷰 가능
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
Voice memo in, speaker labelled document out. Runs whisperX and pyannote locally, and remembers who is who next time.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Recorded conversations become a document that says who said what.
Transcribes, separates the voices, and remembers a voice once you have named it. Everything runs on your own machine.
Diarization tells you there were two people. It will not tell you that the second one is Dana, and the next recording will number them differently anyway. scriba keeps the voice prints that pyannote computes and throws away, files them under the name you give, and matches them the next time.
scriba run meeting.m4a
scriba name meeting.m4a SPEAKER_00=Ada SPEAKER_01=Rafiq
scriba run tomorrow.m4a # Ada and Rafiq are picked out on their own
The screenshots below use two recordings that are entirely synthetic: the dialogue is
invented and the voices are macOS speech synthesis. python tools/make-demo.py
rebuilds them, so you can reproduce the whole flow without a real conversation.
Drop a file in and nothing happens until you say so. Transcription runs for about as long as the recording lasts, which is not something to start by accident.
The two settings above the button are the ones worth touching. Language is left to detection unless you know better, and the number of people in the room changes the result more than anything else here.
Seven stages, each ticked as it finishes, with the engine's own output underneath. The window can be closed and the work carries on.
Record a conversation, transcribe it, hand the text to whatever you use for reading and searching. The flow is obvious. Doing it for real breaks in four places.
The bug this project was born from. A script with a hardcoded --language it, handed a
recording that was actually in Spanish. Whisper did not fail. It guessed, and produced
fluent, punctuated, entirely made-up Italian.
The two languages are close enough that guessing produces real words. Spanish and Italian are full of pairs that sound alike and mean different things, so the output lands on the wrong one and reads perfectly:
| Spanish | Heard as Italian | Which means |
|---|---|---|
salir (to leave) | salire | to go up |
carta (letter) | carta | paper |
burro (donkey) | burro | butter |
éxito (success) | esito | outcome |
Those four are the textbook pairs, put here to show the shape of the failure. What it does to a real recording is worse, because it does it to every sentence at once and each one comes out plausible.
Nothing on screen says anything is wrong. The text reads as correct, and you then reason on top of it for weeks.
Left alone, Whisper picks the language from the first 30 seconds, which in a real
conversation is small talk. lang.py samples five windows across the
whole file and votes, weighting each window by confidence, and says so when the vote is
close instead of choosing in silence.
It also says so when the windows agree and none of them was sure. Agreement on its own used to be the whole confidence, which is a share: five windows that all say Galician at 30% agree perfectly, and the file came out reported at 100% confidence. Now the strength of the winning windows is part of the number, and a unanimous shrug is called one.
Diarization hands you SPEAKER_00, SPEAKER_01. Those labels are arbitrary, and they
are reassigned on every file: the same person is SPEAKER_00 on Monday and
SPEAKER_02 on Tuesday. So you rename them by hand, every time, forever.
pyannote computes a 256-dimensional embedding per speaker anyway, since that is how it
decides who is who. whisperX discards it by default; since 3.8.6, the version pinned
here, return_embeddings=True hands it back. So the embeddings on their own no longer
argue for going around whisperX.
diarize.py calls pyannote directly all the same, and now for a
different reason: whisperX's diarization wrapper runs against pyannote 4 and nothing
else, and it drops the overlap-free segmentation pyannote 4 also returns, with no flag
to ask for it back. Calling pyannote directly keeps one file working on pyannote 3 and 4
alike. The vectors survive either way, and voices.py files them
under the name you assign. From then on the name attaches itself.
There are three zones rather than two:
| Cosine similarity | What happens |
|---|---|
| 0.75 and above | the name is applied |
| 0.55 to 0.75 | "Maybe Ada?" A suggestion. Confirming stays your job. |
| below 0.55 | new voice |
There is also a margin of 0.05 over the runner-up: when two people on file come out almost equally close, nothing is chosen. A wrong name raises no error either, and it quietly poisons every transcript downstream, so an unanswered question is the better failure.
The thresholds are a starting point to calibrate, not a published constant. To set them properly: hand-label three or four conversations, find where the same-speaker and different-speaker similarity distributions cross, put the threshold there. Twenty minutes of annotated audio tells you more than any benchmark, and for diarization in Italian there is no benchmark to argue with.
Whisper cuts roughly every 30 seconds no matter who is speaking. On a seven-minute test file that gave 16 blocks, each one holding more than one speaker. scriba realigns at word level and reassembles real turns: same file, 41 turns. That is the difference between a list of fragments and something a person, or a model, can read.
여기까지가 README 앞부분입니다. 나머지는 원문에서 읽을 수 있습니다. README 전문 보기 ↗