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}