whisper live

This commit is contained in:
2025-07-15 18:54:01 +00:00
parent d4345eef8b
commit 8386b685d6
57 changed files with 2772 additions and 0 deletions

View File

@ -0,0 +1,134 @@
defmodule WhisperLiveWeb.AudioChannel do
use Phoenix.Channel
require Logger
alias WhisperLive.AudioBuffer
def join("audio:lobby", _payload, socket) do
ref = socket_id(socket)
Logger.info("Cliente conectado al canal audio:lobby")
{:ok, _} = AudioBuffer.start_link(ref)
{:ok, _} = WhisperLive.Transcriber.start_link(ref)
{:ok, socket}
end
def handle_in("audio_chunk", %{"data" => base64_audio, "sample_rate" => sample_rate}, socket) do
# 1. Decodificas el audio base64
{:ok, bin} = Base.decode64(base64_audio)
# 2. Guardas o procesas el chunk de audio
# Podrías escribirlo en un archivo temporal para enviar a Whisper
tmpfile = tmp_path("chunk_#{socket.assigns.ref}")
:ok = File.write!(tmpfile, encode_wav(bin, sample_rate))
# 3. Llamas a la transcripción del chunk (podría ser sync o async)
case send_to_whisper(tmpfile) do
{:ok, transcription} ->
# 4. Envías el texto parcial por PubSub o Push a LiveView/cliente
Phoenix.PubSub.broadcast(YourApp.PubSub, "transcription:#{socket.assigns.ref}", {:transcription, transcription})
{:error, reason} ->
Logger.error("Error en transcripción parcial: #{inspect(reason)}")
end
File.rm(tmpfile)
{:noreply, socket}
end
def handle_in("stop_audio", _payload, socket) do
Logger.info("🛑 Grabación detenida por cliente")
ref = socket_id(socket)
case AudioBuffer.get_all(ref) do
[{rate, _} | _] = chunks ->
merged = chunks |> Enum.map(fn {_, bin} -> bin end) |> IO.iodata_to_binary()
filename = "recordings/recording_#{System.system_time(:millisecond)}.wav"
File.mkdir_p!("recordings")
File.write!(filename, encode_wav(merged, rate))
Logger.info("💾 Audio guardado en #{filename}")
# 🔁 Transcribir automáticamente
case send_to_whisper(filename) do
{:ok, response} ->
Logger.info("📝 Transcripción recibida: #{response}")
{:error, reason} ->
Logger.error("❌ Error al transcribir: #{inspect(reason)}")
end
_ ->
Logger.warning("⚠️ No se recibieron chunks de audio")
end
AudioBuffer.stop(ref)
WhisperLive.Transcriber.stop(ref)
{:noreply, socket}
end
defp socket_id(socket), do: socket.transport_pid |> :erlang.pid_to_list() |> List.to_string()
defp encode_wav(data, sample_rate) do
num_channels = 1
bits_per_sample = 16
byte_rate = sample_rate * num_channels * div(bits_per_sample, 8)
block_align = div(bits_per_sample * num_channels, 8)
data_size = byte_size(data)
riff_size = 36 + data_size
<<
"RIFF",
<<riff_size::little-size(32)>>,
"WAVE",
"fmt ",
<<16::little-size(32)>>,
<<1::little-size(16)>>,
<<num_channels::little-size(16)>>,
<<sample_rate::little-size(32)>>,
<<byte_rate::little-size(32)>>,
<<block_align::little-size(16)>>,
<<bits_per_sample::little-size(16)>>,
"data",
<<data_size::little-size(32)>>
>> <> data
end
defp send_to_whisper(filepath) do
url = "http://localhost:4000/infer"
{:ok, file_bin} = File.read(filepath)
filename = Path.basename(filepath)
headers = [
{'Content-Type', 'multipart/form-data; boundary=----ElixirBoundary'}
]
body =
[
"------ElixirBoundary\r\n",
"Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n",
"Content-Type: audio/wav\r\n\r\n",
file_bin,
"\r\n------ElixirBoundary--\r\n"
]
:httpc.request(:post, {url, headers, 'multipart/form-data; boundary=----ElixirBoundary', body}, [], [])
|> case do
{:ok, {{_, 200, _}, _headers, body}} ->
{:ok, to_string(body)}
{:ok, {{_, status, _}, _, body}} ->
{:error, {:http_error, status, to_string(body)}}
error ->
{:error, error}
end
end
defp tmp_path(prefix) do
unique = :erlang.unique_integer([:positive]) |> Integer.to_string()
filename = prefix <> "_" <> unique <> ".wav"
Path.join(System.tmp_dir!(), filename)
end
end

View File

@ -0,0 +1,14 @@
defmodule WhisperLiveWeb.UserSocket do
use Phoenix.Socket
## Canales que acepta este socket:
channel "audio:*", WhisperLiveWeb.AudioChannel
transport :websocket, Phoenix.Transports.WebSocket
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
def id(_socket), do: nil
end