voice_bird_cli/session/
writer.rs1use std::fs::{File, OpenOptions};
2use std::io::{BufWriter, Write};
3use std::path::Path;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct WrittenSegment {
9 pub t_start_ms: u64,
10 pub t_end_ms: u64,
11 pub text: String,
12}
13
14pub struct SegmentWriter {
15 file: BufWriter<File>,
16}
17
18impl SegmentWriter {
19 pub fn open(path: &Path) -> anyhow::Result<Self> {
20 if let Some(parent) = path.parent() {
21 std::fs::create_dir_all(parent)?;
22 }
23 let file = OpenOptions::new().create(true).append(true).open(path)?;
24 Ok(Self {
25 file: BufWriter::new(file),
26 })
27 }
28
29 pub fn append(&mut self, seg: &WrittenSegment) -> anyhow::Result<()> {
30 serde_json::to_writer(&mut self.file, seg)?;
31 self.file.write_all(b"\n")?;
32 self.file.flush()?;
33 self.file.get_ref().sync_data()?; Ok(())
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41 use std::time::Duration;
42 use tempfile::TempDir;
43
44 fn seg(t0: f64, t1: f64, text: &str) -> WrittenSegment {
45 WrittenSegment {
46 t_start_ms: (t0 * 1000.0) as u64,
47 t_end_ms: (t1 * 1000.0) as u64,
48 text: text.into(),
49 }
50 }
51
52 #[test]
53 fn appends_one_line_per_segment() {
54 let dir = TempDir::new().unwrap();
55 let path = dir.path().join("transcript.jsonl");
56 let mut w = SegmentWriter::open(&path).unwrap();
57 w.append(&seg(0.0, 1.5, "hello")).unwrap();
58 w.append(&seg(1.5, 3.0, "world")).unwrap();
59 drop(w);
60
61 let s = std::fs::read_to_string(&path).unwrap();
62 let lines: Vec<_> = s.lines().collect();
63 assert_eq!(lines.len(), 2);
64 assert!(lines[0].contains("\"text\":\"hello\""));
65 assert!(lines[1].contains("\"text\":\"world\""));
66 }
67
68 #[test]
69 fn survives_reopen_and_appends() {
70 let dir = TempDir::new().unwrap();
71 let path = dir.path().join("transcript.jsonl");
72 {
73 let mut w = SegmentWriter::open(&path).unwrap();
74 w.append(&seg(0.0, 1.0, "first")).unwrap();
75 }
76 {
77 let mut w = SegmentWriter::open(&path).unwrap();
78 w.append(&seg(1.0, 2.0, "second")).unwrap();
79 }
80 let s = std::fs::read_to_string(&path).unwrap();
81 assert_eq!(s.lines().count(), 2);
82 }
83}