1use std::fmt::Write;
8
9use crate::{Action, FormatSupport, Plan, Target};
10
11fn quote(s: &str) -> String {
13 let mut out = String::with_capacity(s.len() + 2);
14 out.push('"');
15 for c in s.chars() {
16 match c {
17 '"' => out.push_str("\\\""),
18 '\\' => out.push_str("\\\\"),
19 '\n' => out.push_str("\\n"),
20 '\r' => out.push_str("\\r"),
21 '\t' => out.push_str("\\t"),
22 c if (c as u32) < 0x20 => {
24 let _ = write!(out, "\\u{:04x}", c as u32);
25 }
26 c => out.push(c),
27 }
28 }
29 out.push('"');
30 out
31}
32
33fn array(items: impl IntoIterator<Item = String>) -> String {
34 format!("[{}]", items.into_iter().collect::<Vec<_>>().join(","))
35}
36
37impl Plan {
38 pub fn to_json(&self) -> String {
52 let source = array(self.source.iter().map(|s| {
53 format!(
54 r#"{{"index":{},"type":{},"codec":{}}}"#,
55 s.index,
56 quote(&s.media_type.to_string()),
57 quote(s.codec_id.name())
58 )
59 }));
60 let targets = array(self.targets.iter().map(Target::to_json));
61 let format = match &self.source_format {
62 Some(f) => quote(f),
63 None => "null".to_string(),
64 };
65 format!(r#"{{"source_format":{format},"source":{source},"targets":{targets}}}"#)
66 }
67}
68
69impl Target {
70 pub fn to_json(&self) -> String {
72 let streams = array(self.streams.iter().map(|s| {
73 let action = match s.action {
74 Action::Copy => r#""action":"copy""#.to_string(),
75 Action::Transcode { to, lossy } => format!(
76 r#""action":"transcode","to":{},"lossy":{lossy}"#,
77 quote(to.name())
78 ),
79 Action::Drop(reason) => format!(
80 r#""action":"drop","reason":{},"detail":{}"#,
81 quote(reason.name()),
82 quote(&reason.to_string())
83 ),
84 };
85 format!(
86 r#"{{"input_index":{},"type":{},"from":{},{action}}}"#,
87 s.input_index,
88 quote(&s.media_type.to_string()),
89 quote(s.from.name())
90 )
91 }));
92 format!(
93 concat!(
94 r#"{{"format":{},"long_name":{},"extension":{},"extensions":{},"#,
95 r#""kind":{},"fidelity":{},"summary":{},"args":{},"notes":{},"streams":{}}}"#
96 ),
97 quote(self.format),
98 quote(self.long_name),
99 quote(self.extension),
100 array(self.extensions.iter().map(|e| quote(e))),
101 quote(self.kind.name()),
102 quote(self.fidelity.name()),
103 quote(&self.summary()),
104 array(self.args.iter().map(|a| quote(a))),
105 array(self.notes.iter().map(|n| quote(n))),
106 streams,
107 )
108 }
109}
110
111impl FormatSupport {
112 pub fn to_json(&self) -> String {
114 let codecs = |ids: &[rff_core::CodecId]| array(ids.iter().map(|c| quote(c.name())));
115 let max = match self.max_streams {
116 Some(n) => n.to_string(),
117 None => "null".to_string(),
118 };
119 format!(
120 concat!(
121 r#"{{"format":{},"long_name":{},"extensions":{},"read":{},"write":{},"#,
122 r#""still_image":{},"max_streams":{},"video":{},"audio":{},"subtitle":{}}}"#
123 ),
124 quote(self.format),
125 quote(self.long_name),
126 array(self.extensions.iter().map(|e| quote(e))),
127 self.can_read,
128 self.can_write,
129 self.still_image,
130 max,
131 codecs(&self.video),
132 codecs(&self.audio),
133 codecs(&self.subtitle),
134 )
135 }
136}
137
138pub fn matrix_to_json(rows: &[FormatSupport]) -> String {
140 array(rows.iter().map(FormatSupport::to_json))
141}