Comment utiliser OpenAIS Whisper pour transcrire et diariser les fichiers audio
Whisper est un système de reconnaissance vocal de pointe d'OpenAI qui a été formé sur 680 000 heures de données supervisées multilingues et multitâches collectées sur le Web. Cet ensemble de données important et diversifié conduit à une robustesse améliorée aux accents, au bruit de fond et au langage technique. De plus, il permet la transcription en plusieurs langues, ainsi que la traduction de ces langues en anglais. OpenAI a publié les modèles et le code pour servir de base pour créer des applications utiles qui tirent parti de la reconnaissance vocale.
Un gros inconvénient de Whisper est cependant qu'il ne peut pas vous dire qui parle dans une conversation. C'est un problème lors de l'analyse des conversations. C'est là que la diarisation entre en jeu. La diarisation est le processus d'identification qui parle dans une conversation.
Dans ce tutoriel, vous apprendrez à identifier les haut-parleurs, puis à les assortir avec les transcriptions de Whisper. Nous utiliserons pyannote-audio
pour y parvenir. Commençons!
Tout d'abord, nous devons préparer le fichier audio. Nous utiliserons le podcast des 20 premières minutes de Lex Fridmans avec le téléchargement de Yann. Pour télécharger la vidéo et extraire l'audio, nous utiliserons le package yt-dlp
.
! pip install -U yt-dlp
Nous aurons également besoin de ffmpeg installé
! wget -O - -q https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz | xz -qdc | tar -x
Maintenant, nous pouvons faire le téléchargement et l'extraction audio réels via la ligne de commande.
! yt-dlp -xv --ffmpeg-location ffmpeg-master-latest-linux64-gpl/bin --audio-format wav -o download.wav -- https://youtu.be/SGzMElJ11Cc
Nous avons maintenant le fichier download.wav
dans notre répertoire de travail. Coupez les 20 premières minutes de l'audio. Nous pouvons utiliser le package Pydub pour cela avec seulement quelques lignes de code.
! pip install pydub
from pydub import AudioSegment
t1 = 0 * 1000 # works in milliseconds
t2 = 20 * 60 * 1000
newAudio = AudioSegment . from_wav ( "download.wav" )
a = newAudio [ t1 : t2 ]
a . export ( "audio.wav" , format = "wav" )
audio.wav
est maintenant les 20 premières minutes du fichier audio.
pyannote.audio
est une boîte à outils open source écrite dans Python pour la diarisation des haut-parleurs. Basé sur le cadre d'apprentissage automatique Pytorch, il fournit un ensemble de blocs de construction neuronaux de bout en bout qui peuvent être combinés et optimisés conjointement pour construire des pipelines de diarisation des haut-parleurs. pyannote.audio
est également livré avec des modèles et des pipelines pré-entraînés couvrant un large éventail de domaines pour la détection d'activité vocale, la segmentation du haut-parleur, la détection de la parole chevauchée, l'intégration des haut-parleurs atteignant des performances de pointe pour la plupart d'entre elles.
Installation de pyannote et exécutant sur l'audio vidéo pour générer les diarzations.
! pip install pyannote.audio
from pyannote . audio import Pipeline
pipeline = Pipeline . from_pretrained ( 'pyannote/speaker-diarization' )
DEMO_FILE = { 'uri' : 'blabal' , 'audio' : 'audio.wav' }
dz = pipeline ( DEMO_FILE )
with open ( "diarization.txt" , "w" ) as text_file :
text_file . write ( str ( dz ))
Permet d'imprimer ceci pour voir à quoi il ressemble.
print(*list(dz.itertracks(yield_label = True))[:10], sep="n")
La sortie:
(<Segment(2.03344, 36.8128)>, 0, 'SPEAKER_00')
(<Segment(38.1122, 51.3759)>, 0, 'SPEAKER_00')
(<Segment(51.8653, 90.2053)>, 1, 'SPEAKER_01')
(<Segment(91.2853, 92.9391)>, 1, 'SPEAKER_01')
(<Segment(94.8628, 116.497)>, 0, 'SPEAKER_00')
(<Segment(116.497, 124.124)>, 1, 'SPEAKER_01')
(<Segment(124.192, 151.597)>, 1, 'SPEAKER_01')
(<Segment(152.018, 179.12)>, 1, 'SPEAKER_01')
(<Segment(180.318, 194.037)>, 1, 'SPEAKER_01')
(<Segment(195.016, 207.385)>, 0, 'SPEAKER_00')
Cela semble déjà assez bon, mais nettoyons un peu les données:
def millisec ( timeStr ):
spl = timeStr . split ( ":" )
s = ( int )(( int ( spl [ 0 ]) * 60 * 60 + int ( spl [ 1 ]) * 60 + float ( spl [ 2 ]) ) * 1000 )
return s
import re
dz = open ( 'diarization.txt' ). read (). splitlines ()
dzList = []
for l in dz :
start , end = tuple ( re . findall ( '[0-9]+:[0-9]+:[0-9]+.[0-9]+' , string = l ))
start = millisec ( start ) - spacermilli
end = millisec ( end ) - spacermilli
lex = not re . findall ( 'SPEAKER_01' , string = l )
dzList . append ([ start , end , lex ])
print ( * dzList [: 10 ], sep = ' n ' )
[33, 34812, True]
[36112, 49375, True]
[49865, 88205, False]
[89285, 90939, False]
[92862, 114496, True]
[114496, 122124, False]
[122191, 149596, False]
[150018, 177119, False]
[178317, 192037, False]
[193015, 205385, True]
Nous avons maintenant les données de diarisation dans une liste. Les deux premiers numéros sont l'heure de début et de fin du segment des haut-parleurs en millisecondes. Le troisième numéro est un booléen qui nous dit si le haut-parleur est lex ou non.
Ensuite, nous joignons les segments audio en fonction de la diarisation, avec un espaceur comme délimiteur.
from pydub import AudioSegment
import re
sounds = spacer
segments = []
dz = open ( 'diarization.txt' ). read (). splitlines ()
for l in dz :
start , end = tuple ( re . findall ( '[0-9]+:[0-9]+:[0-9]+.[0-9]+' , string = l ))
start = int ( millisec ( start )) #milliseconds
end = int ( millisec ( end )) #milliseconds
segments . append ( len ( sounds ))
sounds = sounds . append ( audio [ start : end ], crossfade = 0 )
sounds = sounds . append ( spacer , crossfade = 0 )
sounds . export ( "dz.wav" , format = "wav" ) #Exports to a wav file in the current path.
print ( segments [: 8 ])
[2000, 38779, 54042, 94382, 98036, 121670, 131297, 160702]
Ensuite, nous utiliserons Whisper pour transcrire les différents segments du fichier audio. IMPORTANT: Il existe un conflit de version avec pyannote.audio résultant en une erreur. Notre solution de contournement est d'exécuter d'abord Pyannote, puis de chuchoter. Vous pouvez ignorer l'erreur en toute sécurité.
Installation de chuchotements AI ouverts.
! pip install git+https://github.com/openai/whisper.git
Exécution d'ouvrir un chuchotement AI sur le fichier audio préparé. Il écrit la transcription dans un fichier. Vous pouvez ajuster la taille du modèle à vos besoins. Vous pouvez trouver tous les modèles sur la carte de modèle sur GitHub.
! whisper dz.wav --language en --model base
[00:00.000 --> 00:04.720] The following is a conversation with Yann LeCun,
[00:04.720 --> 00:06.560] his second time on the podcast.
[00:06.560 --> 00:11.160] He is the chief AI scientist at Meta, formerly Facebook,
[00:11.160 --> 00:15.040] professor at NYU, touring award winner,
[00:15.040 --> 00:17.600] one of the seminal figures in the history
[00:17.600 --> 00:20.460] of machine learning and artificial intelligence,
...
Afin de travailler avec des fichiers .vtt, nous devons installer la bibliothèque WebVTT-PY.
! pip install -U webvtt-py
Jetons un coup d'œil aux données:
import webvtt
captions = [[( int )( millisec ( caption . start )), ( int )( millisec ( caption . end )), caption . text ] for caption in webvtt . read ( 'dz.wav.vtt' )]
print ( * captions [: 8 ], sep = ' n ' )
[0, 4720, 'The following is a conversation with Yann LeCun,']
[4720, 6560, 'his second time on the podcast.']
[6560, 11160, 'He is the chief AI scientist at Meta, formerly Facebook,']
[11160, 15040, 'professor at NYU, touring award winner,']
[15040, 17600, 'one of the seminal figures in the history']
[17600, 20460, 'of machine learning and artificial intelligence,']
[20460, 23940, 'and someone who is brilliant and opinionated']
[23940, 25400, 'in the best kind of way,']
...
Ensuite, nous correspondrons à chaque ligne de transcripture à certaines diarinations et afficherons tout en générant un fichier HTML. Pour obtenir le calendrier correct, nous devons nous occuper des pièces de l'audio d'origine qui n'étaient pas dans le segment de la diarisation. Nous ajoutons un nouveau div pour chaque segment de notre audio.
# we need this fore our HTML file (basicly just some styling)
preS = '<!DOCTYPE html>n<html lang="en">n <head>n <meta charset="UTF-8">n <meta name="viewport" content="width=device-width, initial-scale=1.0">n <meta http-equiv="X-UA-Compatible" content="ie=edge">n <title>Lexicap</title>n <style>n body {n font-family: sans-serif;n font-size: 18px;n color: #111;n padding: 0 0 1em 0;n }n .l {n color: #050;n }n .s {n display: inline-block;n }n .e {n display: inline-block;n }n .t {n display: inline-block;n }n #player {nttposition: sticky;ntttop: 20px;nttfloat: right;nt}n </style>n </head>n <body>n <h2>Yann LeCun: Dark Matter of Intelligence and Self-Supervised Learning | Lex Fridman Podcast #258</h2>n <div id="player"></div>n <script>n var tag = document.createElement('script');n tag.src = "https://www.youtube.com/iframe_api";n var firstScriptTag = document.getElementsByTagName('script')[0];n firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);n var player;n function onYouTubeIframeAPIReady() {n player = new YT.Player('player', {n height: '210',n width: '340',n videoId: 'SGzMElJ11Cc',n });n }n function setCurrentTime(timepoint) {n player.seekTo(timepoint);n player.playVideo();n }n </script><br>n'
postS = 't</body>n</html>'
from datetime import timedelta
html = list(preS)
for i in range(len(segments)):
idx = 0
for idx in range(len(captions)):
if captions[idx][0] >= (segments[i] - spacermilli):
break;
while (idx < (len(captions))) and ((i == len(segments) - 1) or (captions[idx][1] < segments[i+1])):
c = captions[idx]
start = dzList[i][0] + (c[0] -segments[i])
if start < 0:
start = 0
idx += 1
start = start / 1000.0
startStr = '{0:02d}:{1:02d}:{2:02.2f}'.format((int)(start // 3600),
(int)(start % 3600 // 60),
start % 60)
html.append('ttt<div class="c">n')
html.append(f'tttt<a class="l" href="#{startStr}" id="{startStr}">link</a> |n')
html.append(f'tttt<div class="s"><a href="javascript:void(0);" onclick=setCurrentTime({int(start)})>{startStr}</a></div>n')
html.append(f'tttt<div class="t">{"[Lex]" if dzList[i][2] else "[Yann]"} {c[2]}</div>n')
html.append('ttt</div>nn')
html.append(postS)
s = "".join(html)
with open("lexicap.html", "w") as text_file:
text_file.write(s)
print(s)
Sur Lablab Discord, nous discutons de ce repo et de nombreux autres sujets liés à l'intelligence artificielle! Découvrez à venir l'événement de hackathons d'intelligence artificielle