mirror of
https://github.com/openai/whisper.git
synced 2025-11-23 22:15:58 +00:00
Merge 785dde60787953a9abe3a23fc2fc6fc1f6f20d0e into c0d2f624c09dc18e709e37c2ad90c039a4eb72a2
This commit is contained in:
commit
3f2e652540
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"githubPullRequests.ignoredPullRequestBranches": [
|
||||||
|
"main"
|
||||||
|
]
|
||||||
|
}
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Dockerfile para Whisper + Streamlit + FastAPI
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Instalar dependencias del sistema
|
||||||
|
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copiar archivos del proyecto
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
# Instalar dependencias de Python
|
||||||
|
RUN pip install --upgrade pip \
|
||||||
|
&& pip install --no-cache-dir -r requirements.txt \
|
||||||
|
&& pip install --no-cache-dir streamlit fastapi uvicorn python-multipart
|
||||||
|
|
||||||
|
# Puerto para Streamlit y FastAPI
|
||||||
|
EXPOSE 8501 8000
|
||||||
|
|
||||||
|
# Comando por defecto: arranca ambos servicios
|
||||||
|
CMD streamlit run app.py & uvicorn api:app --host 0.0.0.0 --port 8000
|
||||||
47
api.py
Normal file
47
api.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from fastapi import FastAPI, File, UploadFile, Form
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from whisper.transcribe import transcribe
|
||||||
|
from whisper import load_model
|
||||||
|
from whisper.tokenizer import LANGUAGES
|
||||||
|
import torch
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Cargar modelo solo una vez al iniciar el contenedor
|
||||||
|
MODEL_NAME = os.environ.get("WHISPER_MODEL", "turbo")
|
||||||
|
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
model = load_model(MODEL_NAME, device=DEVICE)
|
||||||
|
|
||||||
|
@app.post("/transcribe")
|
||||||
|
def transcribe_audio(
|
||||||
|
file: UploadFile = File(...),
|
||||||
|
task: str = Form("transcribe"),
|
||||||
|
language: str = Form(None),
|
||||||
|
temperature: float = Form(0.0),
|
||||||
|
word_timestamps: bool = Form(False)
|
||||||
|
):
|
||||||
|
temp_path = f"temp_{file.filename}"
|
||||||
|
with open(temp_path, "wb") as buffer:
|
||||||
|
shutil.copyfileobj(file.file, buffer)
|
||||||
|
try:
|
||||||
|
result = transcribe(
|
||||||
|
model,
|
||||||
|
temp_path,
|
||||||
|
task=task,
|
||||||
|
language=language,
|
||||||
|
temperature=temperature,
|
||||||
|
word_timestamps=word_timestamps,
|
||||||
|
verbose=False
|
||||||
|
)
|
||||||
|
os.remove(temp_path)
|
||||||
|
return JSONResponse({
|
||||||
|
"text": result["text"],
|
||||||
|
"segments": result.get("segments", []),
|
||||||
|
"language": result.get("language", "")
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
if os.path.exists(temp_path):
|
||||||
|
os.remove(temp_path)
|
||||||
|
return JSONResponse({"error": str(e)}, status_code=500)
|
||||||
146
app.py
Normal file
146
app.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import torch
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
from whisper.transcribe import transcribe
|
||||||
|
from whisper import load_model
|
||||||
|
from whisper.tokenizer import LANGUAGES, TO_LANGUAGE_CODE
|
||||||
|
|
||||||
|
st.set_page_config(page_title="Whisper Transcriber", layout="wide")
|
||||||
|
st.title("Whisper Transcriber - Interfaz Streamlit")
|
||||||
|
|
||||||
|
# Sidebar options
|
||||||
|
st.sidebar.header("Opciones de transcripción")
|
||||||
|
|
||||||
|
# Idiomas para el selector (nombre completo)
|
||||||
|
language_names = ["(autodetect)"] + [LANGUAGES[k].title() for k in sorted(LANGUAGES.keys())]
|
||||||
|
language_key_map = dict(zip([LANGUAGES[k].title() for k in sorted(LANGUAGES.keys())], sorted(LANGUAGES.keys())))
|
||||||
|
|
||||||
|
# Modelo
|
||||||
|
model_name = st.sidebar.selectbox(
|
||||||
|
"Modelo",
|
||||||
|
["tiny", "base", "small", "medium", "large", "turbo"],
|
||||||
|
index=5,
|
||||||
|
help="Selecciona el modelo Whisper a utilizar. Modelos más grandes suelen ser más precisos pero más lentos."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Tarea
|
||||||
|
task = st.sidebar.selectbox(
|
||||||
|
"Tarea",
|
||||||
|
["transcribe", "translate"],
|
||||||
|
index=0,
|
||||||
|
help="'transcribe' transcribe el audio en el mismo idioma. 'translate' traduce el audio al inglés."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Idioma
|
||||||
|
language_display = st.sidebar.selectbox(
|
||||||
|
"Idioma (deja vacío para autodetectar)",
|
||||||
|
language_names,
|
||||||
|
index=0,
|
||||||
|
help="Selecciona el idioma hablado en el audio. Si eliges '(autodetect)', el modelo intentará detectarlo automáticamente."
|
||||||
|
)
|
||||||
|
if language_display == "(autodetect)":
|
||||||
|
language = None
|
||||||
|
else:
|
||||||
|
language = language_key_map[language_display]
|
||||||
|
|
||||||
|
# Temperatura
|
||||||
|
st.sidebar.markdown("<span style='font-size: 12px; color: gray;'>La temperatura controla la aleatoriedad de la transcripción. Valores bajos (0) hacen la salida más determinista, valores altos pueden generar resultados más creativos pero menos precisos.</span>", unsafe_allow_html=True)
|
||||||
|
temperature = st.sidebar.slider(
|
||||||
|
"Temperatura",
|
||||||
|
0.0, 1.0, 0.0, 0.1,
|
||||||
|
help="Controla la aleatoriedad de la transcripción. 0 es determinista, valores más altos pueden dar resultados más variados."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Word timestamps
|
||||||
|
word_timestamps = st.sidebar.checkbox(
|
||||||
|
"Timestamps por palabra",
|
||||||
|
value=False,
|
||||||
|
help="Si está activado, se mostrarán marcas de tiempo para cada palabra."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Selector de destino de salida
|
||||||
|
output_mode = st.sidebar.radio(
|
||||||
|
"¿Dónde quieres guardar la salida?",
|
||||||
|
("En mi equipo (local)", "En la ruta del contenedor (/app/output)"),
|
||||||
|
help="Elige si quieres guardar los archivos en tu equipo local o en la carpeta compartida del contenedor."
|
||||||
|
)
|
||||||
|
|
||||||
|
if output_mode == "En mi equipo (local)":
|
||||||
|
st.sidebar.markdown("<small>Selecciona cualquier archivo de la carpeta donde quieras guardar la salida. Se usará la ruta de esa carpeta.</small>", unsafe_allow_html=True)
|
||||||
|
selected_file = st.sidebar.file_uploader(
|
||||||
|
"Selecciona un archivo de la carpeta destino (solo para elegir la carpeta)",
|
||||||
|
type=None,
|
||||||
|
accept_multiple_files=False,
|
||||||
|
key="folder_selector"
|
||||||
|
)
|
||||||
|
if selected_file is not None and hasattr(selected_file, 'name'):
|
||||||
|
import pathlib
|
||||||
|
# En local, file_uploader solo da el nombre, no la ruta absoluta, así que usamos cwd
|
||||||
|
selected_folder = os.getcwd()
|
||||||
|
else:
|
||||||
|
selected_folder = os.getcwd()
|
||||||
|
default_dir = selected_folder
|
||||||
|
else:
|
||||||
|
default_dir = "/app/output"
|
||||||
|
|
||||||
|
output_dir = st.sidebar.text_input(
|
||||||
|
"Carpeta de salida para los resultados",
|
||||||
|
value=default_dir,
|
||||||
|
help="Ruta donde se guardarán los archivos de salida generados por la transcripción. Puedes personalizarla si lo deseas."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Archivo de audio
|
||||||
|
st.header("Selecciona archivos de audio para transcribir")
|
||||||
|
audio_files = st.file_uploader(
|
||||||
|
"Selecciona uno o varios archivos de audio",
|
||||||
|
type=["wav", "mp3", "m4a", "flac", "ogg"],
|
||||||
|
accept_multiple_files=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("Transcribir") and audio_files:
|
||||||
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
st.info(f"Cargando modelo '{model_name}' en {device}...")
|
||||||
|
model = load_model(model_name, device=device)
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
for idx, audio_file in enumerate(audio_files):
|
||||||
|
st.write(f"Procesando archivo: {audio_file.name}")
|
||||||
|
with st.spinner(f"Transcribiendo {audio_file.name}..."):
|
||||||
|
# Guardar archivo temporalmente
|
||||||
|
temp_path = f"temp_{audio_file.name}"
|
||||||
|
with open(temp_path, "wb") as f:
|
||||||
|
f.write(audio_file.read())
|
||||||
|
# Barra de progreso
|
||||||
|
progress = st.progress(0)
|
||||||
|
def progress_callback(p):
|
||||||
|
progress.progress(int(p * 100))
|
||||||
|
# Ejecutar transcripción
|
||||||
|
result = transcribe(
|
||||||
|
model,
|
||||||
|
temp_path,
|
||||||
|
task=task,
|
||||||
|
language=language,
|
||||||
|
temperature=temperature,
|
||||||
|
word_timestamps=word_timestamps,
|
||||||
|
verbose=True
|
||||||
|
)
|
||||||
|
# Guardar resultado en archivo de texto en la carpeta de salida
|
||||||
|
output_txt = os.path.join(output_dir, f"{os.path.splitext(audio_file.name)[0]}.txt")
|
||||||
|
with open(output_txt, "w", encoding="utf-8") as f:
|
||||||
|
f.write(result["text"])
|
||||||
|
# Mostrar resultado
|
||||||
|
st.subheader(f"Transcripción de {audio_file.name}")
|
||||||
|
st.text_area("Texto transcrito", result["text"], height=200)
|
||||||
|
st.write("Detalles:", result)
|
||||||
|
# Botón de descarga siempre disponible
|
||||||
|
st.download_button(
|
||||||
|
label="Descargar transcripción",
|
||||||
|
data=result["text"],
|
||||||
|
file_name=f"{os.path.splitext(audio_file.name)[0]}.txt",
|
||||||
|
mime="text/plain"
|
||||||
|
)
|
||||||
|
os.remove(temp_path)
|
||||||
|
progress.progress(100)
|
||||||
|
st.success("¡Transcripción completada!")
|
||||||
|
else:
|
||||||
|
st.info("Selecciona archivos y pulsa 'Transcribir'.")
|
||||||
13
compose.yaml
Normal file
13
compose.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
whisper:
|
||||||
|
build: .
|
||||||
|
container_name: whisper-app
|
||||||
|
ports:
|
||||||
|
- "8501:8501"
|
||||||
|
- "8000:8000"
|
||||||
|
environment:
|
||||||
|
- WHISPER_MODEL=turbo
|
||||||
|
volumes:
|
||||||
|
- /home/usuario/whisper_output:/app/output
|
||||||
|
restart: unless-stopped
|
||||||
@ -5,3 +5,6 @@ tqdm
|
|||||||
more-itertools
|
more-itertools
|
||||||
tiktoken
|
tiktoken
|
||||||
triton>=2.0.0;platform_machine=="x86_64" and sys_platform=="linux" or sys_platform=="linux2"
|
triton>=2.0.0;platform_machine=="x86_64" and sys_platform=="linux" or sys_platform=="linux2"
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
streamlit
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user