ebustl/
lib.rs

1extern crate chrono;
2extern crate iso6937;
3extern crate nom;
4
5use std::fmt;
6use std::fs::File;
7use std::io;
8use std::io::prelude::*;
9use std::str;
10
11use codepage_strings::Coding;
12pub mod parser;
13use crate::parser::parse_stl_from_slice;
14pub use crate::parser::ParseError;
15
16// STL File
17
18#[derive(Debug)]
19pub struct Stl {
20    pub gsi: GsiBlock,
21    pub ttis: Vec<TtiBlock>,
22}
23
24impl fmt::Display for Stl {
25    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26        write!(f, "{}\n{:?}\n", self.gsi, self.ttis)
27    }
28}
29
30pub struct TtiFormat {
31    #[doc = "Justification Code"]
32    pub jc: u8,
33    #[doc = "Vertical Position"]
34    pub vp: u8,
35    #[doc = "Double Height"]
36    pub dh: bool,
37}
38
39impl Stl {
40    pub fn new() -> Stl {
41        Stl {
42            gsi: GsiBlock::new(),
43            ttis: vec![],
44        }
45    }
46
47    pub fn write_to_file(&self, filename: &str) -> Result<(), io::Error> {
48        let mut f = File::create(filename)?;
49        f.write_all(&self.gsi.serialize())?;
50        for tti in self.ttis.iter() {
51            f.write_all(&tti.serialize())?;
52        }
53        Ok(())
54    }
55
56    pub fn add_sub(&mut self, tci: Time, tco: Time, txt: &str, opt: TtiFormat) {
57        if txt.len() > 112 {
58            //TODO: if txt.len() > 112 split in multiple
59            println!("Warning: sub text is too long!");
60        }
61        self.gsi.tnb += 1; // First TTI has sn=1
62        let tti = TtiBlock::new(self.gsi.tnb, tci, tco, txt, opt);
63        self.gsi.tns += 1;
64        self.ttis.push(tti);
65    }
66}
67
68impl Default for Stl {
69    fn default() -> Self {
70        Self::new()
71    }
72}
73
74pub fn parse_stl_from_file(filename: &str) -> Result<Stl, ParseError> {
75    let mut f = File::open(filename)?;
76    let mut buffer = vec![];
77    f.read_to_end(&mut buffer)?;
78
79    parse_stl_from_slice(&buffer)
80}
81
82struct CodePageDecoder {
83    coding: Coding,
84}
85
86impl CodePageDecoder {
87    pub fn new(codepage: u16) -> Result<Self, ParseError> {
88        Ok(Self {
89            coding: Coding::new(codepage).map_err(|_e| ParseError::CodePageNumber(codepage))?,
90        })
91    }
92
93    fn parse(&self, data: &[u8]) -> Result<String, ParseError> {
94        Ok(self.coding.decode_lossy(data).to_string())
95    }
96}
97
98// GSI Block
99
100#[derive(Debug)]
101#[allow(non_camel_case_types)]
102pub enum CodePageNumber {
103    CPN_437,
104    CPN_850,
105    CPN_860,
106    CPN_863,
107    CPN_865,
108}
109
110impl CodePageNumber {
111    fn serialize(&self) -> Vec<u8> {
112        match *self {
113            CodePageNumber::CPN_437 => vec![0x34, 0x33, 0x37],
114            CodePageNumber::CPN_850 => vec![0x38, 0x35, 0x30],
115            CodePageNumber::CPN_860 => vec![0x38, 0x36, 0x30],
116            CodePageNumber::CPN_863 => vec![0x38, 0x36, 0x33],
117            CodePageNumber::CPN_865 => vec![0x38, 0x36, 0x35],
118        }
119    }
120
121    pub(crate) fn from_u16(codepage: u16) -> Result<CodePageNumber, ParseError> {
122        match codepage {
123            437 => Ok(CodePageNumber::CPN_437),
124            850 => Ok(CodePageNumber::CPN_850),
125            860 => Ok(CodePageNumber::CPN_860),
126            863 => Ok(CodePageNumber::CPN_863),
127            865 => Ok(CodePageNumber::CPN_865),
128            _ => Err(ParseError::CodePageNumber(codepage)),
129        }
130    }
131}
132
133#[derive(Debug)]
134pub enum DisplayStandardCode {
135    Blank,
136    OpenSubtitling,
137    Level1Teletext,
138    Level2Teletext,
139}
140
141impl DisplayStandardCode {
142    fn parse(data: u8) -> Result<DisplayStandardCode, ParseError> {
143        match data {
144            0x20 => Ok(DisplayStandardCode::Blank),
145            0x30 => Ok(DisplayStandardCode::OpenSubtitling),
146            0x31 => Ok(DisplayStandardCode::Level1Teletext),
147            0x32 => Ok(DisplayStandardCode::Level2Teletext),
148            _ => Err(ParseError::DisplayStandardCode),
149        }
150    }
151
152    fn serialize(&self) -> u8 {
153        match *self {
154            DisplayStandardCode::Blank => 0x20,
155            DisplayStandardCode::OpenSubtitling => 0x30,
156            DisplayStandardCode::Level1Teletext => 0x31,
157            DisplayStandardCode::Level2Teletext => 0x32,
158        }
159    }
160}
161
162#[derive(Debug)]
163pub enum TimeCodeStatus {
164    NotIntendedForUse,
165    IntendedForUse,
166}
167
168impl TimeCodeStatus {
169    fn parse(data: u8) -> Result<TimeCodeStatus, ParseError> {
170        match data {
171            0x30 => Ok(TimeCodeStatus::NotIntendedForUse),
172            0x31 => Ok(TimeCodeStatus::IntendedForUse),
173            _ => Err(ParseError::TimeCodeStatus),
174        }
175    }
176
177    fn serialize(&self) -> u8 {
178        match *self {
179            TimeCodeStatus::NotIntendedForUse => 0x30,
180            TimeCodeStatus::IntendedForUse => 0x31,
181        }
182    }
183}
184
185#[derive(Debug)]
186pub enum CharacterCodeTable {
187    Latin,
188    LatinCyrillic,
189    LatinArabic,
190    LatinGreek,
191    LatinHebrew,
192}
193
194impl CharacterCodeTable {
195    fn parse(data: &[u8]) -> Result<CharacterCodeTable, ParseError> {
196        if data.len() != 2 {
197            return Err(ParseError::CharacterCodeTable);
198        }
199        if data[0] != 0x30 {
200            return Err(ParseError::CharacterCodeTable);
201        }
202        match data[1] {
203            0x30 => Ok(CharacterCodeTable::Latin),
204            0x31 => Ok(CharacterCodeTable::LatinCyrillic),
205            0x32 => Ok(CharacterCodeTable::LatinArabic),
206            0x33 => Ok(CharacterCodeTable::LatinGreek),
207            0x34 => Ok(CharacterCodeTable::LatinHebrew),
208            _ => Err(ParseError::CharacterCodeTable),
209        }
210    }
211
212    fn serialize(&self) -> Vec<u8> {
213        match *self {
214            CharacterCodeTable::Latin => vec![0x30, 0x30],
215            CharacterCodeTable::LatinCyrillic => vec![0x30, 0x31],
216            CharacterCodeTable::LatinArabic => vec![0x30, 0x32],
217            CharacterCodeTable::LatinGreek => vec![0x30, 0x33],
218            CharacterCodeTable::LatinHebrew => vec![0x30, 0x34],
219        }
220    }
221}
222
223#[derive(Debug)]
224#[allow(non_camel_case_types)]
225pub enum DiskFormatCode {
226    STL25_01,
227    STL30_01,
228}
229
230impl DiskFormatCode {
231    fn parse(data: &str) -> Result<DiskFormatCode, ParseError> {
232        if data == "STL25.01" {
233            Ok(DiskFormatCode::STL25_01)
234        } else if data == "STL30.01" {
235            Ok(DiskFormatCode::STL30_01)
236        } else {
237            Err(ParseError::DiskFormatCode(data.to_string()))
238        }
239    }
240
241    fn serialize(&self) -> Vec<u8> {
242        match *self {
243            DiskFormatCode::STL25_01 => String::from("STL25.01").into_bytes(),
244            DiskFormatCode::STL30_01 => String::from("STL30.01").into_bytes(),
245        }
246    }
247
248    pub fn get_fps(&self) -> usize {
249        match self {
250            DiskFormatCode::STL25_01 => 25,
251            DiskFormatCode::STL30_01 => 30,
252        }
253    }
254}
255
256#[derive(Debug)]
257pub struct GsiBlock {
258    #[doc = "0..2 Code Page Number"]
259    cpn: CodePageNumber,
260    #[doc = "3..10 Disk Format Code"]
261    dfc: DiskFormatCode,
262    #[doc = "11 Display Standard Code"]
263    dsc: DisplayStandardCode,
264    #[doc = "12..13 Character Code Table Number"]
265    cct: CharacterCodeTable,
266    #[doc = "14..15 Language Code"]
267    lc: String,
268    #[doc = "16..47 Original Program Title"]
269    opt: String,
270    #[doc = "48..79 Original Episode Title"]
271    oet: String,
272    #[doc = "80..111 Translated Program Title"]
273    tpt: String,
274    #[doc = "112..143 Translated Episode Title"]
275    tet: String,
276    #[doc = "144..175 Translator's Name"]
277    tn: String,
278    #[doc = "176..207 Translator's Contact Details"]
279    tcd: String,
280    #[doc = "208..223 Subtitle List Reference Code"]
281    slr: String,
282    #[doc = "224..229 Creation Date"]
283    cd: String,
284    #[doc = "230..235 Revision Date"]
285    rd: String,
286    #[doc = "236..237 Revision Number"]
287    rn: String,
288    #[doc = "238..242 Total Number of Text and Timing Blocks"]
289    tnb: u16,
290    #[doc = "243..247 Total Number of Subtitles"]
291    tns: u16,
292    #[doc = "248..250 Total Number of Subtitle Groups"]
293    tng: u16,
294    #[doc = "251..252 Maximum Number of Displayable Characters in a Text Row"]
295    mnc: u16,
296    #[doc = "253..254 Maximum Number of Displayable Rows"]
297    mnr: u16,
298    #[doc = "255 Time Code Status"]
299    tcs: TimeCodeStatus,
300    #[doc = "256..263 Time Code: Start of Programme (format: HHMMSSFF)"]
301    tcp: String,
302    #[doc = "264..271 Time Code: First-in-Cue (format: HHMMSSFF)"]
303    tcf: String,
304    #[doc = "272 Total Number of Disks"]
305    tnd: u8,
306    #[doc = "273 Disk Sequence Number"]
307    dsn: u8,
308    #[doc = "274..276 Country of Origin"]
309    co: String, // TODO Type with country definitions
310    #[doc = "277..308 Publisher"]
311    pub_: String,
312    #[doc = "309..340 Editor's Name"]
313    en: String,
314    #[doc = "341..372 Editor's Contact Details"]
315    ecd: String,
316    #[doc = "373..447 Spare Bytes"]
317    _spare: String,
318    #[doc = "448..1023 User-Defined Area"]
319    uda: String,
320}
321
322impl GsiBlock {
323    pub fn get_code_page_number(&self) -> &CodePageNumber {
324        &self.cpn
325    }
326    pub fn get_disk_format_code(&self) -> &DiskFormatCode {
327        &self.dfc
328    }
329    pub fn get_display_standard_code(&self) -> &DisplayStandardCode {
330        &self.dsc
331    }
332    pub fn get_character_code_table(&self) -> &CharacterCodeTable {
333        &self.cct
334    }
335    pub fn get_language_code(&self) -> &str {
336        &self.lc
337    }
338    pub fn get_original_program_title(&self) -> &str {
339        &self.opt
340    }
341    pub fn get_original_episode_title(&self) -> &str {
342        &self.oet
343    }
344    pub fn get_translated_program_title(&self) -> &str {
345        &self.tpt
346    }
347    pub fn get_translated_episode_title(&self) -> &str {
348        &self.tet
349    }
350    pub fn get_translators_name(&self) -> &str {
351        &self.tn
352    }
353    pub fn get_translators_contact_details(&self) -> &str {
354        &self.tcd
355    }
356    pub fn get_subtitle_list_reference_code(&self) -> &str {
357        &self.slr
358    }
359    pub fn get_creation_date(&self) -> &str {
360        &self.cd
361    }
362    pub fn get_revision_date(&self) -> &str {
363        &self.rd
364    }
365    pub fn get_revision_number(&self) -> &str {
366        &self.rn
367    }
368    pub fn get_total_number_of_text_and_timing_blocks(&self) -> u16 {
369        self.tnb
370    }
371    pub fn get_total_number_of_subtitles(&self) -> u16 {
372        self.tns
373    }
374    pub fn get_total_number_of_chars_in_row(&self) -> u16 {
375        self.tng
376    }
377    pub fn get_max_number_of_chars_in_row(&self) -> u16 {
378        self.mnc
379    }
380    pub fn get_max_number_of_rows(&self) -> u16 {
381        self.mnr
382    }
383    pub fn get_timecode_status(&self) -> &TimeCodeStatus {
384        &self.tcs
385    }
386    pub fn get_timecode_start_of_program(&self) -> &str {
387        &self.tcp
388    }
389    pub fn get_timecode_first_in_cue(&self) -> &str {
390        &self.tcf
391    }
392    pub fn get_total_number_of_disks(&self) -> u8 {
393        self.tnd
394    }
395    pub fn get_disk_sequence_number(&self) -> u8 {
396        self.dsn
397    }
398    pub fn get_country_of_origin(&self) -> &str {
399        &self.co
400    }
401    pub fn get_publisher(&self) -> &str {
402        &self.pub_
403    }
404    pub fn get_editors_name(&self) -> &str {
405        &self.en
406    }
407    pub fn get_editors_contact_details(&self) -> &str {
408        &self.ecd
409    }
410    pub fn get_user_defined_area(&self) -> &str {
411        &self.uda
412    }
413}
414
415fn push_string(v: &mut Vec<u8>, s: &str, len: usize) {
416    let addendum = s.to_owned().into_bytes();
417    let padding = len - addendum.len();
418    v.extend(addendum.iter().cloned());
419    v.extend(vec![0x20u8; padding]);
420}
421
422impl GsiBlock {
423    pub fn new() -> GsiBlock {
424        let date = chrono::Local::now();
425        let now = date.format("%y%m%d").to_string();
426        GsiBlock {
427            cpn: CodePageNumber::CPN_850,
428            dfc: DiskFormatCode::STL25_01,
429            dsc: DisplayStandardCode::Level1Teletext,
430            cct: CharacterCodeTable::Latin,
431            lc: "0F".to_string(), // FIXME: ok for default?
432            opt: "".to_string(),
433            oet: "".to_string(),
434            tpt: "".to_string(),
435            tet: "".to_string(),
436            tn: "".to_string(),
437            tcd: "".to_string(),
438            slr: "".to_string(),
439            cd: now.clone(),
440            rd: now,
441            rn: "00".to_string(),
442            tnb: 0,
443            tns: 0,
444            tng: 1,  // At least one group?
445            mnc: 40, // FIXME: ok for default?
446            mnr: 23, // FIXME: ok for default?
447            tcs: TimeCodeStatus::IntendedForUse,
448            tcp: "00000000".to_string(),
449            tcf: "00000000".to_string(),
450            tnd: 1,
451            dsn: 1,
452            co: "".to_string(),
453            pub_: "".to_string(),
454            en: "".to_string(),
455            ecd: "".to_string(),
456            _spare: "".to_string(),
457            uda: "".to_string(),
458        }
459    }
460
461    fn serialize(&self) -> Vec<u8> {
462        let mut res = Vec::with_capacity(1024);
463        res.extend(self.cpn.serialize());
464        res.extend(self.dfc.serialize().iter().cloned());
465        res.push(self.dsc.serialize());
466        res.extend(self.cct.serialize());
467        // be careful for the length of following: must force padding
468        push_string(&mut res, &self.lc, 15 - 14 + 1);
469        push_string(&mut res, &self.opt, 47 - 16 + 1);
470        push_string(&mut res, &self.oet, 79 - 48 + 1);
471        push_string(&mut res, &self.tpt, 111 - 80 + 1);
472        push_string(&mut res, &self.tet, 143 - 112 + 1);
473        push_string(&mut res, &self.tn, 175 - 144 + 1);
474        push_string(&mut res, &self.tcd, 207 - 176 + 1);
475        push_string(&mut res, &self.slr, 223 - 208 + 1);
476        push_string(&mut res, &self.cd, 229 - 224 + 1);
477        push_string(&mut res, &self.rd, 235 - 230 + 1);
478        push_string(&mut res, &self.rn, 237 - 236 + 1);
479
480        push_string(&mut res, &format!("{:05}", self.tnb), 242 - 238 + 1);
481        push_string(&mut res, &format!("{:05}", self.tns), 247 - 243 + 1);
482        push_string(&mut res, &format!("{:03}", self.tng), 250 - 248 + 1);
483        push_string(&mut res, &format!("{:02}", self.mnc), 252 - 251 + 1);
484        push_string(&mut res, &format!("{:02}", self.mnr), 254 - 253 + 1);
485
486        res.push(self.tcs.serialize());
487        push_string(&mut res, &self.tcp, 263 - 256 + 1);
488        push_string(&mut res, &self.tcf, 271 - 264 + 1);
489        push_string(&mut res, &format!("{:1}", self.tnd), 1);
490        push_string(&mut res, &format!("{:1}", self.dsn), 1);
491        push_string(&mut res, &self.co, 276 - 274 + 1);
492        push_string(&mut res, &self.pub_, 308 - 277 + 1);
493        push_string(&mut res, &self.en, 340 - 309 + 1);
494        push_string(&mut res, &self.ecd, 372 - 341 + 1);
495        push_string(&mut res, &self._spare, 447 - 373 + 1);
496        push_string(&mut res, &self.uda, 1023 - 448 + 1);
497
498        res
499    }
500}
501
502impl Default for GsiBlock {
503    fn default() -> Self {
504        Self::new()
505    }
506}
507
508impl fmt::Display for GsiBlock {
509    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
510        write!(
511            f,
512            "Program Title: {}\nEpisode Title: {}\ncct:{:?} lc:{}\n",
513            self.opt, self.oet, self.cct, self.lc
514        )
515    }
516}
517
518// TTI Block
519
520#[derive(Debug)]
521pub enum CumulativeStatus {
522    NotPartOfASet,
523    FirstInSet,
524    IntermediateInSet,
525    LastInSet,
526}
527
528impl CumulativeStatus {
529    fn parse(d: u8) -> Result<CumulativeStatus, ParseError> {
530        match d {
531            0 => Ok(CumulativeStatus::NotPartOfASet),
532            1 => Ok(CumulativeStatus::FirstInSet),
533            2 => Ok(CumulativeStatus::IntermediateInSet),
534            3 => Ok(CumulativeStatus::LastInSet),
535            _ => Err(ParseError::CumulativeStatus),
536        }
537    }
538
539    fn serialize(&self) -> u8 {
540        match *self {
541            CumulativeStatus::NotPartOfASet => 0,
542            CumulativeStatus::FirstInSet => 1,
543            CumulativeStatus::IntermediateInSet => 2,
544            CumulativeStatus::LastInSet => 3,
545        }
546    }
547}
548
549pub enum Justification {
550    Unchanged,
551    Left,
552    Centered,
553    Right,
554}
555
556#[derive(Debug, PartialEq, Eq)]
557pub struct Time {
558    pub hours: u8,
559    pub minutes: u8,
560    pub seconds: u8,
561    pub frames: u8,
562}
563
564impl Time {
565    fn new(h: u8, m: u8, s: u8, f: u8) -> Time {
566        Time {
567            hours: h,
568            minutes: m,
569            seconds: s,
570            frames: f,
571        }
572    }
573
574    pub fn format_fps(&self, fps: usize) -> String {
575        format!(
576            "{}:{}:{},{}",
577            self.hours,
578            self.minutes,
579            self.seconds,
580            self.frames as usize * 1000 / fps
581        )
582    }
583    fn serialize(&self) -> Vec<u8> {
584        vec![self.hours, self.minutes, self.seconds, self.frames]
585    }
586}
587
588impl fmt::Display for Time {
589    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
590        write!(
591            f,
592            "{}:{}:{}/{})",
593            self.hours, self.minutes, self.seconds, self.frames
594        )
595    }
596}
597
598pub struct TtiBlock {
599    #[doc = "0 Subtitle Group Number. 00h-FFh"]
600    sgn: u8,
601    #[doc = "1..2 Subtitle Number range. 0000h-FFFFh"]
602    sn: u16,
603    #[doc = "3 Extension Block Number. 00h-FFh"]
604    ebn: u8,
605    #[doc = "4 Cumulative Status. 00-03h"]
606    cs: CumulativeStatus,
607    #[doc = "5..8 Time Code In"]
608    tci: Time,
609    #[doc = "9..12 Time Code Out"]
610    tco: Time,
611    #[doc = "13 Vertical Position"]
612    vp: u8,
613    #[doc = "14 Justification Code"]
614    jc: u8,
615    #[doc = "15 Comment Flag"]
616    cf: u8,
617    #[doc = "16..127 Text Field"]
618    tf: Vec<u8>,
619}
620
621impl TtiBlock {
622    pub fn get_subtitle_group_number(&self) -> u8 {
623        self.sgn
624    }
625    pub fn get_subtitle_number_range(&self) -> u16 {
626        self.sn
627    }
628    pub fn get_extension_block_number(&self) -> u8 {
629        self.ebn
630    }
631    pub fn get_cumulative_status(&self) -> &CumulativeStatus {
632        &self.cs
633    }
634    pub fn get_time_code_in(&self) -> &Time {
635        &self.tci
636    }
637    pub fn get_time_code_out(&self) -> &Time {
638        &self.tco
639    }
640    pub fn get_vertical_position(&self) -> u8 {
641        self.vp
642    }
643    pub fn get_justification_code(&self) -> u8 {
644        self.jc
645    }
646    pub fn get_comment_flag(&self) -> u8 {
647        self.cf
648    }
649}
650
651impl TtiBlock {
652    pub fn new(idx: u16, tci: Time, tco: Time, txt: &str, opt: TtiFormat) -> TtiBlock {
653        TtiBlock {
654            sgn: 0,
655            sn: idx,
656            ebn: 0xff,
657            cs: CumulativeStatus::NotPartOfASet,
658            tci,
659            tco,
660            vp: opt.vp,
661            jc: opt.jc,
662            cf: 0,
663            tf: TtiBlock::encode_text(txt, opt.dh),
664        }
665    }
666
667    fn encode_text(txt: &str, dh: bool) -> Vec<u8> {
668        const TF_LENGTH: usize = 112;
669        let text = iso6937::encode(txt);
670        let mut res = Vec::with_capacity(TF_LENGTH);
671        if dh {
672            res.push(0x0d);
673        }
674        res.push(0x0b);
675        res.push(0x0b);
676        res.extend(text);
677
678        // Make sure size does not exceeds 112 bytes, FIXME: and what if!
679        let max_size = TF_LENGTH - 3; // 3 trailing teletext codes to add.
680        if res.len() > max_size {
681            println!("!!! subtitle length is too long, truncating!");
682        }
683        res.truncate(max_size);
684        res.push(0x0A);
685        res.push(0x0A);
686        res.push(0x8A);
687        let padding = TF_LENGTH - res.len();
688        res.extend(vec![0x8Fu8; padding]);
689        res
690    }
691
692    pub fn get_text(&self) -> String {
693        let mut result = String::from("");
694        let mut first = 0;
695        for i in 0..self.tf.len() {
696            let c = self.tf[i];
697            if match c {
698                0x0..=0x1f => true, //TODO: decode teletext control codes
699                0x20..=0x7f => false,
700                0x80..=0x9f => true, // TODO: decode codes
701                0xa0..=0xff => false,
702            } {
703                if first != i {
704                    result.push_str(&iso6937::decode(&self.tf[first..i]));
705                }
706                if c == 0x8f {
707                    break;
708                } else if c == 0x8a {
709                    result.push_str("\r\n");
710                }
711                first = i + 1;
712            }
713        }
714        result
715    }
716
717    #[allow(clippy::vec_init_then_push)]
718    fn serialize(&self) -> Vec<u8> {
719        let mut res = vec![];
720        res.push(self.sgn);
721        res.push((self.sn & 0xff) as u8);
722        res.push((self.sn >> 8) as u8);
723        res.push(self.ebn);
724        res.push(self.cs.serialize());
725        res.extend(self.tci.serialize().iter().cloned());
726        res.extend(self.tco.serialize().iter().cloned());
727        res.push(self.vp);
728        res.push(self.jc);
729        res.push(self.cf);
730        res.extend(self.tf.iter().cloned());
731        res
732    }
733}
734
735impl fmt::Debug for TtiBlock {
736    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
737        write!(
738            f,
739            "\n{}-->{} sgn:{} sn:{} ebn:{} cs:{:?} vp:{} jc:{} cf:{} [{}]",
740            self.tci,
741            self.tco,
742            self.sgn,
743            self.sn,
744            self.ebn,
745            self.cs,
746            self.vp,
747            self.jc,
748            self.cf,
749            self.get_text()
750        )
751    }
752}
753
754impl fmt::Display for TtiBlock {
755    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
756        write!(
757            f,
758            "\n{} {} {} {} {:?} [{}]",
759            self.tci,
760            self.sgn,
761            self.sn,
762            self.ebn,
763            self.cs,
764            self.get_text()
765        )
766    }
767}