mirror of
https://github.com/openai/whisper.git
synced 2025-11-24 14:35:57 +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
|
||||
tiktoken
|
||||
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