1use std::{
2 fmt::{self, Debug, Display, Formatter},
3 fs::{self, File},
4 io::{Read, Result, Write},
5 path::PathBuf,
6};
7
8use derive_more::Constructor;
9use getset::{Getters, MutGetters, Setters};
10
11use crate::{interval::Tier as IntervalTier, parse_textgrid, point::Tier as PointTier};
12
13#[derive(Clone, Debug)]
15pub enum Tier {
16 IntervalTier(IntervalTier),
17 PointTier(PointTier),
18}
19
20#[allow(dead_code)]
21impl Tier {
22 const fn get_interval_tier(&self) -> Option<&IntervalTier> {
28 match self {
29 Self::IntervalTier(interval_tier) => Some(interval_tier),
30 Self::PointTier(_) => None,
31 }
32 }
33
34 const fn get_point_tier(&self) -> Option<&PointTier> {
40 match self {
41 Self::PointTier(point_tier) => Some(point_tier),
42 Self::IntervalTier(_) => None,
43 }
44 }
45}
46
47impl Default for Tier {
48 fn default() -> Self {
49 Self::IntervalTier(IntervalTier::default())
50 }
51}
52
53impl Display for Tier {
54 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
55 match self {
56 Self::IntervalTier(interval_tier) => write!(f, "{interval_tier}"),
57 Self::PointTier(point_tier) => write!(f, "{point_tier}"),
58 }
59 }
60}
61
62#[derive(Copy, Clone)]
64pub enum OutputFormat {
65 Long,
66 Short,
67}
68
69#[derive(Clone, Constructor, Debug, Default, Getters, MutGetters, Setters)]
70pub struct TextGrid {
73 #[getset(get = "pub")]
74 xmin: f64,
75 #[getset(get = "pub")]
76 xmax: f64,
77 #[getset(get = "pub", get_mut = "pub")]
78 tiers: Vec<Tier>,
79 #[getset(get = "pub", set = "pub")]
80 name: String,
81}
82
83impl TextGrid {
84 #[must_use]
86 pub fn get_size(&self) -> usize {
87 self.tiers.len()
88 }
89
90 pub fn set_xmin<W: Into<Option<bool>>>(&mut self, xmin: f64, warn: W) {
97 if xmin > self.xmax {
98 if warn.into().unwrap_or_default() {
99 eprintln!("Warning: xmin cannot be greater than xmax. Setting to xmax.");
100 }
101 self.xmin = self.xmax;
102 return;
103 } else if xmin < 0.0 {
104 if warn.into().unwrap_or_default() {
105 eprintln!("Warning: xmin cannot be less than 0.0. Setting to 0.0.");
106 }
107 self.xmin = 0.0;
108 return;
109 }
110
111 if warn.into().unwrap_or_default() {
112 for tier in &self.tiers {
113 match tier {
114 Tier::IntervalTier(interval_tier) => {
115 if *interval_tier.xmin() < xmin {
116 eprintln!(
117 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
118 interval_tier.name(), interval_tier.xmin(), xmin
119 );
120 }
121 }
122 Tier::PointTier(point_tier) => {
123 if *point_tier.xmin() < xmin {
124 eprintln!(
125 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
126 point_tier.name(), point_tier.xmin(), xmin
127 );
128 }
129 }
130 }
131 }
132 }
133
134 self.xmin = xmin;
135 }
136
137 pub fn set_xmax<W: Into<Option<bool>>>(&mut self, xmax: f64, warn: W) {
144 if xmax < self.xmin {
145 if warn.into().unwrap_or_default() {
146 eprintln!("Warning: xmax cannot be less than xmin. Setting to xmin.");
147 }
148 self.xmax = self.xmin;
149 return;
150 } else if xmax < 0.0 {
151 if warn.into().unwrap_or_default() {
152 eprintln!("Warning: xmax cannot be less than 0.0. Setting to 0.0.");
153 }
154 self.xmax = 0.0;
155 return;
156 }
157
158 if warn.into().unwrap_or_default() {
159 for tier in &self.tiers {
160 match tier {
161 Tier::IntervalTier(interval_tier) => {
162 if *interval_tier.xmax() > xmax {
163 eprintln!(
164 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
165 interval_tier.name(), interval_tier.xmax(), xmax
166 );
167 }
168 }
169 Tier::PointTier(point_tier) => {
170 if *point_tier.xmax() > xmax {
171 eprintln!(
172 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
173 point_tier.name(), point_tier.xmax(), xmax
174 );
175 }
176 }
177 }
178 }
179 }
180
181 self.xmax = xmax;
182 }
183
184 pub fn push_tier<W: Into<Option<bool>> + Copy>(&mut self, mut tier: Tier, warn: W) {
192 let name = match &tier {
193 Tier::IntervalTier(interval_tier) => interval_tier.name(),
194 Tier::PointTier(point_tier) => point_tier.name(),
195 };
196
197 let mut increment = 0;
198 let mut new_name = name.to_string();
199 while self.get_tier(&new_name).is_some() {
200 increment += 1;
201 new_name = format!("{name}{increment}");
202 }
203 if increment > 0 && warn.into().unwrap_or_default() {
204 eprintln!("Warning: Tier name `{name}` already exists. Renaming to `{new_name}`");
205 }
206
207 if warn.into().unwrap_or_default() {
208 match tier {
209 Tier::IntervalTier(ref mut interval_tier) => {
210 interval_tier.set_name(new_name);
211
212 if *interval_tier.xmin() < self.xmin {
213 eprintln!(
214 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
215 interval_tier.name(), interval_tier.xmin(), self.xmin
216 );
217 }
218 if *interval_tier.xmax() > self.xmax {
219 eprintln!(
220 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
221 interval_tier.name(), interval_tier.xmax(), self.xmax
222 );
223 }
224 }
225 Tier::PointTier(ref mut point_tier) => {
226 point_tier.set_name(new_name);
227
228 if *point_tier.xmin() < self.xmin {
229 eprintln!(
230 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
231 point_tier.name(), point_tier.xmin(), self.xmin
232 );
233 }
234 if *point_tier.xmax() > self.xmax {
235 eprintln!(
236 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
237 point_tier.name(), point_tier.xmax(), self.xmax
238 );
239 }
240 }
241 }
242 }
243
244 self.tiers.push(tier);
245 }
246
247 #[must_use]
257 pub fn get_tier(&self, name: &str) -> Option<&Tier> {
258 self.tiers.iter().find(|tier| match tier {
259 Tier::IntervalTier(interval_tier) => interval_tier.name() == name,
260 Tier::PointTier(point_tier) => point_tier.name() == name,
261 })
262 }
263
264 pub fn delete_tier<W: Into<Option<bool>> + Copy>(&mut self, name: &str, warn: W) {
271 let index = self.tiers.iter().position(|tier| match tier {
272 Tier::IntervalTier(interval_tier) => interval_tier.name() == name,
273 Tier::PointTier(point_tier) => point_tier.name() == name,
274 });
275
276 if let Some(index) = index {
277 self.tiers.remove(index);
278 } else if warn.into().unwrap_or_default() {
279 eprintln!("Warning: Tier `{name}` does not exist therefore cannot be deleted.");
280 }
281 }
282
283 pub fn write(&self, path: PathBuf, format: OutputFormat) -> Result<()> {
300 let mut file = if path.extension().is_none() || path.is_dir() {
301 fs::create_dir_all(path.clone())?;
302
303 let mut path = path;
304 path.push(format!("{}.TextGrid", self.name));
305
306 File::create(path)?
307 } else {
308 let mut parent_path = path.clone();
309
310 if parent_path.pop() {
311 fs::create_dir_all(parent_path)?;
312 };
313
314 File::create(path)?
315 };
316
317 let textgrid_data = match format {
318 OutputFormat::Long => self.format_as_long(),
319 OutputFormat::Short => self.format_as_short(),
320 };
321
322 file.write_all(textgrid_data.join("\n").as_bytes())?;
323
324 Ok(())
325 }
326
327 fn format_as_long(&self) -> Vec<String> {
333 let mut out_strings: Vec<String> = vec![
334 "File type = \"ooTextFile\"".into(),
335 "Object class = \"TextGrid\"".into(),
336 String::new(),
337 format!("xmin = {}", self.xmin),
338 format!("xmax = {}", self.xmax),
339 "tiers? <exists>".into(),
340 format!("size = {}", self.tiers.len()),
341 "item []:".into(),
342 ];
343
344 for (tier_index, tier) in self.tiers.iter().enumerate() {
345 match tier {
346 Tier::IntervalTier(interval_tier) => {
347 out_strings.push(format!("\titem [{}]:", tier_index + 1));
348 out_strings.push("\t\tclass = \"IntervalTier\"".into());
349 out_strings.push(format!("\t\tname = \"{}\"", interval_tier.name()));
350 out_strings.push(format!("\t\txmin = {}", interval_tier.xmin()));
351 out_strings.push(format!("\t\txmax = {}", interval_tier.xmax()));
352 out_strings.push(format!(
353 "\t\tintervals: size = {}",
354 interval_tier.get_size()
355 ));
356
357 for (interval_index, interval) in interval_tier.intervals().iter().enumerate() {
358 out_strings.push(format!("\t\tintervals [{}]:", interval_index + 1));
359 out_strings.push(format!("\t\t\txmin = {}", interval.xmin()));
360 out_strings.push(format!("\t\t\txmax = {}", interval.xmax()));
361 out_strings.push(format!("\t\t\ttext = \"{}\"", interval.text()));
362 }
363 }
364 Tier::PointTier(point_tier) => {
365 out_strings.push(format!("\titem [{}]:", tier_index + 1));
366 out_strings.push("\t\tclass = \"TextTier\"".into());
367 out_strings.push(format!("\t\tname = \"{}\"", point_tier.name()));
368 out_strings.push(format!("\t\txmin = {}", point_tier.xmin()));
369 out_strings.push(format!("\t\txmax = {}", point_tier.xmax()));
370 out_strings.push(format!("\t\tpoints: size = {}", point_tier.get_size()));
371
372 for (point_index, point) in point_tier.points().iter().enumerate() {
373 out_strings.push(format!("\t\tpoints [{}]:", point_index + 1));
374 out_strings.push(format!("\t\t\tnumber = {}", point.number()));
375 out_strings.push(format!("\t\t\tmark = \"{}\"", point.mark()));
376 }
377 }
378 }
379 }
380
381 out_strings
382 }
383
384 fn format_as_short(&self) -> Vec<String> {
390 let mut out_strings: Vec<String> = vec![
391 "\"ooTextFile\"".into(),
392 "\"TextGrid\"".into(),
393 String::new(),
394 self.xmin.to_string(),
395 self.xmax.to_string(),
396 "<exists>".into(),
397 self.tiers.len().to_string(),
398 ];
399
400 for tier in &self.tiers {
401 match tier {
402 Tier::IntervalTier(interval_tier) => {
403 out_strings.push("\"IntervalTier\"".into());
404 out_strings.push(format!("\"{}\"", interval_tier.name()));
405 out_strings.push(interval_tier.xmin().to_string());
406 out_strings.push(interval_tier.xmax().to_string());
407 out_strings.push(interval_tier.get_size().to_string());
408
409 for interval in interval_tier.intervals() {
410 out_strings.push(interval.xmin().to_string());
411 out_strings.push(interval.xmax().to_string());
412 out_strings.push(format!("\"{}\"", interval.text()));
413 }
414 }
415 Tier::PointTier(point_tier) => {
416 out_strings.push("\"TextTier\"".into());
417 out_strings.push(format!("\"{}\"", point_tier.name()));
418 out_strings.push(point_tier.xmin().to_string());
419 out_strings.push(point_tier.xmax().to_string());
420 out_strings.push(point_tier.get_size().to_string());
421
422 for point in point_tier.points() {
423 out_strings.push(point.number().to_string());
424 out_strings.push(format!("\"{}\"", point.mark()));
425 }
426 }
427 }
428 }
429
430 out_strings
431 }
432
433 #[must_use]
439 pub fn check_overlaps(&self) -> Option<Vec<(String, (u64, u64))>> {
440 let mut overlaps: Vec<(String, (u64, u64))> = Vec::new();
441
442 for tier in &self.tiers {
443 match tier {
444 Tier::IntervalTier(interval_tier) => {
445 if let Some(interval_overlaps) = interval_tier.check_overlaps() {
446 overlaps.append(
447 &mut interval_overlaps
448 .into_iter()
449 .map(|overlap| (interval_tier.name().into(), overlap))
450 .collect(),
451 );
452 }
453 }
454 Tier::PointTier(point_tier) => {
455 if let Some(point_overlaps) = point_tier.check_overlaps() {
456 overlaps.append(
457 &mut point_overlaps
458 .into_iter()
459 .map(|overlap| (point_tier.name().into(), overlap))
460 .collect(),
461 );
462 }
463 }
464 }
465 }
466
467 if overlaps.is_empty() {
468 None
469 } else {
470 Some(overlaps)
471 }
472 }
473
474 pub fn fix_boundaries<P: Into<Option<bool>> + Copy>(&mut self, prefer_first: P) {
482 for tier in &mut self.tiers {
483 match tier {
484 Tier::IntervalTier(interval_tier) => {
485 interval_tier.fix_boundaries(prefer_first);
486 }
487 Tier::PointTier(_) => {}
488 }
489 }
490 }
491
492 pub fn fill_gaps(&mut self, text: &str) {
498 for tier in &mut self.tiers {
499 match tier {
500 Tier::IntervalTier(interval_tier) => {
501 interval_tier.fill_gaps(text);
502 }
503 Tier::PointTier(_) => {}
504 }
505 }
506 }
507}
508
509impl Display for TextGrid {
510 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
511 write!(
512 f,
513 "TextGrid {}:
514 xmin = {}
515 xmax = {}
516 tier count = {}",
517 self.name,
518 self.xmin,
519 self.xmax,
520 self.tiers.len(),
521 )
522 }
523}
524
525impl TryFrom<PathBuf> for TextGrid {
527 type Error = std::io::Error;
528
529 fn try_from(path: PathBuf) -> Result<Self> {
530 parse_textgrid(path, None)
531 }
532}
533
534impl TryFrom<&str> for TextGrid {
536 type Error = std::io::Error;
537
538 fn try_from(textgrid: &str) -> Result<Self> {
539 parse_textgrid(textgrid, None)
540 }
541}
542
543impl TryFrom<String> for TextGrid {
545 type Error = std::io::Error;
546
547 fn try_from(textgrid: String) -> Result<Self> {
548 parse_textgrid(textgrid, None)
549 }
550}
551
552impl TryFrom<Vec<String>> for TextGrid {
553 type Error = std::io::Error;
554
555 fn try_from(textgrid: Vec<String>) -> Result<Self> {
556 parse_textgrid(textgrid, None)
557 }
558}
559
560impl TryFrom<Box<dyn Read>> for TextGrid {
561 type Error = std::io::Error;
562
563 fn try_from(textgrid: Box<dyn Read>) -> Result<Self> {
564 parse_textgrid(textgrid, None)
565 }
566}
567
568impl TryFrom<File> for TextGrid {
569 type Error = std::io::Error;
570
571 fn try_from(textgrid: File) -> Result<Self> {
572 parse_textgrid(textgrid, None)
573 }
574}
575
576#[cfg(test)]
578#[allow(clippy::float_cmp)]
579mod test_textgrid {
580 use std::env;
581
582 use crate::get_file_content;
583 use crate::input::Source;
584 use crate::interval::{Interval, Tier as IntervalTier};
585 use crate::point::{Point, Tier as PointTier};
586 use crate::textgrid::{TextGrid, Tier};
587
588 mod set_xmin {
589 use crate::textgrid::TextGrid;
590
591 #[test]
592 fn normal() {
593 let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
594
595 textgrid.set_xmin(1.0, false);
596
597 assert_eq!(*textgrid.xmin(), 1.0);
598 }
599
600 #[test]
601 fn negative_with_warn() {
602 let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
603
604 textgrid.set_xmin(-1.0, false);
605
606 assert_eq!(*textgrid.xmin(), 0.0);
607 }
608
609 #[test]
610 fn more_than_xmax() {
611 let mut textgrid = TextGrid::new(5.0, 10.0, Vec::new(), "test".to_string());
612
613 textgrid.set_xmin(11.0, false);
614
615 assert_eq!(*textgrid.xmin(), 10.0);
616 }
617 }
618
619 mod set_xmax {
620 use crate::textgrid::TextGrid;
621
622 #[test]
623 fn normal() {
624 let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
625
626 textgrid.set_xmax(11.0, false);
627
628 assert_eq!(*textgrid.xmax(), 11.0);
629 }
630
631 #[test]
632 fn negative() {
633 let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
634
635 textgrid.set_xmax(-1.0, false);
636
637 assert_eq!(*textgrid.xmax(), 0.0);
638 }
639
640 #[test]
641 fn less_than_xmin() {
642 let mut textgrid = TextGrid::new(5.0, 10.0, Vec::new(), "test".to_string());
643
644 textgrid.set_xmax(1.0, false);
645
646 assert_eq!(*textgrid.xmax(), 5.0);
647 }
648 }
649
650 #[test]
651 fn push_tier() {
652 let mut textgrid = TextGrid::new(0.0, 10.0, Vec::new(), "test".to_string());
653 let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
654 let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
655
656 textgrid.push_tier(Tier::IntervalTier(interval_tier), false);
657 textgrid.push_tier(Tier::PointTier(point_tier), false);
658
659 assert_eq!(textgrid.get_size(), 2);
660 }
661
662 #[test]
663 fn from_pathbuf() {
664 let cwd = env::current_dir();
665 assert!(cwd.is_ok());
666
667 let textgrid = TextGrid::try_from(cwd.unwrap().join("example/long.TextGrid")).unwrap();
668
669 assert_eq!(textgrid.xmax(), &2.3);
670 }
671
672 #[test]
673 fn format_as_long() {
674 let cwd = env::current_dir();
675 assert!(cwd.is_ok());
676
677 let long_out = get_file_content(
678 Source::String(
679 cwd.unwrap()
680 .join("example/long.TextGrid")
681 .to_str()
682 .unwrap()
683 .into(),
684 ),
685 None,
686 )
687 .unwrap();
688
689 let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
690
691 textgrid.push_tier(
692 Tier::IntervalTier(IntervalTier::new(
693 "John".to_string(),
694 0.0,
695 2.3,
696 vec![Interval::new(0.0, 2.3, "daisy bell".to_string())],
697 )),
698 false,
699 );
700
701 textgrid.push_tier(
702 Tier::IntervalTier(IntervalTier::new(
703 "Kelly".to_string(),
704 0.0,
705 2.3,
706 vec![Interval::new(0.0, 2.3, String::new())],
707 )),
708 false,
709 );
710
711 textgrid.push_tier(
712 Tier::PointTier(PointTier::new(
713 "Bell".to_string(),
714 0.0,
715 2.3,
716 vec![Point::new(1.0, "give me your answer do".to_string())],
717 )),
718 false,
719 );
720
721 let format = textgrid.format_as_long();
722
723 for (i, line) in long_out.0.iter().enumerate() {
724 assert_eq!(
725 line.replace('\r', "").replace(" ! comment test", ""),
726 format[i]
727 );
728 }
729 }
730
731 #[test]
732 fn format_as_short() {
733 let cwd = env::current_dir();
734 assert!(cwd.is_ok());
735
736 let short_out = get_file_content(
737 Source::String(
738 cwd.unwrap()
739 .join("example/short.TextGrid")
740 .to_str()
741 .unwrap()
742 .into(),
743 ),
744 None,
745 )
746 .unwrap();
747
748 let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
749
750 textgrid.push_tier(
751 Tier::IntervalTier(IntervalTier::new(
752 "John".to_string(),
753 0.0,
754 2.3,
755 vec![Interval::new(0.0, 2.3, "daisy bell".to_string())],
756 )),
757 false,
758 );
759
760 textgrid.push_tier(
761 Tier::IntervalTier(IntervalTier::new(
762 "Kelly".to_string(),
763 0.0,
764 2.3,
765 vec![Interval::new(0.0, 2.3, String::new())],
766 )),
767 false,
768 );
769
770 textgrid.push_tier(
771 Tier::PointTier(PointTier::new(
772 "Bell".to_string(),
773 0.0,
774 2.3,
775 vec![Point::new(1.0, "give me your answer do".to_string())],
776 )),
777 false,
778 );
779
780 let format = textgrid.format_as_short();
781
782 for (i, line) in short_out.0.iter().enumerate() {
783 assert_eq!(
784 line.replace('\r', "").replace(" ! comment test", ""),
785 format[i]
786 );
787 }
788 }
789}
790
791#[cfg(test)]
792mod test_tier {
793 use crate::interval::Tier as IntervalTier;
794 use crate::point::Tier as PointTier;
795 use crate::textgrid::Tier;
796
797 #[test]
798 fn get_interval_tier() {
799 let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
800 let tier = Tier::IntervalTier(interval_tier);
801
802 assert!(tier.get_interval_tier().is_some());
803 }
804
805 #[test]
806 fn get_point_tier() {
807 let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
808 let tier = Tier::PointTier(point_tier);
809
810 assert!(tier.get_point_tier().is_some());
811 }
812
813 #[test]
814 fn default() {
815 let tier = Tier::default();
816
817 assert!(matches!(tier, Tier::IntervalTier(_)));
818 }
819
820 #[test]
821 fn to_string() {
822 let interval_tier = IntervalTier::new("test".to_string(), 0.0, 10.0, vec![]);
823 let point_tier = PointTier::new("test".to_string(), 0.0, 10.0, vec![]);
824
825 assert_eq!(
826 interval_tier.to_string(),
827 "IntervalTier test:\n xmin: 0\n xmax: 10\n interval count: 0"
828 );
829 assert_eq!(
830 point_tier.to_string(),
831 "PointTier test:\n xmin: 0\n xmax: 10\n point count: 0"
832 );
833 }
834}