1use std::fmt;
4
5use codepage_strings::Coding;
6use jiff::Zoned;
7
8use crate::{Date, InvalidFieldValue, LanguageCode, ParseError, Time, fields};
9
10pub(crate) const MAX_TNB: u32 = 99_999;
12
13pub(crate) struct CodePageCodec {
14 coding: Coding,
15 codepage: u16,
16}
17
18impl CodePageCodec {
19 pub fn new(codepage: u16) -> Result<Self, ParseError> {
20 Ok(Self {
21 codepage,
22 coding: Coding::new(codepage).map_err(|_e| ParseError::CodePageNumber(codepage))?,
23 })
24 }
25
26 pub(crate) fn decode(&self, data: &[u8]) -> Result<String, ParseError> {
27 Ok(self.coding.decode_lossy(data).to_string())
28 }
29 pub(crate) fn encode(&self, value: &str) -> Result<Vec<u8>, ParseError> {
30 self.coding
31 .encode(value)
32 .map_err(|err| ParseError::CodePageEncoding {
33 codepage: self.codepage,
34 value: value.to_string(),
35 source: err,
36 })
37 }
38}
39
40#[derive(Debug, Clone, Copy, Eq, PartialEq)]
42#[allow(non_camel_case_types)]
43pub enum CodePageNumber {
44 CPN_437,
46 CPN_850,
48 CPN_860,
50 CPN_863,
52 CPN_865,
54}
55
56impl CodePageNumber {
57 pub(crate) fn serialize(&self) -> Vec<u8> {
58 match *self {
59 CodePageNumber::CPN_437 => vec![0x34, 0x33, 0x37],
60 CodePageNumber::CPN_850 => vec![0x38, 0x35, 0x30],
61 CodePageNumber::CPN_860 => vec![0x38, 0x36, 0x30],
62 CodePageNumber::CPN_863 => vec![0x38, 0x36, 0x33],
63 CodePageNumber::CPN_865 => vec![0x38, 0x36, 0x35],
64 }
65 }
66
67 pub(crate) fn from_u16(codepage: u16) -> Result<CodePageNumber, ParseError> {
68 match codepage {
69 437 => Ok(CodePageNumber::CPN_437),
70 850 => Ok(CodePageNumber::CPN_850),
71 860 => Ok(CodePageNumber::CPN_860),
72 863 => Ok(CodePageNumber::CPN_863),
73 865 => Ok(CodePageNumber::CPN_865),
74 _ => Err(ParseError::CodePageNumber(codepage)),
75 }
76 }
77
78 fn to_u16(self) -> u16 {
79 match self {
80 CodePageNumber::CPN_437 => 437,
81 CodePageNumber::CPN_850 => 850,
82 CodePageNumber::CPN_860 => 860,
83 CodePageNumber::CPN_863 => 863,
84 CodePageNumber::CPN_865 => 865,
85 }
86 }
87}
88
89#[derive(Debug, PartialEq, Eq)]
91pub enum DisplayStandardCode {
92 Blank,
94 OpenSubtitling,
96 Level1Teletext,
98 Level2Teletext,
100}
101
102impl DisplayStandardCode {
103 pub(crate) fn parse(data: u8) -> Result<DisplayStandardCode, ParseError> {
104 match data {
105 0x20 => Ok(DisplayStandardCode::Blank),
106 0x30 => Ok(DisplayStandardCode::OpenSubtitling),
107 0x31 => Ok(DisplayStandardCode::Level1Teletext),
108 0x32 => Ok(DisplayStandardCode::Level2Teletext),
109 _ => Err(ParseError::DisplayStandardCode),
110 }
111 }
112
113 pub(crate) fn serialize(&self) -> u8 {
114 match *self {
115 DisplayStandardCode::Blank => 0x20,
116 DisplayStandardCode::OpenSubtitling => 0x30,
117 DisplayStandardCode::Level1Teletext => 0x31,
118 DisplayStandardCode::Level2Teletext => 0x32,
119 }
120 }
121}
122
123#[derive(Debug, PartialEq, Eq)]
125pub enum TimeCodeStatus {
126 NotIntendedForUse,
128 IntendedForUse,
130}
131
132impl TimeCodeStatus {
133 pub(crate) fn parse(data: u8) -> Result<TimeCodeStatus, ParseError> {
134 match data {
135 0x30 => Ok(TimeCodeStatus::NotIntendedForUse),
136 0x31 => Ok(TimeCodeStatus::IntendedForUse),
137 _ => Err(ParseError::TimeCodeStatus),
138 }
139 }
140
141 pub(crate) fn serialize(&self) -> u8 {
142 match *self {
143 TimeCodeStatus::NotIntendedForUse => 0x30,
144 TimeCodeStatus::IntendedForUse => 0x31,
145 }
146 }
147}
148
149#[derive(Debug, Copy, Clone, PartialEq, Eq)]
151pub enum CharacterCodeTable {
152 Latin,
154 LatinCyrillic,
156 LatinArabic,
158 LatinGreek,
160 LatinHebrew,
162}
163
164impl CharacterCodeTable {
165 pub(crate) fn parse(data: &[u8]) -> Result<CharacterCodeTable, ParseError> {
166 if data.len() != 2 {
167 return Err(ParseError::CharacterCodeTable);
168 }
169 if data[0] != 0x30 {
170 return Err(ParseError::CharacterCodeTable);
171 }
172 match data[1] {
173 0x30 => Ok(CharacterCodeTable::Latin),
174 0x31 => Ok(CharacterCodeTable::LatinCyrillic),
175 0x32 => Ok(CharacterCodeTable::LatinArabic),
176 0x33 => Ok(CharacterCodeTable::LatinGreek),
177 0x34 => Ok(CharacterCodeTable::LatinHebrew),
178 _ => Err(ParseError::CharacterCodeTable),
179 }
180 }
181
182 pub(crate) fn serialize(&self) -> Vec<u8> {
183 match *self {
184 CharacterCodeTable::Latin => vec![0x30, 0x30],
185 CharacterCodeTable::LatinCyrillic => vec![0x30, 0x31],
186 CharacterCodeTable::LatinArabic => vec![0x30, 0x32],
187 CharacterCodeTable::LatinGreek => vec![0x30, 0x33],
188 CharacterCodeTable::LatinHebrew => vec![0x30, 0x34],
189 }
190 }
191}
192
193#[derive(Debug, PartialEq, Eq)]
195#[allow(non_camel_case_types)]
196pub enum DiskFormatCode {
197 STL25_01,
199 STL30_01,
201}
202
203impl DiskFormatCode {
204 pub(crate) fn parse(data: &str) -> Result<DiskFormatCode, ParseError> {
205 if data == "STL25.01" {
206 Ok(DiskFormatCode::STL25_01)
207 } else if data == "STL30.01" {
208 Ok(DiskFormatCode::STL30_01)
209 } else {
210 Err(ParseError::DiskFormatCode(data.to_string()))
211 }
212 }
213
214 pub(crate) fn serialize(&self) -> Vec<u8> {
215 match *self {
216 DiskFormatCode::STL25_01 => String::from("STL25.01").into_bytes(),
217 DiskFormatCode::STL30_01 => String::from("STL30.01").into_bytes(),
218 }
219 }
220
221 pub fn fps(&self) -> usize {
226 match self {
227 DiskFormatCode::STL25_01 => 25,
228 DiskFormatCode::STL30_01 => 30,
229 }
230 }
231}
232
233#[derive(Debug, PartialEq, Eq)]
235pub struct GsiBlock {
236 #[doc = "0..2 Code Page Number"]
237 pub(crate) cpn: CodePageNumber,
238 #[doc = "3..10 Disk Format Code"]
239 pub(crate) dfc: DiskFormatCode,
240 #[doc = "11 Display Standard Code"]
241 pub(crate) dsc: DisplayStandardCode,
242 #[doc = "12..13 Character Code Table Number"]
243 pub(crate) cct: CharacterCodeTable,
244 #[doc = "14..15 Language Code"]
245 pub(crate) lc: String,
246 #[doc = "16..47 Original Programme Title"]
247 pub(crate) opt: String,
248 #[doc = "48..79 Original Episode Title"]
249 pub(crate) oet: String,
250 #[doc = "80..111 Translated Programme Title"]
251 pub(crate) tpt: String,
252 #[doc = "112..143 Translated Episode Title"]
253 pub(crate) tet: String,
254 #[doc = "144..175 Translator's Name"]
255 pub(crate) tn: String,
256 #[doc = "176..207 Translator's Contact Details"]
257 pub(crate) tcd: String,
258 #[doc = "208..223 Subtitle List Reference Code"]
259 pub(crate) slr: String,
260 #[doc = "224..229 Creation Date"]
261 pub(crate) cd: String,
262 #[doc = "230..235 Revision Date"]
263 pub(crate) rd: String,
264 #[doc = "236..237 Revision Number"]
265 pub(crate) rn: String,
266 #[doc = "238..242 Total Number of Text and Timing Blocks. 0-99 999"]
267 pub(crate) tnb: u32,
268 #[doc = "243..247 Total Number of Subtitles. 0-99 999"]
269 pub(crate) tns: u32,
270 #[doc = "248..250 Total Number of Subtitle Groups"]
271 pub(crate) tng: u16,
272 #[doc = "251..252 Maximum Number of Displayable Characters in a Text Row"]
273 pub(crate) mnc: u16,
274 #[doc = "253..254 Maximum Number of Displayable Rows"]
275 pub(crate) mnr: u16,
276 #[doc = "255 Time Code Status"]
277 pub(crate) tcs: TimeCodeStatus,
278 #[doc = "256..263 Time Code: Start of Programme (format: HHMMSSFF)"]
279 pub(crate) tcp: String,
280 #[doc = "264..271 Time Code: First-in-Cue (format: HHMMSSFF)"]
281 pub(crate) tcf: String,
282 #[doc = "272 Total Number of Disks"]
283 pub(crate) tnd: u8,
284 #[doc = "273 Disk Sequence Number"]
285 pub(crate) dsn: u8,
286 #[doc = "274..276 Country of Origin"]
287 pub(crate) co: String, #[doc = "277..308 Publisher"]
289 pub(crate) pub_: String,
290 #[doc = "309..340 Editor's Name"]
291 pub(crate) en: String,
292 #[doc = "341..372 Editor's Contact Details"]
293 pub(crate) ecd: String,
294 #[doc = "373..447 Spare Bytes"]
295 pub(crate) _spare: String,
296 #[doc = "448..1023 User-Defined Area"]
297 pub(crate) uda: String,
298}
299
300impl GsiBlock {
303 pub fn code_page_number(&self) -> &CodePageNumber {
305 &self.cpn
306 }
307
308 pub fn disk_format_code(&self) -> &DiskFormatCode {
310 &self.dfc
311 }
312
313 pub fn display_standard_code(&self) -> &DisplayStandardCode {
315 &self.dsc
316 }
317
318 pub fn character_code_table(&self) -> &CharacterCodeTable {
320 &self.cct
321 }
322
323 pub fn language(&self) -> Result<Option<LanguageCode>, InvalidFieldValue<String>> {
339 fields::parse_field("LC", &self.lc, fields::parse_language_code)
340 }
341
342 pub fn original_programme_title(&self) -> Option<&str> {
351 fields::text(&self.opt)
352 }
353
354 pub fn original_episode_title(&self) -> Option<&str> {
365 fields::text(&self.oet)
366 }
367
368 pub fn translated_programme_title(&self) -> Option<&str> {
377 fields::text(&self.tpt)
378 }
379
380 pub fn translated_episode_title(&self) -> Option<&str> {
389 fields::text(&self.tet)
390 }
391
392 pub fn translators_name(&self) -> Option<&str> {
403 fields::text(&self.tn)
404 }
405
406 pub fn translators_contact_details(&self) -> Option<&str> {
415 fields::text(&self.tcd)
416 }
417
418 pub fn subtitle_list_reference_code(&self) -> Option<&str> {
427 fields::text(&self.slr)
428 }
429
430 pub fn creation_date(&self) -> Result<Option<Date>, InvalidFieldValue<String>> {
444 fields::parse_field("CD", &self.cd, fields::parse_date)
445 }
446
447 pub fn revision_date(&self) -> Result<Option<Date>, InvalidFieldValue<String>> {
461 fields::parse_field("RD", &self.rd, fields::parse_date)
462 }
463
464 pub fn revision_number(&self) -> Result<Option<u8>, InvalidFieldValue<String>> {
476 fields::parse_field("RN", &self.rn, fields::parse_revision_number)
477 }
478
479 pub fn total_number_of_text_and_timing_blocks(&self) -> u32 {
481 self.tnb
482 }
483
484 pub fn total_number_of_subtitles(&self) -> u32 {
486 self.tns
487 }
488
489 pub fn total_number_of_subtitle_groups(&self) -> u16 {
491 self.tng
492 }
493
494 pub fn max_number_of_chars_in_row(&self) -> u16 {
496 self.mnc
497 }
498
499 pub fn max_number_of_rows(&self) -> u16 {
501 self.mnr
502 }
503
504 pub fn time_code_status(&self) -> &TimeCodeStatus {
507 &self.tcs
508 }
509
510 pub fn time_code_start_of_programme(&self) -> Result<Option<Time>, InvalidFieldValue<String>> {
526 let fps = self.dfc.fps();
527 fields::parse_field("TCP", &self.tcp, |value| {
528 fields::parse_time_code(value, fps)
529 })
530 }
531
532 pub fn time_code_first_in_cue(&self) -> Result<Option<Time>, InvalidFieldValue<String>> {
548 let fps = self.dfc.fps();
549 fields::parse_field("TCF", &self.tcf, |value| {
550 fields::parse_time_code(value, fps)
551 })
552 }
553
554 pub fn total_number_of_disks(&self) -> u8 {
556 self.tnd
557 }
558
559 pub fn disk_sequence_number(&self) -> u8 {
561 self.dsn
562 }
563
564 pub fn country_of_origin(&self) -> Result<Option<&str>, InvalidFieldValue<String>> {
576 fields::parse_field("CO", &self.co, fields::parse_country_code)
577 }
578
579 pub fn publisher(&self) -> Option<&str> {
588 fields::text(&self.pub_)
589 }
590
591 pub fn editors_name(&self) -> Option<&str> {
602 fields::text(&self.en)
603 }
604
605 pub fn editors_contact_details(&self) -> Option<&str> {
616 fields::text(&self.ecd)
617 }
618
619 pub fn user_defined_area(&self) -> &str {
621 &self.uda
622 }
623}
624
625fn push_string(v: &mut Vec<u8>, s: &str, len: usize) {
626 let addendum = s.to_owned().into_bytes();
627 let padding = len - addendum.len();
628 v.extend(addendum.iter().cloned());
629 v.extend(vec![0x20u8; padding]);
630}
631
632fn push_encoded_string(
633 v: &mut Vec<u8>,
634 c: &CodePageCodec,
635 s: &str,
636 len: usize,
637) -> Result<(), ParseError> {
638 let addendum = c.encode(s)?;
639 let padding = len - addendum.len();
640 v.extend(addendum.iter().cloned());
641 v.extend(vec![0x20u8; padding]);
642 Ok(())
643}
644
645impl GsiBlock {
646 pub fn new() -> GsiBlock {
651 let date = Zoned::now();
652 let now = date.strftime("%y%m%d").to_string();
653 GsiBlock {
654 cpn: CodePageNumber::CPN_850,
655 dfc: DiskFormatCode::STL25_01,
656 dsc: DisplayStandardCode::Level1Teletext,
657 cct: CharacterCodeTable::Latin,
658 lc: "0F".to_string(), opt: "".to_string(),
660 oet: "".to_string(),
661 tpt: "".to_string(),
662 tet: "".to_string(),
663 tn: "".to_string(),
664 tcd: "".to_string(),
665 slr: "".to_string(),
666 cd: now.clone(),
667 rd: now,
668 rn: "00".to_string(),
669 tnb: 0,
670 tns: 0,
671 tng: 1, mnc: 40, mnr: 23, tcs: TimeCodeStatus::IntendedForUse,
675 tcp: "00000000".to_string(),
676 tcf: "00000000".to_string(),
677 tnd: 1,
678 dsn: 1,
679 co: "".to_string(),
680 pub_: "".to_string(),
681 en: "".to_string(),
682 ecd: "".to_string(),
683 _spare: "".to_string(),
684 uda: "".to_string(),
685 }
686 }
687
688 pub(crate) fn serialize(&self) -> Result<Vec<u8>, ParseError> {
689 let codepage = self.code_page_number().to_u16();
690 let coding = CodePageCodec::new(codepage).expect("codepage");
691 let mut res = Vec::with_capacity(1024);
692 res.extend(self.cpn.serialize());
693 res.extend(self.dfc.serialize().iter().cloned());
694 res.push(self.dsc.serialize());
695 res.extend(self.cct.serialize());
696 push_encoded_string(&mut res, &coding, &self.lc, 15 - 14 + 1)?;
698 push_encoded_string(&mut res, &coding, &self.opt, 47 - 16 + 1)?;
699 push_encoded_string(&mut res, &coding, &self.oet, 79 - 48 + 1)?;
700 push_encoded_string(&mut res, &coding, &self.tpt, 111 - 80 + 1)?;
701 push_encoded_string(&mut res, &coding, &self.tet, 143 - 112 + 1)?;
702 push_encoded_string(&mut res, &coding, &self.tn, 175 - 144 + 1)?;
703 push_encoded_string(&mut res, &coding, &self.tcd, 207 - 176 + 1)?;
704 push_encoded_string(&mut res, &coding, &self.slr, 223 - 208 + 1)?;
705 push_encoded_string(&mut res, &coding, &self.cd, 229 - 224 + 1)?;
706 push_encoded_string(&mut res, &coding, &self.rd, 235 - 230 + 1)?;
707 push_encoded_string(&mut res, &coding, &self.rn, 237 - 236 + 1)?;
708
709 push_string(&mut res, &format!("{:05}", self.tnb), 242 - 238 + 1);
710 push_string(&mut res, &format!("{:05}", self.tns), 247 - 243 + 1);
711 push_string(&mut res, &format!("{:03}", self.tng), 250 - 248 + 1);
712 push_string(&mut res, &format!("{:02}", self.mnc), 252 - 251 + 1);
713 push_string(&mut res, &format!("{:02}", self.mnr), 254 - 253 + 1);
714
715 res.push(self.tcs.serialize());
716 push_encoded_string(&mut res, &coding, &self.tcp, 263 - 256 + 1)?;
717 push_encoded_string(&mut res, &coding, &self.tcf, 271 - 264 + 1)?;
718 push_string(&mut res, &format!("{:1}", self.tnd), 1);
719 push_string(&mut res, &format!("{:1}", self.dsn), 1);
720 push_encoded_string(&mut res, &coding, &self.co, 276 - 274 + 1)?;
721 push_encoded_string(&mut res, &coding, &self.pub_, 308 - 277 + 1)?;
722 push_encoded_string(&mut res, &coding, &self.en, 340 - 309 + 1)?;
723 push_encoded_string(&mut res, &coding, &self.ecd, 372 - 341 + 1)?;
724 push_encoded_string(&mut res, &coding, &self._spare, 447 - 373 + 1)?;
725 push_encoded_string(&mut res, &coding, &self.uda, 1023 - 448 + 1)?;
726
727 Ok(res)
728 }
729}
730
731impl Default for GsiBlock {
732 fn default() -> Self {
733 Self::new()
734 }
735}
736
737impl fmt::Display for GsiBlock {
738 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
739 write!(
740 f,
741 "Program Title: {}\nEpisode Title: {}\ncct:{:?} lc:{}\n",
742 self.opt, self.oet, self.cct, self.lc
743 )
744 }
745}