id3/taglike.rs
1use crate::frame::Content;
2use crate::frame::{
3 Comment, EncapsulatedObject, ExtendedText, Frame, Lyrics, Picture, PictureType,
4 SynchronisedLyrics, Timestamp,
5};
6use std::borrow::Cow;
7use std::mem::swap;
8
9/// TagLike is a trait that provides a set of useful default methods that make manipulation of tag
10/// frames easier.
11pub trait TagLike: private::Sealed {
12 #[doc(hidden)]
13 fn frames_vec(&self) -> &Vec<Frame>;
14 #[doc(hidden)]
15 fn frames_vec_mut(&mut self) -> &mut Vec<Frame>;
16
17 /// Returns the `Content::Text` string for the frame with the specified identifier.
18 /// Returns `None` if the frame with the specified ID can't be found or if the content is not
19 /// `Content::Text`.
20 #[doc(hidden)]
21 fn text_for_frame_id(&self, id: &str) -> Option<&str> {
22 self.get(id).and_then(|frame| frame.content().text())
23 }
24
25 /// Returns the (potential) multiple `Content::Text` strings for the frame with the specified identifier.
26 /// Returns `None` if the frame with the specified ID can't be found or if the content is not
27 /// `Content::Text`.
28 #[doc(hidden)]
29 fn text_values_for_frame_id(&self, id: &str) -> Option<Vec<&str>> {
30 self.get(id)
31 .and_then(|frame| frame.content().text_values())
32 .map(Vec::from_iter)
33 }
34
35 #[doc(hidden)]
36 fn read_timestamp_frame(&self, id: &str) -> Option<Timestamp> {
37 self.get(id)
38 .and_then(|frame| frame.content().text())
39 .and_then(|text| text.parse().ok())
40 }
41
42 /// Returns the (disc, total_discs) tuple.
43 #[doc(hidden)]
44 fn disc_pair(&self) -> Option<(u32, Option<u32>)> {
45 self.text_pair("TPOS")
46 }
47
48 /// Loads a text frame by its ID and attempt to split it into two parts
49 ///
50 /// Internally used by track and disc getters and setters.
51 #[doc(hidden)]
52 fn text_pair(&self, id: &str) -> Option<(u32, Option<u32>)> {
53 // The '/' is the preferred character to separate these fields, but the ID3 spec states
54 // that frames may separate multple values on zero bytes.
55 // Therefore, we try to to split on both '/' and '\0'.
56 let text = self.get(id)?.content().text()?;
57 let mut split = text.splitn(2, &['\0', '/'][..]);
58 let a = split.next()?.parse().ok()?;
59 let b = split.next().and_then(|s| s.parse().ok());
60 Some((a, b))
61 }
62
63 /// Returns a reference to the first frame with the specified identifier.
64 ///
65 /// # Example
66 /// ```
67 /// use id3::{Tag, TagLike, Frame, Content};
68 ///
69 /// let mut tag = Tag::new();
70 ///
71 /// tag.add_frame(Frame::text("TIT2", "Hello"));
72 ///
73 /// assert!(tag.get("TIT2").is_some());
74 /// assert!(tag.get("TCON").is_none());
75 /// ```
76 fn get(&self, id: impl AsRef<str>) -> Option<&Frame> {
77 self.frames_vec()
78 .iter()
79 .find(|frame| frame.id() == id.as_ref())
80 }
81
82 /// Adds the frame to the tag, replacing and returning any conflicting frame.
83 ///
84 /// # Example
85 /// ```
86 /// use id3::{Tag, TagLike, Frame, Content};
87 /// use id3::frame::ExtendedText;
88 ///
89 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
90 /// let mut tag = Tag::new();
91 ///
92 /// tag.add_frame(Frame::text("TPE1", "Armin van Buuren"));
93 /// tag.add_frame(ExtendedText{
94 /// description: "hello".to_string(),
95 /// value: "world".to_string(),
96 /// });
97 ///
98 /// let removed = tag.add_frame(Frame::text("TPE1", "John 00 Fleming"))
99 /// .ok_or("no such frame")?;
100 /// assert_eq!(removed.content().text(), Some("Armin van Buuren"));
101 /// Ok(())
102 /// }
103 /// ```
104 fn add_frame(&mut self, new_frame: impl Into<Frame>) -> Option<Frame> {
105 let new_frame = new_frame.into();
106 let removed = self
107 .frames_vec()
108 .iter()
109 .position(|frame| frame.compare(&new_frame))
110 .map(|conflict_index| self.frames_vec_mut().remove(conflict_index));
111 self.frames_vec_mut().push(new_frame);
112 removed
113 }
114
115 /// Adds a text frame.
116 ///
117 /// # Example
118 /// ```
119 /// use id3::{Tag, TagLike};
120 ///
121 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
122 /// let mut tag = Tag::new();
123 /// tag.set_text("TRCK", "1/13");
124 /// assert_eq!(tag.get("TRCK").ok_or("no such frame")?.content().text(), Some("1/13"));
125 /// Ok(())
126 /// }
127 /// ```
128 fn set_text(&mut self, id: impl AsRef<str>, text: impl Into<String>) {
129 self.add_frame(Frame::text(id, text));
130 }
131
132 // Adds a new text frame with multiple string values.
133 //
134 /// # Panics
135 /// If any of the strings contain a null byte.
136 ///
137 /// # Example
138 /// ```
139 /// use id3::{Tag, TagLike};
140 ///
141 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
142 /// let mut tag = Tag::new();
143 /// tag.set_text_values("TCON", ["Synthwave", "Cyber Punk", "Electronic"]);
144 /// let text = tag.get("TCON").ok_or("no such frame")?.content().text();
145 /// assert_eq!(text, Some("Synthwave\u{0}Cyber Punk\u{0}Electronic"));
146 /// Ok(())
147 /// }
148 /// ```
149 fn set_text_values(
150 &mut self,
151 id: impl AsRef<str>,
152 texts: impl IntoIterator<Item = impl Into<String>>,
153 ) {
154 self.add_frame(Frame::with_content(id, Content::new_text_values(texts)));
155 }
156
157 /// Remove all frames with the specified identifier and return them.
158 ///
159 /// # Example
160 /// ```
161 /// use id3::{Content, Frame, Tag, TagLike};
162 ///
163 /// let mut tag = Tag::new();
164 ///
165 /// tag.add_frame(Frame::text("TALB", ""));
166 /// tag.add_frame(Frame::text("TPE1", ""));
167 /// assert_eq!(tag.frames().count(), 2);
168 ///
169 /// let removed = tag.remove("TALB");
170 /// assert_eq!(tag.frames().count(), 1);
171 /// assert_eq!(removed.len(), 1);
172 ///
173 /// let removed = tag.remove("TPE1");
174 /// assert_eq!(tag.frames().count(), 0);
175 /// assert_eq!(removed.len(), 1);
176 /// ```
177 fn remove(&mut self, id: impl AsRef<str>) -> Vec<Frame> {
178 let mut from = Vec::new();
179 swap(&mut from, self.frames_vec_mut());
180 let (keep, remove): (Vec<Frame>, Vec<Frame>) = from
181 .into_iter()
182 .partition(|frame| frame.id() != id.as_ref());
183 *self.frames_vec_mut() = keep;
184 remove
185 }
186
187 /// Returns the year (TYER).
188 /// Returns `None` if the year frame could not be found or if it could not be parsed.
189 ///
190 /// # Example
191 /// ```
192 /// use id3::{Tag, TagLike, Frame};
193 /// use id3::frame::Content;
194 ///
195 /// let mut tag = Tag::new();
196 /// assert!(tag.year().is_none());
197 ///
198 /// tag.add_frame(Frame::text("TYER", "2014"));
199 /// assert_eq!(tag.year(), Some(2014));
200 ///
201 /// tag.remove("TYER");
202 ///
203 /// tag.add_frame(Frame::text("TYER", "nope"));
204 /// assert!(tag.year().is_none());
205 /// ```
206 fn year(&self) -> Option<i32> {
207 self.get("TYER")
208 .and_then(|frame| frame.content().text())
209 .and_then(|text| text.trim_start_matches('0').parse().ok())
210 }
211
212 /// Sets the year (TYER).
213 ///
214 /// # Example
215 /// ```
216 /// use id3::{Tag, TagLike};
217 ///
218 /// let mut tag = Tag::new();
219 /// tag.set_year(2014);
220 /// assert_eq!(tag.year(), Some(2014));
221 /// ```
222 fn set_year(&mut self, year: i32) {
223 self.set_text("TYER", format!("{year:04}"));
224 }
225
226 /// Removes the year (TYER).
227 ///
228 /// # Example
229 /// ```
230 /// use id3::{Tag, TagLike};
231 ///
232 /// let mut tag = Tag::new();
233 /// tag.set_year(2014);
234 /// assert!(tag.year().is_some());
235 ///
236 /// tag.remove_year();
237 /// assert!(tag.year().is_none());
238 /// ```
239 fn remove_year(&mut self) {
240 self.remove("TYER");
241 }
242
243 /// Return the content of the TDRC frame, if any
244 ///
245 /// # Example
246 /// ```
247 /// use id3::{Tag, TagLike};
248 /// use id3::Timestamp;
249 ///
250 /// let mut tag = Tag::new();
251 /// tag.set_date_recorded(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
252 /// assert_eq!(tag.date_recorded().map(|t| t.year), Some(2014));
253 /// ```
254 fn date_recorded(&self) -> Option<Timestamp> {
255 self.read_timestamp_frame("TDRC")
256 }
257
258 /// Sets the content of the TDRC frame
259 ///
260 /// # Example
261 /// ```
262 /// use id3::{Tag, TagLike, Timestamp};
263 ///
264 /// let mut tag = Tag::new();
265 /// tag.set_date_recorded(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
266 /// assert_eq!(tag.date_recorded().map(|t| t.year), Some(2014));
267 /// ```
268 fn set_date_recorded(&mut self, timestamp: Timestamp) {
269 let time_string = timestamp.to_string();
270 self.set_text("TDRC", time_string);
271 }
272
273 /// Remove the content of the TDRC frame
274 ///
275 /// # Example
276 /// ```
277 /// use id3::{Tag, TagLike, Timestamp};
278 ///
279 /// let mut tag = Tag::new();
280 /// tag.set_date_recorded(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
281 /// assert!(tag.date_recorded().is_some());
282 ///
283 /// tag.remove_date_recorded();
284 /// assert!(tag.date_recorded().is_none());
285 /// ```
286 fn remove_date_recorded(&mut self) {
287 self.remove("TDRC");
288 }
289
290 /// Return the content of the TDRL frame, if any
291 ///
292 /// # Example
293 /// ```
294 /// use id3::{Tag, TagLike, Timestamp};
295 ///
296 /// let mut tag = Tag::new();
297 /// tag.set_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
298 /// assert_eq!(tag.date_released().map(|t| t.year), Some(2014));
299 /// ```
300 fn date_released(&self) -> Option<Timestamp> {
301 self.read_timestamp_frame("TDRL")
302 }
303
304 /// Sets the content of the TDRL frame
305 ///
306 /// # Example
307 /// ```
308 /// use id3::{Tag, TagLike, Timestamp};
309 ///
310 /// let mut tag = Tag::new();
311 /// tag.set_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
312 /// assert_eq!(tag.date_released().map(|t| t.year), Some(2014));
313 /// ```
314 fn set_date_released(&mut self, timestamp: Timestamp) {
315 let time_string = timestamp.to_string();
316 self.set_text("TDRL", time_string);
317 }
318
319 /// Remove the content of the TDRL frame
320 ///
321 /// # Example
322 /// ```
323 /// use id3::{Tag, TagLike, Timestamp};
324 ///
325 /// let mut tag = Tag::new();
326 /// tag.set_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
327 /// assert!(tag.date_released().is_some());
328 ///
329 /// tag.remove_date_released();
330 /// assert!(tag.date_released().is_none());
331 /// ```
332 fn remove_date_released(&mut self) {
333 self.remove("TDRL");
334 }
335
336 /// Return the content of the TDOR frame, if any
337 ///
338 /// # Example
339 /// ```
340 /// use id3::{Tag, TagLike, Timestamp};
341 ///
342 /// let mut tag = Tag::new();
343 /// tag.set_original_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
344 /// assert_eq!(tag.original_date_released().map(|t| t.year), Some(2014));
345 /// ```
346 fn original_date_released(&self) -> Option<Timestamp> {
347 self.read_timestamp_frame("TDOR")
348 }
349
350 /// Sets the content of the TDOR frame
351 ///
352 /// # Example
353 /// ```
354 /// use id3::{Tag, TagLike, Timestamp};
355 ///
356 /// let mut tag = Tag::new();
357 /// tag.set_original_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
358 /// assert_eq!(tag.original_date_released().map(|t| t.year), Some(2014));
359 /// ```
360 fn set_original_date_released(&mut self, timestamp: Timestamp) {
361 let time_string = timestamp.to_string();
362 self.set_text("TDOR", time_string);
363 }
364
365 /// Remove the content of the TDOR frame
366 ///
367 /// # Example
368 /// ```
369 /// use id3::{Tag, TagLike, Timestamp};
370 ///
371 /// let mut tag = Tag::new();
372 /// tag.set_original_date_released(Timestamp{ year: 2014, month: None, day: None, hour: None, minute: None, second: None });
373 /// assert!(tag.original_date_released().is_some());
374 ///
375 /// tag.remove_original_date_released();
376 /// assert!(tag.original_date_released().is_none());
377 /// ```
378 fn remove_original_date_released(&mut self) {
379 self.remove("TDOR");
380 }
381
382 /// Returns the artist (TPE1).
383 ///
384 /// # Example
385 /// ```
386 /// use id3::{Frame, Tag, TagLike};
387 /// use id3::frame::Content;
388 ///
389 /// let mut tag = Tag::new();
390 /// tag.add_frame(Frame::text("TPE1", "artist"));
391 /// assert_eq!(tag.artist(), Some("artist"));
392 /// ```
393 fn artist(&self) -> Option<&str> {
394 self.text_for_frame_id("TPE1")
395 }
396
397 /// Returns the (potential) multiple artists (TPE1).
398 fn artists(&self) -> Option<Vec<&str>> {
399 self.text_values_for_frame_id("TPE1")
400 }
401
402 /// Sets the artist (TPE1).
403 ///
404 /// # Example
405 /// ```
406 /// use id3::{Tag, TagLike};
407 ///
408 /// let mut tag = Tag::new();
409 /// tag.set_artist("artist");
410 /// assert_eq!(tag.artist(), Some("artist"));
411 /// ```
412 fn set_artist(&mut self, artist: impl Into<String>) {
413 self.set_text("TPE1", artist);
414 }
415
416 /// Removes the artist (TPE1).
417 ///
418 /// # Example
419 /// ```
420 /// use id3::{Tag, TagLike};
421 ///
422 /// let mut tag = Tag::new();
423 /// tag.set_artist("artist");
424 /// assert!(tag.artist().is_some());
425 ///
426 /// tag.remove_artist();
427 /// assert!(tag.artist().is_none());
428 /// ```
429 fn remove_artist(&mut self) {
430 self.remove("TPE1");
431 }
432
433 /// Sets the album artist (TPE2).
434 ///
435 /// # Example
436 /// ```
437 /// use id3::{Frame, Tag, TagLike};
438 /// use id3::frame::Content;
439 ///
440 /// let mut tag = Tag::new();
441 /// tag.add_frame(Frame::text("TPE2", "artist"));
442 /// assert_eq!(tag.album_artist(), Some("artist"));
443 /// ```
444 fn album_artist(&self) -> Option<&str> {
445 self.text_for_frame_id("TPE2")
446 }
447
448 /// Sets the album artist (TPE2).
449 ///
450 /// # Example
451 /// ```
452 /// use id3::{Tag, TagLike};
453 ///
454 /// let mut tag = Tag::new();
455 /// tag.set_album_artist("artist");
456 /// assert_eq!(tag.album_artist(), Some("artist"));
457 /// ```
458 fn set_album_artist(&mut self, album_artist: impl Into<String>) {
459 self.set_text("TPE2", album_artist);
460 }
461
462 /// Removes the album artist (TPE2).
463 ///
464 /// # Example
465 /// ```
466 /// use id3::{Tag, TagLike};
467 ///
468 /// let mut tag = Tag::new();
469 /// tag.set_album_artist("artist");
470 /// assert!(tag.album_artist().is_some());
471 ///
472 /// tag.remove_album_artist();
473 /// assert!(tag.album_artist().is_none());
474 /// ```
475 fn remove_album_artist(&mut self) {
476 self.remove("TPE2");
477 }
478
479 /// Returns the album (TALB).
480 ///
481 /// # Example
482 /// ```
483 /// use id3::{Frame, Tag, TagLike};
484 /// use id3::frame::Content;
485 ///
486 /// let mut tag = Tag::new();
487 /// tag.add_frame(Frame::text("TALB", "album"));
488 /// assert_eq!(tag.album(), Some("album"));
489 /// ```
490 fn album(&self) -> Option<&str> {
491 self.text_for_frame_id("TALB")
492 }
493
494 /// Sets the album (TALB).
495 ///
496 /// # Example
497 /// ```
498 /// use id3::{Tag, TagLike};
499 ///
500 /// let mut tag = Tag::new();
501 /// tag.set_album("album");
502 /// assert_eq!(tag.album(), Some("album"));
503 /// ```
504 fn set_album(&mut self, album: impl Into<String>) {
505 self.set_text("TALB", album);
506 }
507
508 /// Removes the album (TALB).
509 ///
510 /// # Example
511 /// ```
512 /// use id3::{Tag, TagLike};
513 ///
514 /// let mut tag = Tag::new();
515 /// tag.set_album("album");
516 /// assert!(tag.album().is_some());
517 ///
518 /// tag.remove_album();
519 /// assert!(tag.album().is_none());
520 /// ```
521 fn remove_album(&mut self) {
522 self.remove("TALB");
523 }
524
525 /// Returns the title (TIT2).
526 ///
527 /// # Example
528 /// ```
529 /// use id3::{Frame, Tag, TagLike};
530 /// use id3::frame::Content;
531 ///
532 /// let mut tag = Tag::new();
533 /// tag.add_frame(Frame::text("TIT2", "title"));
534 /// assert_eq!(tag.title(), Some("title"));
535 /// ```
536 fn title(&self) -> Option<&str> {
537 self.text_for_frame_id("TIT2")
538 }
539
540 /// Sets the title (TIT2).
541 ///
542 /// # Example
543 /// ```
544 /// use id3::{Tag, TagLike};
545 ///
546 /// let mut tag = Tag::new();
547 /// tag.set_title("title");
548 /// assert_eq!(tag.title(), Some("title"));
549 /// ```
550 fn set_title(&mut self, title: impl Into<String>) {
551 self.set_text("TIT2", title);
552 }
553
554 /// Removes the title (TIT2).
555 ///
556 /// # Example
557 /// ```
558 /// use id3::{Tag, TagLike};
559 ///
560 /// let mut tag = Tag::new();
561 /// tag.set_title("title");
562 /// assert!(tag.title().is_some());
563 ///
564 /// tag.remove_title();
565 /// assert!(tag.title().is_none());
566 /// ```
567 fn remove_title(&mut self) {
568 self.remove("TIT2");
569 }
570
571 /// Returns the duration (TLEN).
572 ///
573 /// # Example
574 /// ```
575 /// use id3::{Frame, Tag, TagLike};
576 /// use id3::frame::Content;
577 ///
578 /// let mut tag = Tag::new();
579 ///
580 /// tag.add_frame(Frame::text("TLEN", "350"));
581 /// assert_eq!(tag.duration(), Some(350));
582 /// ```
583 fn duration(&self) -> Option<u32> {
584 self.text_for_frame_id("TLEN").and_then(|t| t.parse().ok())
585 }
586
587 /// Sets the duration (TLEN).
588 ///
589 /// # Example
590 /// ```
591 /// use id3::{Tag, TagLike};
592 ///
593 /// let mut tag = Tag::new();
594 /// tag.set_duration(350);
595 /// assert_eq!(tag.duration(), Some(350));
596 /// ```
597 fn set_duration(&mut self, duration: u32) {
598 self.set_text("TLEN", duration.to_string());
599 }
600
601 /// Removes the duration (TLEN).
602 ///
603 /// # Example
604 /// ```
605 /// use id3::{Tag, TagLike};
606 ///
607 /// let mut tag = Tag::new();
608 /// tag.set_duration(350);
609 /// assert!(tag.duration().is_some());
610 ///
611 /// tag.remove_duration();
612 /// assert!(tag.duration().is_none());
613 /// ```
614 fn remove_duration(&mut self) {
615 self.remove("TLEN");
616 }
617
618 /// Returns the plain genre (TCON) text.
619 ///
620 /// Please be aware that ID3v2 specifies that this frame is permitted to refer to a
621 /// predetermined set of ID3v1 genres by index. To handle such frames, use `genre_parsed`
622 /// instead.
623 ///
624 /// # Example
625 /// ```
626 /// use id3::{Frame, Tag, TagLike};
627 /// use id3::frame::Content;
628 ///
629 /// let mut tag = Tag::new();
630 /// tag.add_frame(Frame::text("TCON", "genre"));
631 /// assert_eq!(tag.genre(), Some("genre"));
632 /// tag.set_genre("(31)");
633 /// assert_eq!(tag.genre(), Some("(31)"));
634 /// ```
635 fn genre(&self) -> Option<&str> {
636 self.text_for_frame_id("TCON")
637 }
638
639 /// Returns the genre (TCON) with ID3v1 genre indices resolved.
640 ///
641 /// # Example
642 /// ```
643 /// use id3::frame::Content;
644 /// use id3::{Frame, Tag, TagLike};
645 /// use std::borrow::Cow;
646 ///
647 /// let mut tag = Tag::new();
648 /// tag.add_frame(Frame::text("TCON", "genre"));
649 /// assert_eq!(tag.genre_parsed(), Some(Cow::Borrowed("genre")));
650 /// tag.set_genre("(31)");
651 /// assert_eq!(tag.genre_parsed(), Some(Cow::Owned("Trance".to_string())));
652 /// ```
653 fn genre_parsed(&self) -> Option<Cow<'_, str>> {
654 let tcon = self.text_for_frame_id("TCON")?;
655 Some(crate::tcon::Parser::parse_tcon(tcon))
656 }
657
658 /// Returns the (potential) multiple plain genres (TCON).
659 fn genres(&self) -> Option<Vec<&str>> {
660 self.text_values_for_frame_id("TCON")
661 }
662
663 /// Returns the genre (TCON) with ID3v1 genre indices resolved.
664 ///
665 /// # Example
666 /// ```
667 /// use id3::frame::Content;
668 /// use id3::{Frame, Tag, TagLike};
669 /// use std::borrow::Cow;
670 ///
671 /// let mut tag = Tag::new();
672 /// tag.add_frame(Frame::text("TCON", "genre"));
673 /// assert_eq!(tag.genres_parsed(), vec!["genre"]);
674 /// tag.add_frame(Frame::text("TCON", "21\x00Eurodance"));
675 /// assert_eq!(tag.genres_parsed(), vec!["Ska", "Eurodance"]);
676 /// ```
677 fn genres_parsed(&self) -> Vec<Cow<'_, str>> {
678 self.text_values_for_frame_id("TCON")
679 .unwrap_or_default()
680 .into_iter()
681 .map(crate::tcon::Parser::parse_tcon)
682 .collect()
683 }
684
685 /// Sets the plain genre (TCON).
686 ///
687 /// No attempt is made to interpret and convert ID3v1 indices.
688 ///
689 /// # Example
690 /// ```
691 /// use id3::{Tag, TagLike};
692 ///
693 /// let mut tag = Tag::new();
694 /// tag.set_genre("genre");
695 /// assert_eq!(tag.genre(), Some("genre"));
696 /// ```
697 fn set_genre(&mut self, genre: impl Into<String>) {
698 self.set_text("TCON", genre);
699 }
700
701 /// Removes the genre (TCON).
702 ///
703 /// # Example
704 /// ```
705 /// use id3::{Tag, TagLike};
706 ///
707 /// let mut tag = Tag::new();
708 /// tag.set_genre("genre");
709 /// assert!(tag.genre().is_some());
710 ///
711 /// tag.remove_genre();
712 /// assert!(tag.genre().is_none());
713 /// ```
714 fn remove_genre(&mut self) {
715 self.remove("TCON");
716 }
717
718 /// Returns the disc number (TPOS).
719 ///
720 /// # Example
721 /// ```
722 /// use id3::{Frame, Tag, TagLike};
723 /// use id3::frame::Content;
724 ///
725 /// let mut tag = Tag::new();
726 /// assert!(tag.disc().is_none());
727 ///
728 /// tag.add_frame(Frame::text("TPOS", "4"));
729 /// assert_eq!(tag.disc(), Some(4));
730 ///
731 /// tag.remove("TPOS");
732 ///
733 /// tag.add_frame(Frame::text("TPOS", "nope"));
734 /// assert!(tag.disc().is_none());
735 /// ```
736 fn disc(&self) -> Option<u32> {
737 self.disc_pair().map(|(disc, _)| disc)
738 }
739
740 /// Sets the disc (TPOS).
741 ///
742 /// # Example
743 /// ```
744 /// use id3::{Tag, TagLike};
745 ///
746 /// let mut tag = Tag::new();
747 /// tag.set_disc(2);
748 /// assert_eq!(tag.disc(), Some(2));
749 /// ```
750 fn set_disc(&mut self, disc: u32) {
751 let text = match self
752 .text_pair("TPOS")
753 .and_then(|(_, total_discs)| total_discs)
754 {
755 Some(n) => format!("{disc}/{n}"),
756 None => format!("{disc}"),
757 };
758 self.set_text("TPOS", text);
759 }
760
761 /// Removes the disc number (TPOS).
762 ///
763 /// # Example
764 /// ```
765 /// use id3::{Tag, TagLike};
766 ///
767 /// let mut tag = Tag::new();
768 /// tag.set_disc(3);
769 /// assert!(tag.disc().is_some());
770 ///
771 /// tag.remove_disc();
772 /// assert!(tag.disc().is_none());
773 /// ```
774 fn remove_disc(&mut self) {
775 self.remove("TPOS");
776 }
777
778 /// Returns the total number of discs (TPOS).
779 ///
780 /// # Example
781 /// ```
782 /// use id3::{Frame, Tag, TagLike};
783 /// use id3::frame::Content;
784 ///
785 /// let mut tag = Tag::new();
786 /// assert!(tag.disc().is_none());
787 ///
788 /// tag.add_frame(Frame::text("TPOS", "4/10"));
789 /// assert_eq!(tag.total_discs(), Some(10));
790 ///
791 /// tag.remove("TPOS");
792 ///
793 /// tag.add_frame(Frame::text("TPOS", "4/nope"));
794 /// assert!(tag.total_discs().is_none());
795 /// ```
796 fn total_discs(&self) -> Option<u32> {
797 self.text_pair("TPOS")
798 .and_then(|(_, total_discs)| total_discs)
799 }
800
801 /// Sets the total number of discs (TPOS).
802 ///
803 /// # Example
804 /// ```
805 /// use id3::{Tag, TagLike};
806 ///
807 /// let mut tag = Tag::new();
808 /// tag.set_total_discs(10);
809 /// assert_eq!(tag.total_discs(), Some(10));
810 /// ```
811 fn set_total_discs(&mut self, total_discs: u32) {
812 let text = match self.text_pair("TPOS") {
813 Some((disc, _)) => format!("{disc}/{total_discs}"),
814 None => format!("1/{total_discs}",),
815 };
816 self.set_text("TPOS", text);
817 }
818
819 /// Removes the total number of discs (TPOS).
820 ///
821 /// # Example
822 /// ```
823 /// use id3::{Tag, TagLike};
824 ///
825 /// let mut tag = Tag::new();
826 /// tag.set_total_discs(10);
827 /// assert!(tag.total_discs().is_some());
828 ///
829 /// tag.remove_total_discs();
830 /// assert!(tag.total_discs().is_none());
831 /// ```
832 fn remove_total_discs(&mut self) {
833 if let Some((disc, _)) = self.text_pair("TPOS") {
834 self.set_text("TPOS", format!("{disc}"));
835 }
836 }
837
838 /// Returns the track number (TRCK).
839 ///
840 /// # Example
841 /// ```
842 /// use id3::{Frame, Tag, TagLike};
843 /// use id3::frame::Content;
844 ///
845 /// let mut tag = Tag::new();
846 /// assert!(tag.track().is_none());
847 ///
848 /// tag.add_frame(Frame::text("TRCK", "4"));
849 /// assert_eq!(tag.track(), Some(4));
850 ///
851 /// tag.remove("TRCK");
852 ///
853 /// tag.add_frame(Frame::text("TRCK", "nope"));
854 /// assert!(tag.track().is_none());
855 /// ```
856 fn track(&self) -> Option<u32> {
857 self.text_pair("TRCK").map(|(track, _)| track)
858 }
859
860 /// Sets the track (TRCK).
861 ///
862 /// # Example
863 /// ```
864 /// use id3::{Tag, TagLike};
865 ///
866 /// let mut tag = Tag::new();
867 /// tag.set_track(10);
868 /// assert_eq!(tag.track(), Some(10));
869 /// ```
870 fn set_track(&mut self, track: u32) {
871 let text = match self
872 .text_pair("TRCK")
873 .and_then(|(_, total_tracks)| total_tracks)
874 {
875 Some(n) => format!("{track}/{n}"),
876 None => format!("{track}"),
877 };
878 self.set_text("TRCK", text);
879 }
880
881 /// Removes the track number (TRCK).
882 ///
883 /// # Example
884 /// ```
885 /// use id3::{Tag, TagLike};
886 ///
887 /// let mut tag = Tag::new();
888 /// tag.set_track(10);
889 /// assert!(tag.track().is_some());
890 ///
891 /// tag.remove_track();
892 /// assert!(tag.track().is_none());
893 /// ```
894 fn remove_track(&mut self) {
895 self.remove("TRCK");
896 }
897
898 /// Returns the total number of tracks (TRCK).
899 ///
900 /// # Example
901 /// ```
902 /// use id3::{Frame, Tag, TagLike};
903 /// use id3::frame::Content;
904 ///
905 /// let mut tag = Tag::new();
906 /// assert!(tag.total_tracks().is_none());
907 ///
908 /// tag.add_frame(Frame::text("TRCK", "4/10"));
909 /// assert_eq!(tag.total_tracks(), Some(10));
910 ///
911 /// tag.remove("TRCK");
912 ///
913 /// tag.add_frame(Frame::text("TRCK", "4/nope"));
914 /// assert!(tag.total_tracks().is_none());
915 /// ```
916 fn total_tracks(&self) -> Option<u32> {
917 self.text_pair("TRCK")
918 .and_then(|(_, total_tracks)| total_tracks)
919 }
920
921 /// Sets the total number of tracks (TRCK).
922 ///
923 /// # Example
924 /// ```
925 /// use id3::{Tag, TagLike};
926 ///
927 /// let mut tag = Tag::new();
928 /// tag.set_total_tracks(10);
929 /// assert_eq!(tag.total_tracks(), Some(10));
930 /// ```
931 fn set_total_tracks(&mut self, total_tracks: u32) {
932 let text = match self.text_pair("TRCK") {
933 Some((track, _)) => format!("{track}/{total_tracks}"),
934 None => format!("1/{total_tracks}"),
935 };
936 self.set_text("TRCK", text);
937 }
938
939 /// Removes the total number of tracks (TCON).
940 ///
941 /// # Example
942 /// ```
943 /// use id3::{Tag, TagLike};
944 ///
945 /// let mut tag = Tag::new();
946 /// tag.set_total_tracks(10);
947 /// assert!(tag.total_tracks().is_some());
948 ///
949 /// tag.remove_total_tracks();
950 /// assert!(tag.total_tracks().is_none());
951 /// ```
952 fn remove_total_tracks(&mut self) {
953 if let Some((track, _)) = self.text_pair("TRCK") {
954 self.set_text("TRCK", format!("{track}"));
955 }
956 }
957
958 /// Adds a user defined text frame (TXXX).
959 ///
960 /// # Example
961 /// ```
962 /// use id3::{Tag, TagLike};
963 ///
964 /// let mut tag = Tag::new();
965 ///
966 /// tag.add_extended_text("key1", "value1");
967 /// tag.add_extended_text("key2", "value2");
968 ///
969 /// assert_eq!(tag.extended_texts().count(), 2);
970 /// assert!(tag.extended_texts().any(|t| t.description == "key1" && t.value == "value1"));
971 /// assert!(tag.extended_texts().any(|t| t.description == "key2" && t.value == "value2"));
972 /// ```
973 #[deprecated(note = "Use add_frame(frame::ExtendedText{ .. })")]
974 fn add_extended_text(&mut self, description: impl Into<String>, value: impl Into<String>) {
975 self.add_frame(ExtendedText {
976 description: description.into(),
977 value: value.into(),
978 });
979 }
980
981 /// Removes the user defined text frame (TXXX) with the specified key and value.
982 ///
983 /// A key or value may be `None` to specify a wildcard value.
984 ///
985 /// # Example
986 /// ```
987 /// use id3::{Tag, TagLike};
988 ///
989 /// let mut tag = Tag::new();
990 ///
991 /// tag.add_extended_text("key1", "value1");
992 /// tag.add_extended_text("key2", "value2");
993 /// tag.add_extended_text("key3", "value2");
994 /// tag.add_extended_text("key4", "value3");
995 /// tag.add_extended_text("key5", "value4");
996 /// assert_eq!(tag.extended_texts().count(), 5);
997 ///
998 /// tag.remove_extended_text(Some("key1"), None);
999 /// assert_eq!(tag.extended_texts().count(), 4);
1000 ///
1001 /// tag.remove_extended_text(None, Some("value2"));
1002 /// assert_eq!(tag.extended_texts().count(), 2);
1003 ///
1004 /// tag.remove_extended_text(Some("key4"), Some("value3"));
1005 /// assert_eq!(tag.extended_texts().count(), 1);
1006 ///
1007 /// tag.remove_extended_text(None, None);
1008 /// assert_eq!(tag.extended_texts().count(), 0);
1009 /// ```
1010 fn remove_extended_text(&mut self, description: Option<&str>, value: Option<&str>) {
1011 self.frames_vec_mut().retain(|frame| {
1012 if frame.id() == "TXXX" {
1013 match *frame.content() {
1014 Content::ExtendedText(ref ext) => {
1015 let descr_match = description.map(|v| v == ext.description).unwrap_or(true);
1016 let value_match = value.map(|v| v == ext.value).unwrap_or(true);
1017 // True if we want to keep the frame.
1018 !(descr_match && value_match)
1019 }
1020 _ => {
1021 // A TXXX frame must always have content of the ExtendedText type. Remove
1022 // frames that do not fit this requirement.
1023 false
1024 }
1025 }
1026 } else {
1027 true
1028 }
1029 });
1030 }
1031
1032 /// Adds a picture frame (APIC).
1033 /// Any other pictures with the same type will be removed from the tag.
1034 ///
1035 /// # Example
1036 /// ```
1037 /// use id3::{Tag, TagLike};
1038 /// use id3::frame::{Picture, PictureType};
1039 ///
1040 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1041 /// let mut tag = Tag::new();
1042 /// tag.add_picture(Picture {
1043 /// mime_type: "image/jpeg".to_string(),
1044 /// picture_type: PictureType::Other,
1045 /// description: "some image".to_string(),
1046 /// data: vec![],
1047 /// });
1048 /// tag.add_picture(Picture {
1049 /// mime_type: "image/png".to_string(),
1050 /// picture_type: PictureType::Other,
1051 /// description: "some other image".to_string(),
1052 /// data: vec![],
1053 /// });
1054 /// assert_eq!(tag.pictures().count(), 1);
1055 /// assert_eq!(&tag.pictures().nth(0).ok_or("no such picture")?.mime_type[..], "image/png");
1056 /// Ok(())
1057 /// }
1058 /// ```
1059 #[deprecated(note = "Use add_frame(frame::Picture{ .. })")]
1060 fn add_picture(&mut self, picture: Picture) {
1061 self.add_frame(picture);
1062 }
1063
1064 /// Removes all pictures of the specified type.
1065 ///
1066 /// # Example
1067 /// ```
1068 /// use id3::{Tag, TagLike};
1069 /// use id3::frame::{Picture, PictureType};
1070 ///
1071 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1072 /// let mut tag = Tag::new();
1073 /// tag.add_picture(Picture {
1074 /// mime_type: "image/jpeg".to_string(),
1075 /// picture_type: PictureType::Other,
1076 /// description: "some image".to_string(),
1077 /// data: vec![],
1078 /// });
1079 /// tag.add_picture(Picture {
1080 /// mime_type: "image/png".to_string(),
1081 /// picture_type: PictureType::CoverFront,
1082 /// description: "some other image".to_string(),
1083 /// data: vec![],
1084 /// });
1085 ///
1086 /// assert_eq!(tag.pictures().count(), 2);
1087 /// tag.remove_picture_by_type(PictureType::CoverFront);
1088 /// assert_eq!(tag.pictures().count(), 1);
1089 /// assert_eq!(tag.pictures().nth(0).ok_or("no such picture")?.picture_type, PictureType::Other);
1090 /// Ok(())
1091 /// }
1092 /// ```
1093 fn remove_picture_by_type(&mut self, picture_type: PictureType) {
1094 self.frames_vec_mut().retain(|frame| {
1095 if frame.id() == "APIC" {
1096 let pic = match *frame.content() {
1097 Content::Picture(ref picture) => picture,
1098 _ => return false,
1099 };
1100 return pic.picture_type != picture_type;
1101 }
1102
1103 true
1104 });
1105 }
1106
1107 /// Removes all pictures.
1108 ///
1109 /// # Example
1110 /// ```
1111 /// use id3::{Tag, TagLike};
1112 /// use id3::frame::{Picture, PictureType};
1113 ///
1114 /// let mut tag = Tag::new();
1115 /// tag.add_picture(Picture {
1116 /// mime_type: "image/jpeg".to_string(),
1117 /// picture_type: PictureType::Other,
1118 /// description: "some image".to_string(),
1119 /// data: vec![],
1120 /// });
1121 /// tag.add_picture(Picture {
1122 /// mime_type: "image/png".to_string(),
1123 /// picture_type: PictureType::CoverFront,
1124 /// description: "some other image".to_string(),
1125 /// data: vec![],
1126 /// });
1127 ///
1128 /// assert_eq!(tag.pictures().count(), 2);
1129 /// tag.remove_all_pictures();
1130 /// assert_eq!(tag.pictures().count(), 0);
1131 /// ```
1132 fn remove_all_pictures(&mut self) {
1133 self.frames_vec_mut().retain(|frame| frame.id() != "APIC");
1134 }
1135
1136 /// Adds a comment (COMM).
1137 ///
1138 /// # Example
1139 /// ```
1140 /// use id3::{Tag, TagLike};
1141 /// use id3::frame::Comment;
1142 ///
1143 /// let mut tag = Tag::new();
1144 ///
1145 /// let com1 = Comment {
1146 /// lang: "eng".to_string(),
1147 /// description: "key1".to_string(),
1148 /// text: "value1".to_string(),
1149 /// };
1150 /// let com2 = Comment {
1151 /// lang: "eng".to_string(),
1152 /// description: "key2".to_string(),
1153 /// text: "value2".to_string(),
1154 /// };
1155 /// tag.add_comment(com1.clone());
1156 /// tag.add_comment(com2.clone());
1157 ///
1158 /// assert_eq!(tag.comments().count(), 2);
1159 /// assert_ne!(None, tag.comments().position(|c| *c == com1));
1160 /// assert_ne!(None, tag.comments().position(|c| *c == com2));
1161 /// ```
1162 #[deprecated(note = "Use add_frame(frame::Comment{ .. })")]
1163 fn add_comment(&mut self, comment: Comment) {
1164 self.add_frame(comment);
1165 }
1166
1167 /// Removes the comment (COMM) with the specified key and value.
1168 ///
1169 /// A key or value may be `None` to specify a wildcard value.
1170 ///
1171 /// # Example
1172 /// ```
1173 /// use id3::{Tag, TagLike};
1174 /// use id3::frame::Comment;
1175 ///
1176 /// let mut tag = Tag::new();
1177 ///
1178 /// tag.add_comment(Comment {
1179 /// lang: "eng".to_string(),
1180 /// description: "key1".to_string(),
1181 /// text: "value1".to_string(),
1182 /// });
1183 /// tag.add_comment(Comment {
1184 /// lang: "eng".to_string(),
1185 /// description: "key2".to_string(),
1186 /// text: "value2".to_string(),
1187 /// });
1188 /// assert_eq!(tag.comments().count(), 2);
1189 ///
1190 /// tag.remove_comment(Some("key1"), None);
1191 /// assert_eq!(tag.comments().count(), 1);
1192 ///
1193 /// tag.remove_comment(None, Some("value2"));
1194 /// assert_eq!(tag.comments().count(), 0);
1195 /// ```
1196 fn remove_comment(&mut self, description: Option<&str>, text: Option<&str>) {
1197 self.frames_vec_mut().retain(|frame| {
1198 if frame.id() == "COMM" {
1199 match *frame.content() {
1200 Content::Comment(ref com) => {
1201 let descr_match = description.map(|v| v == com.description).unwrap_or(true);
1202 let text_match = text.map(|v| v == com.text).unwrap_or(true);
1203 // True if we want to keep the frame.
1204 !(descr_match && text_match)
1205 }
1206 _ => {
1207 // A COMM frame must always have content of the Comment type. Remove frames
1208 // that do not fit this requirement.
1209 false
1210 }
1211 }
1212 } else {
1213 true
1214 }
1215 });
1216 }
1217
1218 /// Adds an encapsulated object frame (GEOB).
1219 ///
1220 /// # Example
1221 /// ```
1222 /// use id3::{Tag, TagLike};
1223 ///
1224 /// let mut tag = Tag::new();
1225 ///
1226 /// tag.add_encapsulated_object("key1", "application/octet-stream", "", &b"\x00\x01\xAB"[..]);
1227 /// tag.add_encapsulated_object("key2", "application/json", "foo.json", &b"{ \"value\" }"[..]);
1228 ///
1229 /// assert_eq!(tag.encapsulated_objects().count(), 2);
1230 /// assert!(tag.encapsulated_objects().any(|t| t.description == "key1" && t.mime_type == "application/octet-stream" && t.filename == "" && t.data == b"\x00\x01\xAB"));
1231 /// assert!(tag.encapsulated_objects().any(|t| t.description == "key2" && t.mime_type == "application/json" && t.filename == "foo.json" && t.data == b"{ \"value\" }"));
1232 /// ```
1233 #[deprecated(note = "Use add_frame(frame::EncapsulatedObject{ .. })")]
1234 fn add_encapsulated_object(
1235 &mut self,
1236 description: impl Into<String>,
1237 mime_type: impl Into<String>,
1238 filename: impl Into<String>,
1239 data: impl Into<Vec<u8>>,
1240 ) {
1241 self.add_frame(EncapsulatedObject {
1242 description: description.into(),
1243 mime_type: mime_type.into(),
1244 filename: filename.into(),
1245 data: data.into(),
1246 });
1247 }
1248
1249 /// Removes the encapsulated object frame (GEOB) with the specified key, MIME type, filename
1250 /// and
1251 /// data.
1252 ///
1253 /// A key or value may be `None` to specify a wildcard value.
1254 ///
1255 /// # Example
1256 /// ```
1257 /// use id3::{Tag, TagLike};
1258 ///
1259 /// let mut tag = Tag::new();
1260 ///
1261 /// tag.add_encapsulated_object("key1", "application/octet-stream", "filename1", &b"value1"[..]);
1262 /// tag.add_encapsulated_object("key2", "text/plain", "filename2", &b"value2"[..]);
1263 /// tag.add_encapsulated_object("key3", "text/plain", "filename3", &b"value2"[..]);
1264 /// tag.add_encapsulated_object("key4", "application/octet-stream", "filename4", &b"value3"[..]);
1265 /// tag.add_encapsulated_object("key5", "application/octet-stream", "filename4", &b"value4"[..]);
1266 /// tag.add_encapsulated_object("key6", "application/octet-stream", "filename5", &b"value5"[..]);
1267 /// tag.add_encapsulated_object("key7", "application/octet-stream", "filename6", &b"value6"[..]);
1268 /// tag.add_encapsulated_object("key8", "application/octet-stream", "filename7", &b"value7"[..]);
1269 /// assert_eq!(tag.encapsulated_objects().count(), 8);
1270 ///
1271 /// tag.remove_encapsulated_object(Some("key1"), None, None, None);
1272 /// assert_eq!(tag.encapsulated_objects().count(), 7);
1273 ///
1274 /// tag.remove_encapsulated_object(None, Some("text/plain"), None, None);
1275 /// assert_eq!(tag.encapsulated_objects().count(), 5);
1276 ///
1277 /// tag.remove_encapsulated_object(None, None, Some("filename4"), None);
1278 /// assert_eq!(tag.encapsulated_objects().count(), 3);
1279 ///
1280 /// tag.remove_encapsulated_object(None, None, None, Some(&b"value5"[..]));
1281 /// assert_eq!(tag.encapsulated_objects().count(), 2);
1282 ///
1283 /// tag.remove_encapsulated_object(Some("key7"), None, Some("filename6"), None);
1284 /// assert_eq!(tag.encapsulated_objects().count(), 1);
1285 ///
1286 /// tag.remove_encapsulated_object(None, None, None, None);
1287 /// assert_eq!(tag.encapsulated_objects().count(), 0);
1288 /// ```
1289 fn remove_encapsulated_object(
1290 &mut self,
1291 description: Option<&str>,
1292 mime_type: Option<&str>,
1293 filename: Option<&str>,
1294 data: Option<&[u8]>,
1295 ) {
1296 self.frames_vec_mut().retain(|frame| {
1297 if frame.id() == "GEOB" {
1298 match *frame.content() {
1299 Content::EncapsulatedObject(ref ext) => {
1300 let descr_match = description.map(|v| v == ext.description).unwrap_or(true);
1301 let mime_match = mime_type.map(|v| v == ext.mime_type).unwrap_or(true);
1302 let filename_match = filename.map(|v| v == ext.filename).unwrap_or(true);
1303 let data_match = data.map(|v| v == ext.data).unwrap_or(true);
1304 // True if we want to keep the frame.
1305 !(descr_match && mime_match && filename_match && data_match)
1306 }
1307 _ => {
1308 // A GEOB frame must always have content of the EncapsulatedObject type.
1309 // Remove frames that do not fit this requirement.
1310 false
1311 }
1312 }
1313 } else {
1314 true
1315 }
1316 });
1317 }
1318
1319 /// Sets the lyrics (USLT).
1320 ///
1321 /// # Example
1322 /// ```
1323 /// use id3::{Tag, TagLike};
1324 /// use id3::frame::Lyrics;
1325 ///
1326 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1327 /// let mut tag = Tag::new();
1328 /// tag.add_lyrics(Lyrics {
1329 /// lang: "eng".to_string(),
1330 /// description: "".to_string(),
1331 /// text: "The lyrics".to_string(),
1332 /// });
1333 /// assert_eq!(tag.lyrics().nth(0).ok_or("no such lyrics")?.text, "The lyrics");
1334 /// Ok(())
1335 /// }
1336 /// ```
1337 #[deprecated(note = "Use add_frame(frame::Lyrics{ .. })")]
1338 fn add_lyrics(&mut self, lyrics: Lyrics) {
1339 self.add_frame(lyrics);
1340 }
1341
1342 /// Removes the lyrics text (USLT) from the tag.
1343 ///
1344 /// # Example
1345 /// ```
1346 /// use id3::{Tag, TagLike};
1347 /// use id3::frame::Lyrics;
1348 ///
1349 /// let mut tag = Tag::new();
1350 /// tag.add_lyrics(Lyrics {
1351 /// lang: "eng".to_string(),
1352 /// description: "".to_string(),
1353 /// text: "The lyrics".to_string(),
1354 /// });
1355 /// assert_eq!(1, tag.lyrics().count());
1356 /// tag.remove_all_lyrics();
1357 /// assert_eq!(0, tag.lyrics().count());
1358 /// ```
1359 fn remove_all_lyrics(&mut self) {
1360 self.remove("USLT");
1361 }
1362
1363 /// Adds a synchronised lyrics frame (SYLT).
1364 ///
1365 /// # Example
1366 /// ```
1367 /// use id3::{Tag, TagLike};
1368 /// use id3::frame::{SynchronisedLyrics, SynchronisedLyricsType, TimestampFormat};
1369 ///
1370 /// let mut tag = Tag::new();
1371 /// tag.add_synchronised_lyrics(SynchronisedLyrics {
1372 /// lang: "eng".to_string(),
1373 /// timestamp_format: TimestampFormat::Ms,
1374 /// content_type: SynchronisedLyricsType::Lyrics,
1375 /// content: vec![
1376 /// (1000, "he".to_string()),
1377 /// (1100, "llo".to_string()),
1378 /// (1200, "world".to_string()),
1379 /// ],
1380 /// description: "description".to_string()
1381 /// });
1382 /// assert_eq!(1, tag.synchronised_lyrics().count());
1383 /// ```
1384 #[deprecated(note = "Use add_frame(frame::SynchronisedLyrics{ .. })")]
1385 fn add_synchronised_lyrics(&mut self, lyrics: SynchronisedLyrics) {
1386 self.add_frame(lyrics);
1387 }
1388
1389 /// Removes all synchronised lyrics (SYLT) frames from the tag.
1390 ///
1391 /// # Example
1392 /// ```
1393 /// use id3::{Tag, TagLike};
1394 /// use id3::frame::{SynchronisedLyrics, SynchronisedLyricsType, TimestampFormat};
1395 ///
1396 /// let mut tag = Tag::new();
1397 /// tag.add_synchronised_lyrics(SynchronisedLyrics {
1398 /// lang: "eng".to_string(),
1399 /// timestamp_format: TimestampFormat::Ms,
1400 /// content_type: SynchronisedLyricsType::Lyrics,
1401 /// content: vec![
1402 /// (1000, "he".to_string()),
1403 /// (1100, "llo".to_string()),
1404 /// (1200, "world".to_string()),
1405 /// ],
1406 /// description: "description".to_string()
1407 /// });
1408 /// assert_eq!(1, tag.synchronised_lyrics().count());
1409 /// tag.remove_all_synchronised_lyrics();
1410 /// assert_eq!(0, tag.synchronised_lyrics().count());
1411 /// ```
1412 fn remove_all_synchronised_lyrics(&mut self) {
1413 self.remove("SYLT");
1414 }
1415
1416 /// /// Removes all chapters (CHAP) frames from the tag.
1417 ///
1418 /// # Example
1419 /// ```
1420 /// use id3::{Tag, TagLike};
1421 /// use id3::frame::{Chapter, Content, Frame};
1422 ///
1423 /// let mut tag = Tag::new();
1424 /// tag.add_frame(Chapter{
1425 /// element_id: "01".to_string(),
1426 /// start_time: 1000,
1427 /// end_time: 2000,
1428 /// start_offset: 0xff,
1429 /// end_offset: 0xff,
1430 /// frames: Vec::new(),
1431 /// });
1432 /// assert_eq!(1, tag.chapters().count());
1433 /// tag.remove_all_chapters();
1434 /// assert_eq!(0, tag.chapters().count());
1435 /// ```
1436 fn remove_all_chapters(&mut self) {
1437 self.remove("CHAP");
1438 }
1439
1440 /// /// Removes all tables of contents (CTOC) frames from the tag.
1441 ///
1442 /// # Example
1443 /// ```
1444 /// use id3::{Tag, TagLike};
1445 /// use id3::frame::{Chapter, TableOfContents, Content, Frame};
1446 ///
1447 /// let mut tag = Tag::new();
1448 /// tag.add_frame(Chapter{
1449 /// element_id: "chap01".to_string(),
1450 /// start_time: 1000,
1451 /// end_time: 2000,
1452 /// start_offset: 0xff,
1453 /// end_offset: 0xff,
1454 /// frames: Vec::new(),
1455 /// });
1456 /// tag.add_frame(TableOfContents{
1457 /// element_id: "01".to_string(),
1458 /// top_level: true,
1459 /// ordered: true,
1460 /// elements: vec!["chap01".to_string()],
1461 /// frames: Vec::new(),
1462 /// });
1463 /// assert_eq!(1, tag.tables_of_contents().count());
1464 /// tag.remove_all_tables_of_contents();
1465 /// assert_eq!(0, tag.tables_of_contents().count());
1466 /// ```
1467 fn remove_all_tables_of_contents(&mut self) {
1468 self.remove("CTOC");
1469 }
1470
1471 /// Removes all Unique File Identifiers with the specified owner_identifier.
1472 ///
1473 /// # Example
1474 /// ```
1475 /// use id3::{Tag, TagLike};
1476 /// use id3::frame::{UniqueFileIdentifier};
1477 ///
1478 /// fn main() -> Result<(), Box<dyn std::error::Error>> {
1479 /// let mut tag = Tag::new();
1480 /// tag.add_frame(UniqueFileIdentifier {
1481 /// owner_identifier: "https://example.com".to_string(),
1482 /// identifier: "09FxXfNTQsCgzkPmCeFwlr".into(),
1483 /// });
1484 /// tag.add_frame(UniqueFileIdentifier {
1485 /// owner_identifier: "http://www.id3.org/dummy/ufid.html".to_string(),
1486 /// identifier: "7FZo5fMqyG5Ys1dm8F1FHa".into(),
1487 /// });
1488 ///
1489 /// assert_eq!(tag.unique_file_identifiers().count(), 2);
1490 /// tag.remove_unique_file_identifier_by_owner_identifier("http://www.id3.org/dummy/ufid.html");
1491 /// assert_eq!(tag.unique_file_identifiers().count(), 1);
1492 /// assert_eq!(tag.unique_file_identifiers().nth(0).ok_or("no such ufid owner")?.owner_identifier, "https://example.com");
1493 /// Ok(())
1494 /// }
1495 /// ```
1496 fn remove_unique_file_identifier_by_owner_identifier(&mut self, owner_identifier: &str) {
1497 self.frames_vec_mut().retain(|frame| {
1498 if frame.id() == "UFID" {
1499 let uf = match *frame.content() {
1500 Content::UniqueFileIdentifier(ref unique_file_identifier) => {
1501 unique_file_identifier
1502 }
1503 _ => return false,
1504 };
1505 return uf.owner_identifier != owner_identifier;
1506 }
1507
1508 true
1509 });
1510 }
1511
1512 /// Removes all unique file identifiers.
1513 ///
1514 /// # Example
1515 /// ```
1516 /// use id3::{Tag, TagLike};
1517 /// use id3::frame::{UniqueFileIdentifier};
1518 ///
1519 /// let mut tag = Tag::new();
1520 /// tag.add_frame(UniqueFileIdentifier {
1521 /// owner_identifier: "https://example.com".to_string(),
1522 /// identifier: "09FxXfNTQsCgzkPmCeFwlr".into(),
1523 /// });
1524 /// tag.add_frame(UniqueFileIdentifier {
1525 /// owner_identifier: "http://www.id3.org/dummy/ufid.html".to_string(),
1526 /// identifier: "7FZo5fMqyG5Ys1dm8F1FHa".into(),
1527 /// });
1528 ///
1529 /// assert_eq!(tag.unique_file_identifiers().count(), 2);
1530 /// tag.remove_all_unique_file_identifiers();
1531 /// assert_eq!(tag.unique_file_identifiers().count(), 0);
1532 /// ```
1533 fn remove_all_unique_file_identifiers(&mut self) {
1534 self.frames_vec_mut().retain(|frame| frame.id() != "UFID");
1535 }
1536}
1537
1538// https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed
1539mod private {
1540 use crate::frame::Chapter;
1541 use crate::frame::TableOfContents;
1542 use crate::tag::Tag;
1543
1544 pub trait Sealed {}
1545
1546 impl Sealed for Tag {}
1547 impl Sealed for Chapter {}
1548 impl Sealed for TableOfContents {}
1549}