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