From 2e880ca55254b1a60eac69b65da2c9d1dc38fc7d Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Tue, 2 Jun 2026 21:50:57 +0200 Subject: [PATCH] main: use spooled temp files where possible, instead of "mic.wav" --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/main.rs | 41 +++++++++++++++++++++++++++++++++++------ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3f8c08..8f15fe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -949,6 +949,7 @@ dependencies = [ "json-ld", "oximedia-metering", "ratatui", + "rc-writer", "rdf-types", "reqwest 0.13.4", "resampler", @@ -3186,6 +3187,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rc-writer" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8049c74229f22d8cba889ee1d541b05da9c9668d8fe2011bb922250d0be148" + [[package]] name = "rdf-types" version = "0.22.5" diff --git a/Cargo.toml b/Cargo.toml index 741f9d5..b8170e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ jack = "0.13.5" json-ld = { version = "0.21.4", features = ["reqwest", "serde"] } oximedia-metering = "0.1.7" ratatui = "0.30.0" +rc-writer = "1.1.10" rdf-types = "0.22.5" reqwest = "0.13.4" resampler = "0.5.1" diff --git a/src/main.rs b/src/main.rs index fe735dd..2f3c73d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,12 @@ use serde::{Deserialize, Serialize}; use ratatui::{Frame, layout::{Constraint, Direction, Layout}, widgets::{Block, BorderType, Clear, Gauge, List, ListDirection, ListItem, ListState, Paragraph, Wrap}}; use serde_json::Value; use sqlite::OpenFlags; +use tempfile::SpooledData; use throbber_widgets_tui::{Throbber, ThrobberState}; use crossterm::{event::{self, EventStream, KeyCode, KeyModifiers}}; use tokio::{sync::watch, time::Instant}; use tui_input::{Input, backend::crossterm::EventHandler}; -use std::{io::Read, process::Command}; +use std::{cell::RefCell, io::Read, process::Command, rc::Rc, sync::{Arc, Mutex}}; use futures::{StreamExt, future::FutureExt}; // TODO: We should have a separate 'state.json' file, which remembers jack connections, and the world time for the show to end. Then we only update the 'time remaining' field in the scene and only deal with relative durations inside the scene data @@ -528,6 +529,25 @@ enum AudioRecordRequest { Finish } +struct RcFile(Arc>); + +impl std::io::Write for RcFile { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.0.lock().unwrap().write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.0.lock().unwrap().flush() + } +} + +impl std::io::Seek for RcFile { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + self.0.lock().unwrap().seek(pos) + } +} + + #[tokio::main] async fn main() { color_eyre::install().unwrap(); @@ -582,7 +602,9 @@ async fn main() { let mut meter = VuMeter::new(rate.into(), 1, None); + let spool_size = 16 * (rate as usize) * 10;// 10 seconds of audio let mut writer = None; + let mut outfile = None; let client: Client = Client::default(); @@ -592,16 +614,23 @@ async fn main() { let audio_event = maybe_audio_event.unwrap(); match audio_event { AudioRecordRequest::Start => { - // FIXME: We should switch this over to using spooled tempfiles instead of a named file in the current directory that can easily get clobbered - //const SPOOL_SIZE: usize = 16 * (rate as usize) * 10;// 10 seconds of audio - //let mut outfile = tempfile::spooled_tempfile(SPOOL_SIZE); - writer = Some(hound::WavWriter::create("mic.wav", spec).unwrap()); + outfile = Some(Arc::new(Mutex::new(tempfile::spooled_tempfile(spool_size)))); + writer = Some(hound::WavWriter::new(RcFile(outfile.as_ref().unwrap().clone()), spec).unwrap()); }, AudioRecordRequest::Finish => { writer = None; + let final_audio = outfile.take().unwrap(); + let bytes = match Arc::into_inner(final_audio).unwrap().into_inner().unwrap().into_inner() { + SpooledData::OnDisk(mut file) => { + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).unwrap(); + bytes.into() + }, + SpooledData::InMemory(cursor) => cursor.into_inner().into(), + }; let response = client.audio().transcription().create(CreateTranscriptionRequest { - file: AudioInput { source: InputSource::Path { path: "mic.wav".into() } }, + file: AudioInput { source: InputSource::Bytes { filename: "transcription.wav".into(), bytes } }, model: "gpt-4o-mini-transcribe".into(), ..Default::default() }).await.unwrap();