omni_dev/transcript/format/
json.rs1use crate::transcript::error::{Result, TranscriptError};
5use crate::transcript::source::Transcript;
6
7pub fn render(transcript: &Transcript) -> Result<String> {
14 serde_json::to_string_pretty(transcript)
15 .map(|s| {
16 let mut s = s;
17 s.push('\n');
18 s
19 })
20 .map_err(|e| TranscriptError::ParseError(format!("json serialisation failed: {e}")))
21}
22
23#[cfg(test)]
24#[allow(clippy::unwrap_used, clippy::expect_used)]
25mod tests {
26 use super::*;
27 use crate::transcript::cue::Cue;
28 use crate::transcript::source::TrackKind;
29
30 fn fixture(cues: Vec<Cue>) -> Transcript {
31 Transcript {
32 source: "mock".into(),
33 locator_id: "abc".into(),
34 language: "en".into(),
35 kind: TrackKind::Manual,
36 cues,
37 }
38 }
39
40 #[test]
41 fn empty_cues_serialises() {
42 let out = render(&fixture(vec![])).unwrap();
43 let value: serde_json::Value = serde_json::from_str(&out).unwrap();
44 assert_eq!(value["cues"], serde_json::json!([]));
45 }
46
47 #[test]
48 fn includes_top_level_metadata() {
49 let out = render(&fixture(vec![])).unwrap();
50 let v: serde_json::Value = serde_json::from_str(&out).unwrap();
51 assert_eq!(v["source"], "mock");
52 assert_eq!(v["locator_id"], "abc");
53 assert_eq!(v["language"], "en");
54 assert_eq!(v["kind"], "manual");
55 }
56
57 #[test]
58 fn cues_have_timing_fields() {
59 let out = render(&fixture(vec![Cue::new(123, 456, "hi")])).unwrap();
60 let v: serde_json::Value = serde_json::from_str(&out).unwrap();
61 let cue = &v["cues"][0];
62 assert_eq!(cue["start_ms"], 123);
63 assert_eq!(cue["end_ms"], 456);
64 assert_eq!(cue["text"], "hi");
65 }
66
67 #[test]
68 fn output_is_pretty_printed() {
69 let out = render(&fixture(vec![Cue::new(0, 1, "x")])).unwrap();
70 assert!(out.contains("\n "));
72 }
73
74 #[test]
75 fn output_ends_with_trailing_newline() {
76 let out = render(&fixture(vec![])).unwrap();
77 assert!(out.ends_with('\n'));
78 }
79
80 #[test]
81 fn round_trips_through_serde() {
82 let original = fixture(vec![
83 Cue::new(0, 1_000, "hello"),
84 Cue::new(1_000, 2_500, "world\nlines"),
85 ]);
86 let out = render(&original).unwrap();
87 let back: Transcript = serde_json::from_str(&out).unwrap();
88 assert_eq!(original, back);
89 }
90
91 #[test]
92 fn track_kind_translated_serialised_lowercase() {
93 let mut t = fixture(vec![]);
94 t.kind = TrackKind::Translated;
95 let out = render(&t).unwrap();
96 let v: serde_json::Value = serde_json::from_str(&out).unwrap();
97 assert_eq!(v["kind"], "translated");
98 }
99
100 #[test]
101 fn special_characters_in_text_are_escaped() {
102 let t = fixture(vec![Cue::new(0, 1, "quote \" backslash \\ newline\n")]);
103 let out = render(&t).unwrap();
104 let back: Transcript = serde_json::from_str(&out).unwrap();
107 assert_eq!(back.cues[0].text, "quote \" backslash \\ newline\n");
108 }
109}