Spaces:
Sleeping
Sleeping
Commit
•
67002a7
1
Parent(s):
296ec00
Upload 6 files
Browse files- Dockerfile +38 -0
- app.py +176 -0
- requirements.txt +13 -0
- results/Captura de pantalla 2024-02-14 230343.png +0 -0
- results/Que nadie sepa mi sufrir - Julio Jaramillo (Letra).mp3 +0 -0
- results/response_1707969561662.json +4 -0
Dockerfile
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# usar imagen base de Python 3.10
|
2 |
+
FROM python:3.10
|
3 |
+
|
4 |
+
# instalar dependencias de Python
|
5 |
+
RUN pip install pandas numpy nltk moviepy pydub transformers pydantic pytest whisperx fastapi uvicorn
|
6 |
+
|
7 |
+
# instalar paquete especial whisperx desde GitHub
|
8 |
+
RUN pip install git+https://github.com/m-bain/whisperx.git
|
9 |
+
|
10 |
+
# instalar paquetes de PyTorch para CUDA 12.1
|
11 |
+
RUN pip install torch==1.10.0+cu121 torchvision==0.11.1+cu121 torchaudio==0.10.0+cu121 -f https://download.pytorch.org/whl/torch_stable.html
|
12 |
+
|
13 |
+
# instalar utilidades básicas
|
14 |
+
RUN apt-get update && apt-get install -y \
|
15 |
+
curl \
|
16 |
+
git \
|
17 |
+
unzip \
|
18 |
+
wget \
|
19 |
+
zip \
|
20 |
+
git-lfs \
|
21 |
+
ffmpeg
|
22 |
+
|
23 |
+
# copiar archivos de la aplicación
|
24 |
+
COPY . /app
|
25 |
+
# copiar todos los archivos de la aplicación
|
26 |
+
|
27 |
+
COPY . .
|
28 |
+
# establecer directorio de trabajo
|
29 |
+
WORKDIR /app
|
30 |
+
|
31 |
+
# instalar dependencias de Python desde requirements.txt
|
32 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
33 |
+
|
34 |
+
# cambiar permisos de archivos necesarios
|
35 |
+
RUN chmod -R 777 /app
|
36 |
+
|
37 |
+
# definir comando predeterminado para iniciar la aplicación
|
38 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pyannote.audio import Pipeline
|
2 |
+
from pydub import AudioSegment
|
3 |
+
import whisperx
|
4 |
+
import torch
|
5 |
+
import json
|
6 |
+
import os
|
7 |
+
from fastapi import FastAPI, File, UploadFile
|
8 |
+
from fastapi.middleware.cors import CORSMiddleware
|
9 |
+
from pydub.utils import mediainfo
|
10 |
+
import subprocess
|
11 |
+
from dotenv import load_dotenv
|
12 |
+
|
13 |
+
# cargar el archivo .env
|
14 |
+
load_dotenv()
|
15 |
+
|
16 |
+
# Inicializar la aplicación
|
17 |
+
app = FastAPI()
|
18 |
+
|
19 |
+
# Configurar CORS
|
20 |
+
app.add_middleware(
|
21 |
+
CORSMiddleware,
|
22 |
+
allow_origins=["*"],
|
23 |
+
allow_credentials=True,
|
24 |
+
allow_methods=["*"],
|
25 |
+
allow_headers=["*"],
|
26 |
+
)
|
27 |
+
|
28 |
+
|
29 |
+
# Carga de token y verificación de disponibilidad de GPU
|
30 |
+
hf_token = os.getenv("HF_TOKEN")
|
31 |
+
|
32 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
33 |
+
|
34 |
+
# Tipo de cómputo #"float32" o "float16" si se desea utilizar la aceleración de la GPU o "float32" si se desea utilizar la CPU.
|
35 |
+
compute_type = "float32" if device == "cuda" else "float32"
|
36 |
+
|
37 |
+
# Transcripción
|
38 |
+
print("Descargando el modelo...")
|
39 |
+
model = whisperx.load_model(
|
40 |
+
"large-v2", device, language="es", compute_type=compute_type
|
41 |
+
)
|
42 |
+
print("¡Modelo descargado con éxito!")
|
43 |
+
|
44 |
+
|
45 |
+
def transcribe_wav(audio):
|
46 |
+
"""
|
47 |
+
Transcribe un archivo de audio WAV y devuelve el texto transrito.
|
48 |
+
"""
|
49 |
+
resultado = model.transcribe(audio, language="es", batch_size=28)
|
50 |
+
return "\n".join([segment["text"] for segment in resultado["segments"]])
|
51 |
+
|
52 |
+
|
53 |
+
def segment_and_transcribe_audio(audio_entrada: str) -> dict:
|
54 |
+
"""
|
55 |
+
Segmenta y transcribe el archivo de audio de entrada.
|
56 |
+
|
57 |
+
Parameters:
|
58 |
+
:param audio_entrada: str: Ruta al archivo de audio de entrada.
|
59 |
+
|
60 |
+
Returns:
|
61 |
+
dict: Diccionario que contiene las transcripciones para cada segmento de parlante.
|
62 |
+
"""
|
63 |
+
transcriptions = {}
|
64 |
+
|
65 |
+
if not os.path.exists(audio_entrada):
|
66 |
+
raise FileNotFoundError("El archivo de audio no existe.")
|
67 |
+
|
68 |
+
audio_info = mediainfo(audio_entrada)
|
69 |
+
duration_s = audio_info.get("duration")
|
70 |
+
if duration_s is None:
|
71 |
+
raise ValueError("No se pudo obtener la duración del archivo de audio.")
|
72 |
+
|
73 |
+
duration_s = float(duration_s)
|
74 |
+
print(f"Duración del audio: {duration_s} s")
|
75 |
+
if duration_s < 10:
|
76 |
+
raise ValueError(
|
77 |
+
f"La duración del audio es demasiado corta para realizar una transcripción. Duración: {duration_s} s"
|
78 |
+
)
|
79 |
+
|
80 |
+
# Cargar el archivo de audio usando whisperx.load_audio
|
81 |
+
audio = whisperx.load_audio(audio_entrada)
|
82 |
+
|
83 |
+
# Realizar la transcripción completa del audio
|
84 |
+
transcripcion_completa = transcribe_wav(audio)
|
85 |
+
transcriptions["full"] = transcripcion_completa
|
86 |
+
|
87 |
+
# Inicializar el pipeline de diarización de parlantes
|
88 |
+
pipeline = Pipeline.from_pretrained(
|
89 |
+
"pyannote/speaker-diarization-3.1", use_auth_token=hf_token
|
90 |
+
)
|
91 |
+
pipeline.to(torch.device(device))
|
92 |
+
|
93 |
+
# Realizar la diarización de los parlantes en el audio
|
94 |
+
with open(audio_entrada, "rb") as f:
|
95 |
+
diarization = pipeline(f)
|
96 |
+
|
97 |
+
# Escribir los resultados de la diarización en un archivo de texto
|
98 |
+
with open("audio.txt", "w") as lab:
|
99 |
+
diarization.write_lab(lab)
|
100 |
+
|
101 |
+
# Leer los resultados de la diarización desde el archivo de texto
|
102 |
+
with open("audio.txt", "r") as file:
|
103 |
+
lines = file.readlines()
|
104 |
+
|
105 |
+
# Procesar los resultados de la diarización
|
106 |
+
data = []
|
107 |
+
for line in lines:
|
108 |
+
parts = line.strip().split()
|
109 |
+
data.append(
|
110 |
+
{"start": float(parts[0]), "end": float(parts[1]), "speaker": parts[2]}
|
111 |
+
)
|
112 |
+
|
113 |
+
# Guardar los resultados de la diarización en un archivo JSON
|
114 |
+
with open("audio.json", "w") as file:
|
115 |
+
json.dump(data, file, indent=4)
|
116 |
+
|
117 |
+
# Cargar el archivo de audio original usando AudioSegment
|
118 |
+
audio_segment = AudioSegment.from_wav(audio_entrada)
|
119 |
+
|
120 |
+
# Leer los resultados de la diarización desde el archivo JSON
|
121 |
+
with open("audio.json", "r") as file:
|
122 |
+
diarization = json.load(file)
|
123 |
+
|
124 |
+
# Segmentar el audio en base a los resultados de la diarización
|
125 |
+
segments = {}
|
126 |
+
for segment in diarization:
|
127 |
+
speaker = segment["speaker"]
|
128 |
+
if speaker not in segments:
|
129 |
+
segments[speaker] = []
|
130 |
+
start = int(segment["start"] * 1000)
|
131 |
+
end = int(segment["end"] * 1000)
|
132 |
+
segments[speaker].append(audio_segment[start:end])
|
133 |
+
|
134 |
+
# Transcribir cada segmento de parlante y guardar los resultados
|
135 |
+
for speaker, segs in segments.items():
|
136 |
+
result = sum(segs, AudioSegment.empty())
|
137 |
+
result.export(f"{speaker}.wav", format="wav")
|
138 |
+
transcriptions[speaker] = transcribe_wav(f"{speaker}.wav")
|
139 |
+
|
140 |
+
# Eliminar los archivos temporales creados
|
141 |
+
os.remove("audio.txt")
|
142 |
+
os.remove(audio_entrada)
|
143 |
+
os.remove("audio.json")
|
144 |
+
for speaker in segments.keys():
|
145 |
+
os.remove(f"{speaker}.wav")
|
146 |
+
|
147 |
+
return transcriptions
|
148 |
+
|
149 |
+
|
150 |
+
@app.get("/")
|
151 |
+
def read_root():
|
152 |
+
return {"message": "¡Bienvenido a la API de transcripción de audio!"}
|
153 |
+
|
154 |
+
|
155 |
+
@app.post("/transcribe")
|
156 |
+
def transcribe_audio(audio: UploadFile = File(...)):
|
157 |
+
"""
|
158 |
+
Transcribe un archivo de audio y devuelve el texto transcrito.
|
159 |
+
|
160 |
+
Parameters:
|
161 |
+
:param audio: UploadFile: Archivo de audio a transcribir.
|
162 |
+
|
163 |
+
Returns:
|
164 |
+
JSONResponse: Respuesta JSON que contiene el texto transcrito.
|
165 |
+
"""
|
166 |
+
# Guardar el archivo de audio en wav con la libreria AudioSegment
|
167 |
+
audio_segment = AudioSegment.from_file(audio.file)
|
168 |
+
audio_segment.export("audio.wav", format="wav")
|
169 |
+
return segment_and_transcribe_audio("audio.wav")
|
170 |
+
|
171 |
+
|
172 |
+
# if __name__ == "__main__":
|
173 |
+
|
174 |
+
# subprocess.run(
|
175 |
+
# ["uvicorn", "app:app", "--host", "localhost", "--port", "7860", "--reload"]
|
176 |
+
# )
|
requirements.txt
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pandas
|
2 |
+
numpy
|
3 |
+
nltk
|
4 |
+
moviepy
|
5 |
+
pydub
|
6 |
+
transformers
|
7 |
+
pydantic
|
8 |
+
pytest
|
9 |
+
whisperx
|
10 |
+
fastapi
|
11 |
+
uvicorn
|
12 |
+
python-dotenv
|
13 |
+
python-multipart
|
results/Captura de pantalla 2024-02-14 230343.png
ADDED
![]() |
results/Que nadie sepa mi sufrir - Julio Jaramillo (Letra).mp3
ADDED
Binary file (979 kB). View file
|
|
results/response_1707969561662.json
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"full": " No te asombres si te digo lo que fuiste Una ingrata con mi pobre corazón Porque el fuego de tus lindos ojos negros Alumbraron el camino de otro amor Porque el fuego de tus lindos ojos negros Alumbraron el camino de otro amor\n amor de mis amores reina mía que me hiciste que no puedo conformarme sin poderte contemplar ya que pagaste mal mi cariño tan sincero lo que conseguirás que no te nombre nunca más amor de mis amores si dejaste de quererme no hay cuidado que la gente de eso no se enterará\n ¿Qué gano con decir que una mujer cambió mi suerte? Se burlarán de mí, que nadie sepa mi sufrir\n Y pensar que te adoraba ciegamente, que a tu lado como nunca me sentí. Y por esas cosas raras de la vida, sin el beso de tu boca yo me\n Y por esas cosas raras de la vida, sin el beso de tu boca yo me vi. Amor de mis amores, reina mía que me hiciste, que no puedo conformarme sin poderte contemplar. Ya que pagaste mal mi cariño tan sincero, lo que conseguirás que no te nombre nunca más.\n amor de mis amores si dejaste de quererme no hay cuidado que la gente de eso no se enterara que gano con decir que una mujer cambia mi suerte se burlaran de mi que nadie sepa mi sufrir",
|
3 |
+
"SPEAKER_00": " No te asombres si te digo lo que fuiste Una ingrata con mi pobre corazón Porque el fuego de tus lindos ojos negros Alumbraron el camino de otro amor Porque el fuego de tus lindos ojos negros Alumbraron el camino de otro amor\n amor de mis amores reina mía que me hiciste que no puedo conformarme sin poderte contemplar ya que pagaste mal mi cariño tan sincero lo que conseguirás que no te nombre nunca más\n amor de mis amores si dejaste de quererme no hay cuidado que la gente de eso no se enterara que gano con decir que una mujer cambio mi suerte se burlaran de mi que nadie sepa mi sufrir y pensar que te adoraba ciegamente que a tu lado como nunca me sentí y por esas cosas raras de la vida sin el beso de tu voz\n y por esas cosas raras de la vida sin el beso de tu boca yo me vi amor de mis amores reina mía que me hiciste que no puedo conformarme sin poderte contemplar ya que pagaste mal mi cariño tan sincero lo que conseguirás que no te nombre nunca más\n amor de mis amores si dejaste de quererme no hay cuidado que la gente de eso no se enterara que gano con decir que una mujer cambia mi suerte se burlaran de mi que nadie sepa mi sufrir"
|
4 |
+
}
|