1use anyhow::anyhow;
2use id3::TagLike;
3
4pub fn read_from_tag(tag: &id3::Tag) -> serde_json::Value {
5 let comment = tag.comments().
9 find(|c| c.description.is_empty()).
10 map(|c| remove_nul_byte(&c.text).to_string());
11
12 if tag.version() == id3::Version::Id3v24 {
13 serde_json::json!({
14 "version": format!("{}", tag.version()),
15 "data": {
16 "title": tag.title().map(remove_nul_byte),
17 "artist": tag.artist().map(remove_nul_byte),
18 "album": tag.album().map(remove_nul_byte),
19 "track": tag.track(),
20 "date": tag.date_recorded().map(|ts| format!("{}", ts)),
21 "genre": tag.genre().map(remove_nul_byte),
22 "comment": comment,
23 },
24 })
25 } else {
26 serde_json::json!({
27 "version": format!("{}", tag.version()),
28 "data": {
29 "title": tag.title().map(remove_nul_byte),
30 "artist": tag.artist().map(remove_nul_byte),
31 "album": tag.album().map(remove_nul_byte),
32 "track": tag.track(),
33 "year": tag.year(),
34 "genre": tag.genre().map(remove_nul_byte),
35 "comment": comment,
36 },
37 })
38 }
39}
40
41pub fn write_to_tag(
42 json_map: &serde_json::Map<String, serde_json::Value>,
43 tag: &mut id3::Tag,
44 version: Option<id3::Version>,
45) -> anyhow::Result<()> {
46 if let Some(serde_json::Value::Object(fields_map)) = json_map.get("data") {
48 return write_to_tag(fields_map, tag, version);
49 };
50
51 let version = version.unwrap_or_else(|| tag.version());
52
53 for (key, value) in json_map {
54 match key.as_str() {
55 "title" => {
56 if let Some(title) = extract_string("title", value)? {
57 tag.set_title(title);
58 } else {
59 tag.remove_title();
60 }
61 },
62 "artist" => {
63 if let Some(artist) = extract_string("artist", value)? {
64 tag.set_artist(artist);
65 } else {
66 tag.remove_artist();
67 }
68 },
69 "album" => {
70 if let Some(album) = extract_string("album", value)? {
71 tag.set_album(album);
72 } else {
73 tag.remove_album();
74 }
75 },
76 "track" => {
77 if let Some(track) = extract_u32("track", value)? {
78 tag.set_track(track);
79 } else {
80 tag.remove_track();
81 }
82 },
83 "year" if version < id3::Version::Id3v24 => {
84 if let Some(year) = extract_u32("year", value)? {
85 tag.set_year(year.try_into()?);
86 } else {
87 tag.remove_year();
88 }
89 },
90 "date" if version >= id3::Version::Id3v24 => {
91 if let Some(date) = extract_string("date", value)? {
92 tag.set_date_recorded(date.parse()?);
93 } else {
94 tag.remove_date_recorded();
95 }
96 },
97 "genre" => {
98 if let Some(genre) = extract_string("genre", value)? {
99 tag.set_genre(genre);
100 } else {
101 tag.remove_genre();
102 }
103 },
104 "comment" => {
105 let mut comment_frames = tag.remove("COMM");
106 let existing_index = comment_frames.iter().
107 position(|c| c.content().comment().unwrap().description.is_empty());
108 let new_comment_body = extract_string("comment", value)?;
109
110 match (existing_index, new_comment_body) {
111 (Some(index), None) => {
112 comment_frames.remove(index);
113 },
114 (Some(index), Some(text)) => {
115 let existing_comment = comment_frames[index].content().comment().unwrap();
116 let mut new_comment = existing_comment.clone();
117 new_comment.text = text;
118
119 let new_frame = id3::Frame::with_content("COMM", id3::Content::Comment(new_comment));
120 comment_frames[index] = new_frame;
121 },
122 (None, Some(text)) => {
123 let new_comment = id3::frame::Comment {
124 lang: String::new(),
125 description: String::new(),
126 text,
127 };
128 let new_frame = id3::Frame::with_content("COMM", id3::Content::Comment(new_comment));
129
130 comment_frames.push(new_frame);
131 }
132 (None, None) => continue,
133 }
134
135 for frame in comment_frames {
136 tag.add_frame(frame);
137 }
138 },
139 _ => (),
140 }
141 }
142
143 Ok(())
144}
145
146
147fn extract_string(label: &str, json_value: &serde_json::Value) -> anyhow::Result<Option<String>> {
148 match json_value {
149 serde_json::Value::Null => Ok(None),
150 serde_json::Value::String(value) => Ok(Some(value.clone())),
151 _ => Err(anyhow!("Invalid string value for \"{}\": {:?}", label, json_value)),
152 }
153}
154
155fn extract_u32(label: &str, json_value: &serde_json::Value) -> anyhow::Result<Option<u32>> {
156 let invalid_number = || anyhow!("Invalid numeric value for \"{}\": {:?}", label, json_value);
157
158 match json_value {
159 serde_json::Value::Null => Ok(None),
160 serde_json::Value::String(value) => Ok(Some(value.parse()?)),
161 serde_json::Value::Number(number) => {
162 let value = number.as_u64().ok_or_else(invalid_number)?.try_into()?;
163 Ok(Some(value))
164 },
165 _ => Err(invalid_number()),
166 }
167}
168
169fn remove_nul_byte(input: &str) -> &str {
170 input.trim_end_matches('\u{0000}')
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_extract_string() {
179 let json = serde_json::json!("String!");
180 let value = extract_string("_", &json).unwrap();
181 assert_eq!(value, Some(String::from("String!")));
182
183 let json = serde_json::json!({ "key": "String!" });
184 let value = extract_string("key", &json.get("key").unwrap()).unwrap();
185 assert_eq!(value, Some(String::from("String!")));
186
187 let json = serde_json::json!({ "key": None::<String> });
188 let value = extract_string("key", &json.get("key").unwrap()).unwrap();
189 assert_eq!(value, None);
190
191 let json = serde_json::json!({ "key": 13 });
192 assert!(extract_string("key", &json.get("key").unwrap()).is_err());
193
194 let json = serde_json::json!({ "key": ["String!"] });
195 assert!(extract_string("key", &json.get("key").unwrap()).is_err());
196 }
197
198 #[test]
199 fn test_extract_u32() {
200 let json = serde_json::json!(42);
201 let value = extract_u32("_", &json).unwrap();
202 assert_eq!(value, Some(42));
203
204 let json = serde_json::json!(None::<u64>);
205 let value = extract_u32("_", &json).unwrap();
206 assert_eq!(value, None);
207
208 let json = serde_json::json!({ "key": "13" });
209 let value = extract_u32("key", &json.get("key").unwrap()).unwrap();
210 assert_eq!(value, Some(13));
211
212 let json = serde_json::json!({ "key": "String!" });
213 assert!(extract_u32("key", &json.get("key").unwrap()).is_err());
214
215 let json = serde_json::json!({ "key": ["String!"] });
216 assert!(extract_u32("key", &json.get("key").unwrap()).is_err());
217
218 let json = serde_json::json!({ "key": u64::MAX });
219 assert!(extract_u32("key", &json.get("key").unwrap()).is_err());
220 }
221}