Cómo usar OpenAis Whisper para transcribir y diarizar archivos de audio
Whisper es un sistema de reconocimiento de voz de última generación de OpenAI que ha sido capacitado en 680,000 horas de datos supervisados multilingües y multitarea recopilados de la web. Este gran y diverso conjunto de datos conduce a una mejor robustez a los acentos, el ruido de fondo y al lenguaje técnico. Además, permite la transcripción en múltiples idiomas, así como la traducción de esos idiomas al inglés. Openai lanzó los modelos y el código para servir como base para construir aplicaciones útiles que aprovechen el reconocimiento de voz.
Sin embargo, un gran inconveniente de Whisper es que no puede decirte quién está hablando en una conversación. Ese es un problema al analizar las conversaciones. Aquí es donde entra la diarización. La diarización es el proceso de identificación de quién habla en una conversación.
En este tutorial aprenderá cómo identificar los altavoces y luego combinarlos con las transcripciones de Whisper. Usaremos pyannote-audio para lograr esto. ¡Comencemos!
Primero, necesitamos preparar el archivo de audio. Usaremos los primeros 20 minutos del podcast Lex Fridmans con la descarga de Yann. Para descargar el video y extraer el audio, usaremos el paquete yt-dlp .
! pip install -U yt-dlpTambién necesitaremos FFMPEG instalado
! wget -O - -q https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz | xz -qdc | tar -xAhora podemos hacer la descarga real y la extracción de audio a través de la línea de comando.
! yt-dlp -xv --ffmpeg-location ffmpeg-master-latest-linux64-gpl/bin --audio-format wav -o download.wav -- https://youtu.be/SGzMElJ11Cc Ahora tenemos el archivo download.wav en nuestro directorio de trabajo. Cortemos los primeros 20 minutos del audio. Podemos usar el paquete Pydub para esto con solo unas pocas líneas de código.
! 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 es ahora los primeros 20 minutos del archivo de audio.
pyannote.audio es un kit de herramientas de código abierto escrito en Python para la diarización del altavoz. Basado en el marco de aprendizaje automático de Pytorch, proporciona un conjunto de bloques de construcción neuronales de extremo a extremo capacitables que se pueden combinar y optimizar conjuntamente para construir tuberías de diarios de altavoces. pyannote.audio también viene con modelos y tuberías previos a la aparición que cubren una amplia gama de dominios para la detección de actividades de voz, segmentación de altavoces, detección de voz superpuesta, un altavoz que alcanza el rendimiento de última generación para la mayoría de ellos.
Instalar pyannote y ejecutarlo en el audio de video para generar las diarizaciones.
! 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 ))Imprimamos esto para ver cómo se ve.
print(*list(dz.itertracks(yield_label = True))[:10], sep="n")
La salida:
(<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')
Esto ya se ve bastante bien, pero limpiemos un poco los datos:
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]
Ahora tenemos los datos de diarización en una lista. Los dos primeros números son el tiempo de inicio y finalización del segmento de altavoces en milisegundos. El tercer número es un booleano que nos dice si el altavoz es lex o no.
A continuación, adjuntaremos los segementos de audio de acuerdo con la diarización, con un espaciador como delimitador.
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]A continuación, utilizaremos Whisper para transcribir los diferentes segmentos del archivo de audio. IMPORTANTE: Hay un conflicto de versión con pyannote.audio que resulta en un error. Nuestra solución es primero ejecutar Pyannote y luego susurrar. Puede ignorar con seguridad el error.
Instalación de Open AI Whisper.
! pip install git+https://github.com/openai/whisper.git Ejecutando Open AI Whisper en el archivo de audio preparado. Escribe la transcripción en un archivo. Puede ajustar el tamaño del modelo a sus necesidades. Puede encontrar todos los modelos en la tarjeta modelo en 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,
...
Para trabajar con archivos .vtt, necesitamos instalar la biblioteca WebVTT-PY.
! pip install -U webvtt-pyEchemos un vistazo a los datos:
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,']
...
A continuación, coincidiremos con cada línea de transcripción con algunas diarizaciones y mostraremos todo generando un archivo HTML. Para obtener el momento correcto, debemos cuidar las piezas en el audio original que no estaban en segmento de diarización. Agregamos un nuevo DIV para cada segmento en nuestro 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)
¡En Lablab Discord, discutimos este repositorio y muchos otros temas relacionados con la inteligencia artificial! Evento de Hackathons de inteligencia artificial del próximo.