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