MythTV  master
cc608decoder.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 
3 // Some of the XDS was inspired by code in TVTime. -- dtk 03/30/2006
4 
5 #include <algorithm>
6 using namespace std;
7 
8 // Qt headers
9 #include <QStringList>
10 #include <QCoreApplication>
11 
12 // MythTV headers
13 #include "format.h"
14 #include "captions/cc608decoder.h"
15 #include "mythcontext.h"
16 #include "mythlogging.h"
17 #include "vbilut.h"
18 
19 #define DEBUG_XDS 0
20 
21 static void init_xds_program_type(QString xds_program_type[96]);
22 
24  : m_reader(ccr),
25  m_rbuf(new unsigned char[sizeof(ccsubtitle)+255])
26 {
27  // fill translation table
28  for (uint i = 0; i < 128; i++)
29  m_stdChar[i] = QChar(i);
30  m_stdChar[42] = QLatin1Char(0xE1); // á
31  m_stdChar[92] = QLatin1Char(0xE9); // é
32  m_stdChar[94] = QLatin1Char(0xED); // í
33  m_stdChar[95] = QLatin1Char(0xF3); // ó
34  m_stdChar[96] = QLatin1Char(0xFA); // ú
35  m_stdChar[123] = QLatin1Char(0xE7); // ç
36  m_stdChar[124] = QLatin1Char(0xF7); // ÷
37  m_stdChar[125] = QLatin1Char(0xD1); // Ñ
38  m_stdChar[126] = QLatin1Char(0xF1); // ñ
39  m_stdChar[127] = 0x2588; /* full block */
40 
42 }
43 
45 {
46  delete [] m_rbuf;
47 }
48 
49 void CC608Decoder::FormatCC(int tc, int code1, int code2)
50 {
51  FormatCCField(tc, 0, code1);
52  FormatCCField(tc, 1, code2);
53 }
54 
55 void CC608Decoder::GetServices(uint seconds, bool seen[4]) const
56 {
57  time_t now = time(nullptr);
58  time_t then = now - seconds;
59  for (uint i = 0; i < 4; i++)
60  seen[i] = (m_lastSeen[i] >= then);
61 }
62 
63 static const std::array<const int,16> rowdata =
64 {
65  11, -1, 1, 2, 3, 4, 12, 13,
66  14, 15, 5, 6, 7, 8, 9, 10
67 };
68 
69 static const std::array<const QChar,16> specialchar =
70 {
71  QLatin1Char(0xAE), QLatin1Char(0xB0), QLatin1Char(0xBD), QLatin1Char(0xBF), // ®°½¿
72  0x2122, QLatin1Char(0xA2), QLatin1Char(0xA3), 0x266A, // ™¢£♪
73  QLatin1Char(0xE0), QLatin1Char(' '), QLatin1Char(0xE8), QLatin1Char(0xE2), // à èâ
74  QLatin1Char(0xEA), QLatin1Char(0xEE), QLatin1Char(0xF4), QLatin1Char(0xFB) // êîôû
75 };
76 
77 static const std::array<const QChar,32> extendedchar2 =
78 {
79  QLatin1Char(0xC1), QLatin1Char(0xC9), QLatin1Char(0xD3), QLatin1Char(0xDA), // ÁÉÓÚ
80  QLatin1Char(0xDC), QLatin1Char(0xFC), QLatin1Char('`'), QLatin1Char(0xA1), // Üü`¡
81  QLatin1Char('*'), QLatin1Char('\''), 0x2014, QLatin1Char(0xA9), // *'-©
82  0x2120, QLatin1Char(0xB7), 0x201C, 0x201D, // ℠·“”
83  QLatin1Char(0xC0), QLatin1Char(0xC2), QLatin1Char(0xC7), QLatin1Char(0xC8), // ÀÂÇÈ
84  QLatin1Char(0xCA), QLatin1Char(0xCB), QLatin1Char(0xEB), QLatin1Char(0xCE), // ÊËëÎ
85  QLatin1Char(0xCF), QLatin1Char(0xEF), QLatin1Char(0xD4), QLatin1Char(0xD9), // ÏïÔÙ
86  QLatin1Char(0xF9), QLatin1Char(0xDB), QLatin1Char(0xAB), QLatin1Char(0xBB) // ùÛ«»
87 };
88 
89 static const std::array<const QChar,32> extendedchar3 =
90 {
91  QLatin1Char(0xC3), QLatin1Char(0xE3), QLatin1Char(0xCD), QLatin1Char(0xCC), // ÃãÍÌ
92  QLatin1Char(0xEC), QLatin1Char(0xD2), QLatin1Char(0xF2), QLatin1Char(0xD5), // ìÒòÕ
93  QLatin1Char(0xF5), QLatin1Char('{'), QLatin1Char('}'), QLatin1Char('\\'), // õ{}
94  QLatin1Char('^'), QLatin1Char('_'), QLatin1Char(0xA6), QLatin1Char('~'), // ^_¦~
95  QLatin1Char(0xC4), QLatin1Char(0xE4), QLatin1Char(0xD6), QLatin1Char(0xF6), // ÄäÖö
96  QLatin1Char(0xDF), QLatin1Char(0xA5), QLatin1Char(0xA4), QLatin1Char('|'), // ߥ¤|
97  QLatin1Char(0xC5), QLatin1Char(0xE5), QLatin1Char(0xD8), QLatin1Char(0xF8), // ÅåØø
98  0x250C, 0x2510, 0x2514, 0x2518 // ┌┐└┘
99 };
100 
101 void CC608Decoder::FormatCCField(int tc, int field, int data)
102 {
103  int len = 0;
104  int mode = 0;
105 
106  if (data == -1) // invalid data. flush buffers to be safe.
107  {
108  // TODO: flush reader buffer
109  if (m_ccMode[field] != -1)
110  {
111  for (mode = field*4; mode < (field*4 + 4); mode++)
112  ResetCC(mode);
113  m_xds[field] = 0;
114  m_badVbi[field] = 0;
115  m_ccMode[field] = -1;
116  m_txtMode[field*2] = 0;
117  m_txtMode[field*2 + 1] = 0;
118  }
119  return;
120  }
121 
122  if ((m_lastFormatData[field&1] == data) &&
123  (m_lastFormatTc[field&1] == tc))
124  {
125  LOG(VB_VBI, LOG_DEBUG, "Format CC -- Duplicate");
126  return;
127  }
128 
129  m_lastFormatTc[field&1] = tc;
130  m_lastFormatData[field&1] = data;
131 
132  int b1 = data & 0x7f;
133  int b2 = (data >> 8) & 0x7f;
134 #if 1
135  LOG(VB_VBI, LOG_DEBUG,
136  QString("Format CC @%1/%2 = %3 %4, %5/%6 = '%7' '%8'")
137  .arg(tc).arg(field)
138  .arg((data&0xff), 2, 16)
139  .arg((data&0xff00)>>8, 2, 16)
140  .arg(b1, 2, 16, QChar('0'))
141  .arg(b2, 2, 16, QChar('0'))
142  .arg(QChar((b1 & 0x60) ? b1 : '_'))
143  .arg(QChar((b2 & 0x60) ? b2 : '_')));
144 #endif
145  if (m_ccMode[field] >= 0)
146  {
147  mode = field << 2 |
148  (m_txtMode[field*2 + m_ccMode[field]] << 1) |
149  m_ccMode[field];
150  if (mode >= 0)
151  len = m_ccBuf[mode].length();
152  else
153  len = 0;
154  }
155  else
156  {
157  mode = -1;
158  len = 0;
159  }
160 
161  if (FalseDup(tc, field, data))
162  {
163  if (m_ignoreTimeCode)
164  return;
165  goto skip;
166  }
167 
168  if (XDSDecode(field, b1, b2))
169  return;
170 
171  if (b1 & 0x60)
172  // 0x20 <= b1 <= 0x7F
173  // text codes
174  {
175  if (mode >= 0)
176  {
177  m_lastCodeTc[field] += 33;
178  m_timeCode[mode] = tc;
179 
180  // commit row number only when first text code
181  // comes in
182  if (m_newRow[mode])
183  NewRowCC(mode, len);
184 
185  m_ccBuf[mode] += CharCC(b1);
186  m_col[mode]++;
187  if (b2 & 0x60)
188  {
189  m_ccBuf[mode] += CharCC(b2);
190  m_col[mode]++;
191  }
192  }
193  }
194 
195  else if ((b1 & 0x10) && (b2 > 0x1F))
196  // 0x10 <= b1 <= 0x1F
197  // control codes
198  {
199  m_lastCodeTc[field] += 67;
200 
201  int newccmode = (b1 >> 3) & 1;
202  int newtxtmode = m_txtMode[field*2 + newccmode];
203  if ((b1 & 0x06) == 0x04)
204  {
205  switch (b2)
206  {
207  case 0x29:
208  case 0x2C:
209  case 0x20:
210  case 0x2F:
211  case 0x25:
212  case 0x26:
213  case 0x27:
214  // CC1,2
215  newtxtmode = 0;
216  break;
217  case 0x2A:
218  case 0x2B:
219  // TXT1,2
220  newtxtmode = 1;
221  break;
222  }
223  }
224  m_ccMode[field] = newccmode;
225  m_txtMode[field*2 + newccmode] = newtxtmode;
226  mode = (field << 2) | (newtxtmode << 1) | m_ccMode[field];
227 
228  m_timeCode[mode] = tc;
229  len = m_ccBuf[mode].length();
230 
231  if (b2 & 0x40) //preamble address code (row & indent)
232  {
233  if (newtxtmode)
234  // no address codes in TXT mode?
235  goto skip;
236 
237  m_newRow[mode] = rowdata[((b1 << 1) & 14) | ((b2 >> 5) & 1)];
238  if (m_newRow[mode] == -1)
239  // bogus code?
240  m_newRow[mode] = m_lastRow[mode] + 1;
241 
242  if (b2 & 0x10) //row contains indent flag
243  {
244  m_newCol[mode] = (b2 & 0x0E) << 1;
245  // Encode as 0x7020 or 0x7021 depending on the
246  // underline flag.
247  m_newAttr[mode] = (b2 & 0x1) + 0x20;
248  LOG(VB_VBI, LOG_INFO,
249  QString("cc608 preamble indent, b2=%1")
250  .arg(b2, 2, 16));
251  }
252  else
253  {
254  m_newCol[mode] = 0;
255  m_newAttr[mode] = (b2 & 0xf) + 0x10;
256  // Encode as 0x7010 through 0x702f for the 16 possible
257  // values of b2.
258  LOG(VB_VBI, LOG_INFO,
259  QString("cc608 preamble color change, b2=%1")
260  .arg(b2, 2, 16));
261  }
262 
263  // row, indent, attribute settings are not final
264  // until text code arrives
265  }
266  else
267  {
268  switch (b1 & 0x07)
269  {
270  case 0x00: //attribute
271 #if 0
272  LOG(VB_VBI, LOG_DEBUG,
273  QString("<ATTRIBUTE %1 %2>").arg(b1).arg(b2));
274 #endif
275  break;
276  case 0x01: //midrow or char
277  if (m_newRow[mode])
278  NewRowCC(mode, len);
279 
280  switch (b2 & 0x70)
281  {
282  case 0x20: //midrow attribute change
283  LOG(VB_VBI, LOG_INFO,
284  QString("cc608 mid-row color change, b2=%1")
285  .arg(b2, 2, 16));
286  // Encode as 0x7000 through 0x700f for the
287  // 16 possible values of b2.
288  m_ccBuf[mode] += ' ';
289  m_ccBuf[mode] += QChar(0x7000 + (b2 & 0xf));
290  m_col[mode]++;
291  break;
292  case 0x30: //special character..
293  m_ccBuf[mode] += specialchar[b2 & 0x0f];
294  m_col[mode]++;
295  break;
296  }
297  break;
298  case 0x02: //extended char
299  // extended char is preceded by alternate char
300  // - if there's no alternate, it could be noise
301  if (!len)
302  break;
303 
304  if (b2 & 0x30)
305  {
306  m_ccBuf[mode].remove(len - 1, 1);
307  m_ccBuf[mode] += extendedchar2[b2 - 0x20];
308  break;
309  }
310  break;
311  case 0x03: //extended char
312  // extended char is preceded by alternate char
313  // - if there's no alternate, it could be noise
314  if (!len)
315  break;
316 
317  if (b2 & 0x30)
318  {
319  m_ccBuf[mode].remove(len - 1, 1);
320  m_ccBuf[mode] += extendedchar3[b2 - 0x20];
321  break;
322  }
323  break;
324  case 0x04: //misc
325  case 0x05: //misc + F
326 #if 0
327  LOG(VB_VBI, LOG_DEBUG,
328  QString("ccmode %1 cmd %2").arg(m_ccMode)
329  .arg(b2, 2, 16, '0'));
330 #endif
331  switch (b2)
332  {
333  case 0x21: //backspace
334  // add backspace if line has been encoded already
335  if (m_newRow[mode])
336  len = NewRowCC(mode, len);
337 
338  if (len == 0 ||
339  m_ccBuf[mode].startsWith("\b"))
340  {
341  m_ccBuf[mode] += '\b';
342  m_col[mode]--;
343  }
344  else
345  {
346  m_ccBuf[mode].remove(len - 1, 1);
347  m_col[mode]--;
348  }
349  break;
350  case 0x25: //2 row caption
351  case 0x26: //3 row caption
352  case 0x27: //4 row caption
353  if (m_style[mode] == CC_STYLE_PAINT && len)
354  {
355  // flush
356  BufferCC(mode, len, 0);
357  m_ccBuf[mode] = "";
358  m_row[mode] = 0;
359  m_col[mode] = 0;
360  }
361  else if (m_style[mode] == CC_STYLE_POPUP)
362  ResetCC(mode);
363 
364  m_rowCount[mode] = b2 - 0x25 + 2;
365  m_style[mode] = CC_STYLE_ROLLUP;
366  break;
367  case 0x2D: //carriage return
368  if (m_style[mode] != CC_STYLE_ROLLUP)
369  break;
370 
371  if (m_newRow[mode])
372  m_row[mode] = m_newRow[mode];
373 
374  // flush if there is text or need to scroll
375  // TODO: decode ITV (WebTV) link in TXT2
376  if (len || (m_row[mode] != 0 && !m_lineCont[mode] &&
377  (!newtxtmode || m_row[mode] >= 16)))
378  {
379  BufferCC(mode, len, 0);
380  }
381 
382  if (newtxtmode)
383  {
384  if (m_row[mode] < 16)
385  m_newRow[mode] = m_row[mode] + 1;
386  else
387  // scroll up previous lines
388  m_newRow[mode] = 16;
389  }
390 
391  m_ccBuf[mode] = "";
392  m_col[mode] = 0;
393  m_lineCont[mode] = 0;
394  break;
395 
396  case 0x29:
397  // resume direct caption (paint-on style)
398  if (m_style[mode] == CC_STYLE_ROLLUP && len)
399  {
400  // flush
401  BufferCC(mode, len, 0);
402  m_ccBuf[mode] = "";
403  m_row[mode] = 0;
404  m_col[mode] = 0;
405  }
406  else if (m_style[mode] == CC_STYLE_POPUP)
407  ResetCC(mode);
408 
409  m_style[mode] = CC_STYLE_PAINT;
410  m_rowCount[mode] = 0;
411  m_lineCont[mode] = 0;
412  break;
413 
414  case 0x2B: //resume text display
415  m_resumeText[mode] = 1;
416  if (m_row[mode] == 0)
417  {
418  m_newRow[mode] = 1;
419  m_newCol[mode] = 0;
420  m_newAttr[mode] = 0;
421  }
422  m_style[mode] = CC_STYLE_ROLLUP;
423  break;
424  case 0x2C: //erase displayed memory
425  if (m_ignoreTimeCode ||
426  (tc - m_lastClr[mode]) > 5000 ||
427  m_lastClr[mode] == 0)
428  {
429  // don't overflow the frontend with
430  // too many redundant erase codes
431  BufferCC(mode, 0, 1);
432  }
433  if (m_style[mode] != CC_STYLE_POPUP)
434  {
435  m_row[mode] = 0;
436  m_col[mode] = 0;
437  }
438  m_lineCont[mode] = 0;
439  break;
440 
441  case 0x20: //resume caption (pop-up style)
442  if (m_style[mode] != CC_STYLE_POPUP)
443  {
444  if (len)
445  // flush
446  BufferCC(mode, len, 0);
447  m_ccBuf[mode] = "";
448  m_row[mode] = 0;
449  m_col[mode] = 0;
450  }
451  m_style[mode] = CC_STYLE_POPUP;
452  m_rowCount[mode] = 0;
453  m_lineCont[mode] = 0;
454  break;
455  case 0x2F: //end caption + swap memory
456  if (m_style[mode] != CC_STYLE_POPUP)
457  {
458  if (len)
459  // flush
460  BufferCC(mode, len, 0);
461  }
462  else if (m_ignoreTimeCode ||
463  (tc - m_lastClr[mode]) > 5000 ||
464  m_lastClr[mode] == 0)
465  {
466  // clear and flush
467  BufferCC(mode, len, 1);
468  }
469  else if (len)
470  {
471  // flush
472  BufferCC(mode, len, 0);
473  }
474  m_ccBuf[mode] = "";
475  m_row[mode] = 0;
476  m_col[mode] = 0;
477  m_style[mode] = CC_STYLE_POPUP;
478  m_rowCount[mode] = 0;
479  m_lineCont[mode] = 0;
480  break;
481 
482  case 0x2A: //text restart
483  // clear display
484  BufferCC(mode, 0, 1);
485  ResetCC(mode);
486  // TXT starts at row 1
487  m_newRow[mode] = 1;
488  m_newCol[mode] = 0;
489  m_newAttr[mode] = 0;
490  m_style[mode] = CC_STYLE_ROLLUP;
491  break;
492 
493  case 0x2E: //erase non-displayed memory
494  ResetCC(mode);
495  break;
496  }
497  break;
498  case 0x07: //misc (TAB)
499  if (m_newRow[mode])
500  {
501  m_newCol[mode] += (b2 & 0x03);
502  NewRowCC(mode, len);
503  }
504  else
505  {
506  // illegal?
507  for (int x = 0; x < (b2 & 0x03); x++)
508  {
509  m_ccBuf[mode] += ' ';
510  m_col[mode]++;
511  }
512  }
513  break;
514  }
515  }
516  }
517 
518  skip:
519  for (mode = field*4; mode < (field*4 + 4); mode++)
520  {
521  len = m_ccBuf[mode].length();
522  if ((m_ignoreTimeCode || ((tc - m_timeCode[mode]) > 100)) &&
523  (m_style[mode] != CC_STYLE_POPUP) && len)
524  {
525  // flush unfinished line if waiting too long
526  // in paint-on or scroll-up mode
527  m_timeCode[mode] = tc;
528  BufferCC(mode, len, 0);
529  m_ccBuf[mode] = "";
530  m_row[mode] = m_lastRow[mode];
531  m_lineCont[mode] = 1;
532  }
533  }
534 
535  if (data != m_lastCode[field])
536  {
537  m_lastCode[field] = data;
538  m_lastCodeTc[field] = tc;
539  }
540  m_lastTc[field] = tc;
541 }
542 
543 int CC608Decoder::FalseDup(int tc, int field, int data)
544 {
545  int b1 = data & 0x7f;
546  int b2 = (data >> 8) & 0x7f;
547 
548  if (m_ignoreTimeCode)
549  {
550  // most digital streams with encoded VBI
551  // have duplicate control codes;
552  // suppress every other repeated control code
553  if ((data == m_lastCode[field]) &&
554  ((b1 & 0x70) == 0x10))
555  {
556  m_lastCode[field] = -1;
557  return 1;
558  }
559  return 0;
560  }
561 
562  // bttv-0.9 VBI reads are pretty reliable (1 read/33367us).
563  // bttv-0.7 reads don't seem to work as well so if read intervals
564  // vary from this, be more conservative in detecting duplicate
565  // CC codes.
566  int dup_text_fudge = 0;
567  int dup_ctrl_fudge = 0;
568  if (m_badVbi[field] < 100 && b1 != 0 && b2 != 0)
569  {
570  int d = tc - m_lastTc[field];
571  if (d < 25 || d > 42)
572  m_badVbi[field]++;
573  else if (m_badVbi[field] > 0)
574  m_badVbi[field]--;
575  }
576  if (m_badVbi[field] < 4)
577  {
578  // this should pick up all codes
579  dup_text_fudge = -2;
580  // this should pick up 1st, 4th, 6th, 8th, ... codes
581  dup_ctrl_fudge = 33 - 4;
582  }
583  else
584  {
585  dup_text_fudge = 4;
586  dup_ctrl_fudge = 33 - 4;
587  }
588 
589  if (data == m_lastCode[field])
590  {
591  if ((b1 & 0x70) == 0x10)
592  {
593  if (tc > (m_lastCodeTc[field] + 67 + dup_ctrl_fudge))
594  return 0;
595  }
596  else if (b1)
597  {
598  // text, XDS
599  if (tc > (m_lastCodeTc[field] + 33 + dup_text_fudge))
600  return 0;
601  }
602 
603  return 1;
604  }
605 
606  return 0;
607 }
608 
609 void CC608Decoder::ResetCC(int mode)
610 {
611 // m_lastRow[mode] = 0;
612 // m_newRow[mode] = 0;
613 // m_newCol[mode] = 0;
614 // m_timeCode[mode] = 0;
615  m_row[mode] = 0;
616  m_col[mode] = 0;
617  m_rowCount[mode] = 0;
618 // m_style[mode] = CC_STYLE_POPUP;
619  m_lineCont[mode] = 0;
620  m_resumeText[mode] = 0;
621  m_lastClr[mode] = 0;
622  m_ccBuf[mode] = "";
623 }
624 
625 QString CC608Decoder::ToASCII(const QString &cc608str, bool suppress_unknown)
626 {
627  QString ret = "";
628 
629  for (auto cp : qAsConst(cc608str))
630  {
631  int cpu = cp.unicode();
632  if (cpu == 0)
633  break;
634  switch (cpu)
635  {
636  case 0x2120 : ret += "(SM)"; break;
637  case 0x2122 : ret += "(TM)"; break;
638  case 0x2014 : ret += "(--)"; break;
639  case 0x201C : ret += "``"; break;
640  case 0x201D : ret += "''"; break;
641  case 0x250C : ret += "|-"; break;
642  case 0x2510 : ret += "-|"; break;
643  case 0x2514 : ret += "|_"; break;
644  case 0x2518 : ret += "_|"; break;
645  case 0x2588 : ret += "[]"; break;
646  case 0x266A : ret += "o/~"; break;
647  case '\b' : ret += "\\b"; break;
648  default :
649  if (cpu >= 0x7000 && cpu < 0x7000 + 0x30)
650  {
651  if (!suppress_unknown)
652  ret += QString("[%1]").arg(cpu, 2, 16);
653  }
654  else if (cpu <= 0x80)
655  ret += QString(cp.toLatin1());
656  else if (!suppress_unknown)
657  ret += QString("{%1}").arg(cpu, 2, 16);
658  }
659  }
660 
661  return ret;
662 }
663 
664 void CC608Decoder::BufferCC(int mode, int len, int clr)
665 {
666  QByteArray tmpbuf;
667  if (len)
668  {
669  // calculate UTF-8 encoding length
670  tmpbuf = m_ccBuf[mode].toUtf8();
671  len = min(tmpbuf.length(), 255);
672  }
673 
674  unsigned char *bp = m_rbuf;
675  *(bp++) = m_row[mode];
676  *(bp++) = m_rowCount[mode];
677  *(bp++) = m_style[mode];
678  // overload resumetext field
679  unsigned char f = m_resumeText[mode];
680  f |= mode << 4;
681  if (m_lineCont[mode])
682  f |= CC_LINE_CONT;
683  *(bp++) = f;
684  *(bp++) = clr;
685  *(bp++) = len;
686  if (len)
687  {
688  memcpy(bp,
689  tmpbuf.constData(),
690  len);
691  len += sizeof(ccsubtitle);
692  }
693  else
694  len = sizeof(ccsubtitle);
695 
696  if ((len != 0) && VERBOSE_LEVEL_CHECK(VB_VBI, LOG_INFO))
697  {
698  LOG(VB_VBI, LOG_INFO, QString("### %1 %2 %3 %4 %5 %6 %7 - '%8'")
699  .arg(m_timeCode[mode], 10)
700  .arg(m_row[mode], 2).arg(m_rowCount[mode])
701  .arg(m_style[mode]).arg(f, 2, 16)
702  .arg(clr).arg(len, 3)
703  .arg(ToASCII(QString::fromUtf8(tmpbuf.constData(), len), false)));
704  }
705 
706  m_reader->AddTextData(m_rbuf, len, m_timeCode[mode], 'C');
707  int ccmode = m_rbuf[3] & CC_MODE_MASK;
708  int stream = -1;
709  switch (ccmode)
710  {
711  case CC_CC1: stream = 0; break;
712  case CC_CC2: stream = 1; break;
713  case CC_CC3: stream = 2; break;
714  case CC_CC4: stream = 3; break;
715  }
716  if (stream >= 0)
717  m_lastSeen[stream] = time(nullptr);
718 
719  m_resumeText[mode] = 0;
720  if (clr && !len)
721  m_lastClr[mode] = m_timeCode[mode];
722  else if (len)
723  m_lastClr[mode] = 0;
724 }
725 
726 int CC608Decoder::NewRowCC(int mode, int len)
727 {
728  if (m_style[mode] == CC_STYLE_ROLLUP)
729  {
730  // previous line was likely missing a carriage return
731  m_row[mode] = m_newRow[mode];
732  if (len)
733  {
734  BufferCC(mode, len, 0);
735  m_ccBuf[mode] = "";
736  len = 0;
737  }
738  m_col[mode] = 0;
739  m_lineCont[mode] = 0;
740  }
741  else
742  {
743  // popup/paint style
744 
745  if (m_row[mode] == 0)
746  {
747  if (len == 0)
748  m_row[mode] = m_newRow[mode];
749  else
750  {
751  // previous line was missing a row address
752  // - assume it was one row up
753  m_ccBuf[mode] += '\n';
754  len++;
755  if (m_row[mode] == 0)
756  m_row[mode] = m_newRow[mode] - 1;
757  else
758  m_row[mode]--;
759  }
760  }
761  else if (m_newRow[mode] > m_lastRow[mode])
762  {
763  // next line can be more than one row away
764  for (int i = 0; i < (m_newRow[mode] - m_lastRow[mode]); i++)
765  {
766  m_ccBuf[mode] += '\n';
767  len++;
768  }
769  m_col[mode] = 0;
770  }
771  else if (m_newRow[mode] == m_lastRow[mode])
772  {
773  // same row
774  if (m_newCol[mode] >= m_col[mode])
775  {
776  // new line appends to current line
777  m_newCol[mode] -= m_col[mode];
778  }
779  else
780  {
781  // new line overwrites current line;
782  // could be legal (overwrite spaces?) but
783  // more likely we have bad address codes
784  // - just move to next line; may exceed row 15
785  // but frontend will adjust
786  m_ccBuf[mode] += '\n';
787  len++;
788  m_col[mode] = 0;
789  }
790  }
791  else
792  {
793  // next line goes upwards (not legal?)
794  // - flush
795  BufferCC(mode, len, 0);
796  m_ccBuf[mode] = "";
797  m_row[mode] = m_newRow[mode];
798  m_col[mode] = 0;
799  m_lineCont[mode] = 0;
800  len = 0;
801  }
802  }
803 
804  m_lastRow[mode] = m_newRow[mode];
805  m_newRow[mode] = 0;
806 
807  int limit = m_newCol[mode];
808  for (int x = 0; x < limit; x++)
809  {
810  m_ccBuf[mode] += ' ';
811  len++;
812  m_col[mode]++;
813  }
814 
815  if (m_newAttr[mode])
816  {
817  m_ccBuf[mode] += QChar(m_newAttr[mode] + 0x7000);
818  len++;
819  }
820 
821  m_newCol[mode] = 0;
822  m_newAttr[mode] = 0;
823 
824  return len;
825 }
826 
827 
828 static bool IsPrintable(char c)
829 {
830  return !(((c) & 0x7F) < 0x20 || ((c) & 0x7F) > 0x7E);
831 }
832 
833 static char Printable(char c)
834 {
835  return IsPrintable(c) ? ((c) & 0x7F) : '.';
836 }
837 
838 #if 0
839 static int OddParity(unsigned char c)
840 {
841  c ^= (c >> 4); c ^= (c >> 2); c ^= (c >> 1);
842  return c & 1;
843 }
844 #endif
845 
846 // // // // // // // // // // // // // // // // // // // // // // // //
847 // // // // // // // // // // // VPS // // // // // // // // // // //
848 // // // // // // // // // // // // // // // // // // // // // // // //
849 
850 static void DumpPIL(int pil)
851 {
852  int day = (pil >> 15);
853  int mon = (pil >> 11) & 0xF;
854  int hour = (pil >> 6 ) & 0x1F;
855  int min = (pil ) & 0x3F;
856 
857 #define PIL_TIME(day, mon, hour, min) \
858  (((day) << 15) + ((mon) << 11) + ((hour) << 6) + ((min) << 0))
859 
860  if (pil == PIL_TIME(0, 15, 31, 63))
861  LOG(VB_VBI, LOG_INFO, " PDC: Timer-control (no PDC)");
862  else if (pil == PIL_TIME(0, 15, 30, 63))
863  LOG(VB_VBI, LOG_INFO, " PDC: Recording inhibit/terminate");
864  else if (pil == PIL_TIME(0, 15, 29, 63))
865  LOG(VB_VBI, LOG_INFO, " PDC: Interruption");
866  else if (pil == PIL_TIME(0, 15, 28, 63))
867  LOG(VB_VBI, LOG_INFO, " PDC: Continue");
868  else if (pil == PIL_TIME(31, 15, 31, 63))
869  LOG(VB_VBI, LOG_INFO, " PDC: No time");
870  else
871  LOG(VB_VBI, LOG_INFO, QString(" PDC: %1, 200X-%2-%3 %4:%5")
872  .arg(pil).arg(mon).arg(day).arg(hour).arg(min));
873 #undef PIL_TIME
874 }
875 
876 void CC608Decoder::DecodeVPS(const unsigned char *buf)
877 {
878  int c = vbi_bit_reverse[buf[1]];
879 
880  if ((int8_t) c < 0)
881  {
882  m_vpsLabel[m_vpsL] = 0;
883  memcpy(m_vpsPrLabel, m_vpsLabel, sizeof(m_vpsPrLabel));
884  m_vpsL = 0;
885  }
886  c &= 0x7F;
888  m_vpsL = (m_vpsL + 1) % 16;
889 
890  LOG(VB_VBI, LOG_INFO, QString("VPS: 3-10: %1 %2 %3 %4 %5 %6 %7 %8 (\"%9\")")
891  .arg(buf[0]).arg(buf[1]).arg(buf[2]).arg(buf[3]).arg(buf[4])
892  .arg(buf[5]).arg(buf[6]).arg(buf[7]).arg(m_vpsPrLabel));
893 
894  int pcs = buf[2] >> 6;
895  int cni = + ((buf[10] & 3) << 10)
896  + ((buf[11] & 0xC0) << 2)
897  + ((buf[8] & 0xC0) << 0)
898  + (buf[11] & 0x3F);
899  int pil = ((buf[8] & 0x3F) << 14) + (buf[9] << 6) + (buf[10] >> 2);
900  int pty = buf[12];
901 
902  LOG(VB_VBI, LOG_INFO, QString("CNI: %1 PCS: %2 PTY: %3 ")
903  .arg(cni).arg(pcs).arg(pty));
904 
905  DumpPIL(pil);
906 
907  // c & 0xf0;
908 }
909 
910 // // // // // // // // // // // // // // // // // // // // // // // //
911 // // // // // // // // // // // WSS // // // // // // // // // // //
912 // // // // // // // // // // // // // // // // // // // // // // // //
913 
914 void CC608Decoder::DecodeWSS(const unsigned char *buf)
915 {
916  static const std::array<const int,8> kWssBits { 0, 0, 0, 1, 0, 1, 1, 1 };
917  uint wss = 0;
918 
919  for (uint i = 0; i < 16; i++)
920  {
921  uint b1 = kWssBits[buf[i] & 7];
922  uint b2 = kWssBits[(buf[i] >> 3) & 7];
923 
924  if (b1 == b2)
925  return;
926  wss |= b2 << i;
927  }
928  unsigned char parity = wss & 0xf;
929  parity ^= parity >> 2;
930  parity ^= parity >> 1;
931 
932  LOG(VB_VBI, LOG_INFO,
933  QString("WSS: %1; %2 mode; %3 color coding;\n\t\t\t"
934  " %4 helper; reserved b7=%5; %6\n\t\t\t"
935  " open subtitles: %7; %scopyright %8; copying %9")
936  .arg(QString::fromStdString(formats[wss & 7]))
937  .arg((wss & 0x0010) ? "film" : "camera")
938  .arg((wss & 0x0020) ? "MA/CP" : "standard")
939  .arg((wss & 0x0040) ? "modulated" : "no")
940  .arg(!!(wss & 0x0080))
941  .arg((wss & 0x0100) ? "have TTX subtitles; " : "")
942  .arg(QString::fromStdString(subtitles[(wss >> 9) & 3]))
943  .arg((wss & 0x0800) ? "surround sound; " : "")
944  .arg((wss & 0x1000) ? "asserted" : "unknown")
945  .arg((wss & 0x2000) ? "restricted" : "not restricted"));
946 
947  if (parity & 1)
948  {
949  m_wssFlags = wss;
950  m_wssValid = true;
951  }
952 }
953 
954 QString CC608Decoder::XDSDecodeString(const vector<unsigned char> &buf,
955  uint start, uint end) const
956 {
957 #if DEBUG_XDS
958  for (uint i = start; (i < buf.size()) && (i < end); i++)
959  {
960  LOG(VB_VBI, LOG_INFO, QString("%1: 0x%2 -> 0x%3 %4")
961  .arg(i,2)
962  .arg(buf[i],2,16,QChar('0'))
963  .arg(CharCC(buf[i]).unicode(),2,16,QChar('0'))
964  .arg(CharCC(buf[i])));
965  }
966 #endif // DEBUG_XDS
967 
968  QString tmp = "";
969  for (uint i = start; (i < buf.size()) && (i < end); i++)
970  {
971  if (buf[i] > 0x0)
972  tmp += CharCC(buf[i]);
973  }
974 
975 #if DEBUG_XDS
976  LOG(VB_VBI, LOG_INFO, QString("XDSDecodeString: '%1'").arg(tmp));
977 #endif // DEBUG_XDS
978 
979  return tmp.trimmed();
980 }
981 
982 static bool is_better(const QString &newStr, const QString &oldStr)
983 {
984  if (!newStr.isEmpty() && newStr != oldStr &&
985  (newStr != oldStr.left(newStr.length())))
986  {
987  if (oldStr.isEmpty())
988  return true;
989 
990  // check if the string contains any bogus characters
991  for (auto ch : qAsConst(newStr))
992  if (ch.toLatin1() < 0x20)
993  return false;
994 
995  return true;
996  }
997  return false;
998 }
999 
1001 {
1002  QMutexLocker locker(&m_xdsLock);
1003  return m_xdsRatingSystems[(future) ? 1 : 0];
1004 }
1005 
1006 uint CC608Decoder::GetRating(uint i, bool future) const
1007 {
1008  QMutexLocker locker(&m_xdsLock);
1009  return m_xdsRating[(future) ? 1 : 0][i & 0x3] & 0x7;
1010 }
1011 
1012 QString CC608Decoder::GetRatingString(uint i, bool future) const
1013 {
1014  QMutexLocker locker(&m_xdsLock);
1015 
1016  const std::array<const QString,4> prefix { "MPAA-", "TV-", "CE-", "CF-" };
1017  const std::array<const std::array<const QString,8>,4> mainStr
1018  {{
1019  { "NR", "G", "PG", "PG-13", "R", "NC-17", "X", "NR" },
1020  { "NR", "Y", "Y7", "G", "PG", "14", "MA", "NR" },
1021  { "E", "C", "C8+", "G", "PG", "14+", "18+", "NR" },
1022  { "E", "G", "8+", "13+", "16+", "18+", "NR", "NR" },
1023  }};
1024 
1025  QString main = prefix[i] + mainStr[i][GetRating(i, future)];
1026 
1027  if (kRatingTPG == i)
1028  {
1029  uint cf = (future) ? 1 : 0;
1030  if (!(m_xdsRating[cf][i]&0xF0))
1031  return main;
1032 
1033  main += " ";
1034  // TPG flags
1035  if (m_xdsRating[cf][i] & 0x80)
1036  main += "D"; // Dialog
1037  if (m_xdsRating[cf][i] & 0x40)
1038  main += "V"; // Violence
1039  if (m_xdsRating[cf][i] & 0x20)
1040  main += "S"; // Sex
1041  if (m_xdsRating[cf][i] & 0x10)
1042  main += "L"; // Language
1043  }
1044 
1045  return main;
1046 }
1047 
1048 QString CC608Decoder::GetProgramName(bool future) const
1049 {
1050  QMutexLocker locker(&m_xdsLock);
1051  return m_xdsProgramName[(future) ? 1 : 0];
1052 }
1053 
1054 QString CC608Decoder::GetProgramType(bool future) const
1055 {
1056  QMutexLocker locker(&m_xdsLock);
1057  const vector<uint> &program_type = m_xdsProgramType[(future) ? 1 : 0];
1058  QString tmp = "";
1059 
1060  for (size_t i = 0; i < program_type.size(); i++)
1061  {
1062  if (i != 0)
1063  tmp += ", ";
1064  tmp += m_xdsProgramTypeString[program_type[i]];
1065  }
1066 
1067  return tmp;
1068 }
1069 
1070 QString CC608Decoder::GetXDS(const QString &key) const
1071 {
1072  QMutexLocker locker(&m_xdsLock);
1073 
1074  if (key == "ratings")
1075  return QString::number(GetRatingSystems(false));
1076  if (key.startsWith("has_rating_"))
1077  return ((1<<key.right(1).toUInt()) & GetRatingSystems(false))?"1":"0";
1078  if (key.startsWith("rating_"))
1079  return GetRatingString(key.right(1).toUInt(), false);
1080 
1081  if (key == "future_ratings")
1082  return QString::number(GetRatingSystems(true));
1083  if (key.startsWith("has_future_rating_"))
1084  return ((1<<key.right(1).toUInt()) & GetRatingSystems(true))?"1":"0";
1085  if (key.startsWith("future_rating_"))
1086  return GetRatingString(key.right(1).toUInt(), true);
1087 
1088  if (key == "programname")
1089  return GetProgramName(false);
1090  if (key == "future_programname")
1091  return GetProgramName(true);
1092 
1093  if (key == "programtype")
1094  return GetProgramType(false);
1095  if (key == "future_programtype")
1096  return GetProgramType(true);
1097 
1098  if (key == "callsign")
1099  return m_xdsNetCall;
1100  if (key == "channame")
1101  return m_xdsNetName;
1102  if (key == "tsid")
1103  return QString::number(m_xdsTsid);
1104 
1105  return QString();
1106 }
1107 
1108 static std::array<const int,16> b1_to_service
1109 { -1, // 0x0
1110  0, 0, //0x1,0x2 -- Current
1111  1, 1, //0x3,0x4 -- Future
1112  2, 2, //0x5,0x6 -- Channel
1113  3, 3, //0x7,0x8 -- Misc
1114  4, 4, //0x9,0xA -- Public Service
1115  5, 5, //0xB,0xC -- Reserved
1116  6, 6, //0xD,0xE -- Private Data
1117  -1, // 0xF
1118 };
1119 
1120 bool CC608Decoder::XDSDecode(int field, int b1, int b2)
1121 {
1122  if (field == 0)
1123  return false; // XDS is only on second field
1124 
1125 #if DEBUG_XDS
1126  LOG(VB_VBI, LOG_INFO,
1127  QString("XDSDecode: 0x%1 0x%2 '%3%4' xds[%5]=%6 in XDS %7")
1128  .arg(b1,2,16,QChar('0')).arg(b2,2,16,QChar('0'))
1129  .arg((CharCC(b1).unicode()>0x20) ? CharCC(b1) : QChar(' '))
1130  .arg((CharCC(b2).unicode()>0x20) ? CharCC(b2) : QChar(' '))
1131  .arg(field).arg(m_xds[field])
1132  .arg(m_xdsCurService));
1133 #else
1134  (void) field;
1135 #endif // DEBUG_XDS
1136 
1137  if (m_xdsCurService < 0)
1138  {
1139  if (b1 > 0x0f)
1140  return false;
1141 
1143 
1144  if (m_xdsCurService < 0)
1145  return false;
1146 
1147  if (b1 & 1)
1148  {
1149  m_xdsBuf[m_xdsCurService].clear(); // if start of service clear buffer
1150 #if DEBUG_XDS
1151  LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Starting XDS %1").arg(m_xdsCurService));
1152 #endif // DEBUG_XDS
1153  }
1154  }
1155  else if ((0x0 < b1) && (b1 < 0x0f))
1156  { // switch to different service
1158 #if DEBUG_XDS
1159  LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Resuming XDS %1").arg(m_xdsCurService));
1160 #endif // DEBUG_XDS
1161  }
1162 
1163  if (m_xdsCurService < 0)
1164  return false;
1165 
1166  m_xdsBuf[m_xdsCurService].push_back(b1);
1167  m_xdsBuf[m_xdsCurService].push_back(b2);
1168 
1169  if (b1 == 0x0f) // end of packet
1170  {
1171 #if DEBUG_XDS
1172  LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Ending XDS %1").arg(m_xdsCurService));
1173 #endif // DEBUG_XDS
1176  m_xdsBuf[m_xdsCurService].clear();
1177  m_xdsCurService = -1;
1178  }
1179  else if ((0x10 <= b1) && (b1 <= 0x1f)) // suspension of XDS packet
1180  {
1181 #if DEBUG_XDS
1182  LOG(VB_VBI, LOG_INFO, QString("XDSDecode: Suspending XDS %1 on 0x%2")
1183  .arg(m_xdsCurService).arg(b1,2,16,QChar('0')));
1184 #endif // DEBUG_XDS
1185  m_xdsCurService = -1;
1186  }
1187 
1188  return true;
1189 }
1190 
1191 void CC608Decoder::XDSPacketParse(const vector<unsigned char> &xds_buf)
1192 {
1193  QMutexLocker locker(&m_xdsLock);
1194 
1195  bool handled = false;
1196  int xds_class = xds_buf[0];
1197 
1198  if (!xds_class)
1199  return;
1200 
1201  if ((xds_class == 0x01) || (xds_class == 0x03)) // cont code 2 and 4, resp.
1202  handled = XDSPacketParseProgram(xds_buf, (xds_class == 0x03));
1203  else if (xds_class == 0x05) // cont code: 0x06
1204  handled = XDSPacketParseChannel(xds_buf);
1205  else if ((xds_class == 0x07) || // cont code: 0x08 // misc.
1206  (xds_class == 0x09) || // cont code: 0x0a // public (aka weather)
1207  (xds_class == 0x0b)) // cont code: 0x0c // reserved
1208  ;
1209  else if (xds_class == 0x0d) // cont code: 0x0e
1210  handled = true; // undefined
1211 
1212  if (!handled)
1213  {
1214 #if DEBUG_XDS
1215  LOG(VB_VBI, LOG_INFO, QString("XDS: ") +
1216  QString("Unhandled packet (0x%1 0x%2) sz(%3) '%4'")
1217  .arg(xds_buf[0],0,16).arg(xds_buf[1],0,16)
1218  .arg(xds_buf.size())
1219  .arg(XDSDecodeString(xds_buf, 2, xds_buf.size() - 2)));
1220 #endif
1221  }
1222 }
1223 
1224 bool CC608Decoder::XDSPacketCRC(const vector<unsigned char> &xds_buf)
1225 {
1226  /* Check the checksum for validity of the packet. */
1227  int sum = 0;
1228  for (size_t i = 0; i < xds_buf.size() - 1; i++)
1229  sum += xds_buf[i];
1230 
1231  if ((((~sum) & 0x7f) + 1) != xds_buf[xds_buf.size() - 1])
1232  {
1233  m_xdsCrcFailed++;
1234 
1235  LOG(VB_VBI, LOG_ERR, QString("XDS: failed CRC %1 of %2")
1237 
1238  return false;
1239  }
1240 
1241  m_xdsCrcPassed++;
1242  return true;
1243 }
1244 
1246  const vector<unsigned char> &xds_buf, bool future)
1247 {
1248  bool handled = true;
1249  int b2 = xds_buf[1];
1250  int cf = (future) ? 1 : 0;
1251  QString loc = (future) ? "XDS: Future " : "XDS: Current ";
1252 
1253  if ((b2 == 0x01) && (xds_buf.size() >= 6))
1254  {
1255  uint min = xds_buf[2] & 0x3f;
1256  uint hour = xds_buf[3] & 0x0f;
1257  uint day = xds_buf[4] & 0x1f;
1258  uint month = xds_buf[5] & 0x0f;
1259  month = (month < 1 || month > 12) ? 0 : month;
1260 
1261  LOG(VB_VBI, LOG_INFO, loc +
1262  QString("Start Time %1/%2 %3:%4%5")
1263  .arg(month).arg(day).arg(hour).arg(min / 10).arg(min % 10));
1264  }
1265  else if ((b2 == 0x02) && (xds_buf.size() >= 4))
1266  {
1267  uint length_min = xds_buf[2] & 0x3f;
1268  uint length_hour = xds_buf[3] & 0x3f;
1269  uint length_elapsed_min = 0;
1270  uint length_elapsed_hour = 0;
1271  uint length_elapsed_secs = 0;
1272  if (xds_buf.size() > 6)
1273  {
1274  length_elapsed_min = xds_buf[4] & 0x3f;
1275  length_elapsed_hour = xds_buf[5] & 0x3f;
1276  }
1277  if (xds_buf.size() > 8 && xds_buf[7] == 0x40)
1278  length_elapsed_secs = xds_buf[6] & 0x3f;
1279 
1280  QString msg = QString("Program Length %1:%2%3 "
1281  "Time in Show %4:%5%6.%7%8")
1282  .arg(length_hour).arg(length_min / 10).arg(length_min % 10)
1283  .arg(length_elapsed_hour)
1284  .arg(length_elapsed_min / 10).arg(length_elapsed_min % 10)
1285  .arg(length_elapsed_secs / 10).arg(length_elapsed_secs % 10);
1286 
1287  LOG(VB_VBI, LOG_INFO, loc + msg);
1288  }
1289  else if ((b2 == 0x03) && (xds_buf.size() >= 6))
1290  {
1291  QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1292  if (is_better(tmp, m_xdsProgramName[cf]))
1293  {
1294  m_xdsProgramName[cf] = tmp;
1295  LOG(VB_VBI, LOG_INFO, loc + QString("Program Name: '%1'")
1296  .arg(GetProgramName(future)));
1297  }
1298  }
1299  else if ((b2 == 0x04) && (xds_buf.size() >= 6))
1300  {
1301  vector<uint> program_type;
1302  for (size_t i = 2; i < xds_buf.size() - 2; i++)
1303  {
1304  int cur = xds_buf[i] - 0x20;
1305  if (cur >= 0 && cur < 96)
1306  program_type.push_back(cur);
1307  }
1308 
1309  bool unchanged = m_xdsProgramType[cf].size() == program_type.size();
1310  for (uint i = 0; (i < program_type.size()) && unchanged; i++)
1311  unchanged = m_xdsProgramType[cf][i] == program_type[i];
1312 
1313  if (!unchanged)
1314  {
1315  m_xdsProgramType[cf] = program_type;
1316  LOG(VB_VBI, LOG_INFO, loc + QString("Program Type '%1'")
1317  .arg(GetProgramType(future)));
1318  }
1319  }
1320  else if ((b2 == 0x05) && (xds_buf.size() >= 4))
1321  {
1322  uint movie_rating = xds_buf[2] & 0x7;
1323  uint rating_system = (xds_buf[2] >> 3) & 0x7;
1324  uint tv_rating = xds_buf[3] & 0x7;
1325  uint VSL = xds_buf[3] & (0x7 << 3);
1326  uint sel = VSL | rating_system;
1327  if (sel == 3)
1328  {
1329  if (!(kHasCanEnglish & m_xdsRatingSystems[cf]) ||
1330  (tv_rating != GetRating(kRatingCanEnglish, future)))
1331  {
1333  m_xdsRating[cf][kRatingCanEnglish] = tv_rating;
1334  LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1336  }
1337  }
1338  else if (sel == 7)
1339  {
1340  if (!(kHasCanFrench & m_xdsRatingSystems[cf]) ||
1341  (tv_rating != GetRating(kRatingCanFrench, future)))
1342  {
1344  m_xdsRating[cf][kRatingCanFrench] = tv_rating;
1345  LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1346  .arg(GetRatingString(kRatingCanFrench, future)));
1347  }
1348  }
1349  else if (sel == 0x13 || sel == 0x1f)
1350  ; // Reserved according to TVTime code
1351  else if ((rating_system & 0x3) == 1)
1352  {
1353  if (!(kHasTPG & m_xdsRatingSystems[cf]) ||
1354  (tv_rating != GetRating(kRatingTPG, future)))
1355  {
1356  uint f = ((xds_buf[0]<<3) & 0x80) | ((xds_buf[1]<<1) & 0x70);
1357  m_xdsRatingSystems[cf] |= kHasTPG;
1358  m_xdsRating[cf][kRatingTPG] = tv_rating | f;
1359  LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1360  .arg(GetRatingString(kRatingTPG, future)));
1361  }
1362  }
1363  else if (rating_system == 0)
1364  {
1365  if (!(kHasMPAA & m_xdsRatingSystems[cf]) ||
1366  (movie_rating != GetRating(kRatingMPAA, future)))
1367  {
1369  m_xdsRating[cf][kRatingMPAA] = movie_rating;
1370  LOG(VB_VBI, LOG_INFO, loc + QString("VChip %1")
1371  .arg(GetRatingString(kRatingMPAA, future)));
1372  }
1373  }
1374  else
1375  {
1376  LOG(VB_VBI, LOG_ERR, loc +
1377  QString("VChip Unhandled -- rs(%1) rating(%2:%3)")
1378  .arg(rating_system).arg(tv_rating).arg(movie_rating));
1379  }
1380  }
1381 #if 0
1382  else if (b2 == 0x07)
1383  ; // caption
1384  else if (b2 == 0x08)
1385  ; // cgms
1386  else if (b2 == 0x09)
1387  ; // unknown
1388  else if (b2 == 0x0c)
1389  ; // misc
1390  else if (b2 == 0x10 || b2 == 0x13 || b2 == 0x15 || b2 == 0x16 ||
1391  b2 == 0x91 || b2 == 0x92 || b2 == 0x94 || b2 == 0x97)
1392  ; // program description
1393  else if (b2 == 0x86)
1394  ; // audio
1395  else if (b2 == 0x89)
1396  ; // aspect
1397  else if (b2 == 0x8c)
1398  ; // program data
1399 #endif
1400  else
1401  handled = false;
1402 
1403  return handled;
1404 }
1405 
1406 bool CC608Decoder::XDSPacketParseChannel(const vector<unsigned char> &xds_buf)
1407 {
1408  bool handled = true;
1409 
1410  int b2 = xds_buf[1];
1411  if ((b2 == 0x01) && (xds_buf.size() >= 6))
1412  {
1413  QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1414  if (is_better(tmp, m_xdsNetName))
1415  {
1416  LOG(VB_VBI, LOG_INFO, QString("XDS: Network Name '%1'").arg(tmp));
1417  m_xdsNetName = tmp;
1418  }
1419  }
1420  else if ((b2 == 0x02) && (xds_buf.size() >= 6))
1421  {
1422  QString tmp = XDSDecodeString(xds_buf, 2, xds_buf.size() - 2);
1423  if (is_better(tmp, m_xdsNetCall) && (tmp.indexOf(" ") < 0))
1424  {
1425  LOG(VB_VBI, LOG_INFO, QString("XDS: Network Call '%1'").arg(tmp));
1426  m_xdsNetCall = tmp;
1427  }
1428  }
1429  else if ((b2 == 0x04) && (xds_buf.size() >= 6))
1430  {
1431  uint tsid = (xds_buf[2] << 24 | xds_buf[3] << 16 |
1432  xds_buf[4] << 8 | xds_buf[5]);
1433  if (tsid != m_xdsTsid)
1434  {
1435  LOG(VB_VBI, LOG_INFO, QString("XDS: TSID 0x%1").arg(tsid,0,16));
1436  m_xdsTsid = tsid;
1437  }
1438  }
1439  else
1440  handled = false;
1441 
1442  return handled;
1443 }
1444 
1445 static void init_xds_program_type(QString xds_program_type[96])
1446 {
1447  xds_program_type[0] = QCoreApplication::translate("(Categories)",
1448  "Education");
1449  xds_program_type[1] = QCoreApplication::translate("(Categories)",
1450  "Entertainment");
1451  xds_program_type[2] = QCoreApplication::translate("(Categories)",
1452  "Movie");
1453  xds_program_type[3] = QCoreApplication::translate("(Categories)",
1454  "News");
1455  xds_program_type[4] = QCoreApplication::translate("(Categories)",
1456  "Religious");
1457  xds_program_type[5] = QCoreApplication::translate("(Categories)",
1458  "Sports");
1459  xds_program_type[6] = QCoreApplication::translate("(Categories)",
1460  "Other");
1461  xds_program_type[7] = QCoreApplication::translate("(Categories)",
1462  "Action");
1463  xds_program_type[8] = QCoreApplication::translate("(Categories)",
1464  "Advertisement");
1465  xds_program_type[9] = QCoreApplication::translate("(Categories)",
1466  "Animated");
1467  xds_program_type[10] = QCoreApplication::translate("(Categories)",
1468  "Anthology");
1469  xds_program_type[11] = QCoreApplication::translate("(Categories)",
1470  "Automobile");
1471  xds_program_type[12] = QCoreApplication::translate("(Categories)",
1472  "Awards");
1473  xds_program_type[13] = QCoreApplication::translate("(Categories)",
1474  "Baseball");
1475  xds_program_type[14] = QCoreApplication::translate("(Categories)",
1476  "Basketball");
1477  xds_program_type[15] = QCoreApplication::translate("(Categories)",
1478  "Bulletin");
1479  xds_program_type[16] = QCoreApplication::translate("(Categories)",
1480  "Business");
1481  xds_program_type[17] = QCoreApplication::translate("(Categories)",
1482  "Classical");
1483  xds_program_type[18] = QCoreApplication::translate("(Categories)",
1484  "College");
1485  xds_program_type[19] = QCoreApplication::translate("(Categories)",
1486  "Combat");
1487  xds_program_type[20] = QCoreApplication::translate("(Categories)",
1488  "Comedy");
1489  xds_program_type[21] = QCoreApplication::translate("(Categories)",
1490  "Commentary");
1491  xds_program_type[22] = QCoreApplication::translate("(Categories)",
1492  "Concert");
1493  xds_program_type[23] = QCoreApplication::translate("(Categories)",
1494  "Consumer");
1495  xds_program_type[24] = QCoreApplication::translate("(Categories)",
1496  "Contemporary");
1497  xds_program_type[25] = QCoreApplication::translate("(Categories)",
1498  "Crime");
1499  xds_program_type[26] = QCoreApplication::translate("(Categories)",
1500  "Dance");
1501  xds_program_type[27] = QCoreApplication::translate("(Categories)",
1502  "Documentary");
1503  xds_program_type[28] = QCoreApplication::translate("(Categories)",
1504  "Drama");
1505  xds_program_type[29] = QCoreApplication::translate("(Categories)",
1506  "Elementary");
1507  xds_program_type[30] = QCoreApplication::translate("(Categories)",
1508  "Erotica");
1509  xds_program_type[31] = QCoreApplication::translate("(Categories)",
1510  "Exercise");
1511  xds_program_type[32] = QCoreApplication::translate("(Categories)",
1512  "Fantasy");
1513  xds_program_type[33] = QCoreApplication::translate("(Categories)",
1514  "Farm");
1515  xds_program_type[34] = QCoreApplication::translate("(Categories)",
1516  "Fashion");
1517  xds_program_type[35] = QCoreApplication::translate("(Categories)",
1518  "Fiction");
1519  xds_program_type[36] = QCoreApplication::translate("(Categories)",
1520  "Food");
1521  xds_program_type[37] = QCoreApplication::translate("(Categories)",
1522  "Football");
1523  xds_program_type[38] = QCoreApplication::translate("(Categories)",
1524  "Foreign");
1525  xds_program_type[39] = QCoreApplication::translate("(Categories)",
1526  "Fundraiser");
1527  xds_program_type[40] = QCoreApplication::translate("(Categories)",
1528  "Game/Quiz");
1529  xds_program_type[41] = QCoreApplication::translate("(Categories)",
1530  "Garden");
1531  xds_program_type[42] = QCoreApplication::translate("(Categories)",
1532  "Golf");
1533  xds_program_type[43] = QCoreApplication::translate("(Categories)",
1534  "Government");
1535  xds_program_type[44] = QCoreApplication::translate("(Categories)",
1536  "Health");
1537  xds_program_type[45] = QCoreApplication::translate("(Categories)",
1538  "High School");
1539  xds_program_type[46] = QCoreApplication::translate("(Categories)",
1540  "History");
1541  xds_program_type[47] = QCoreApplication::translate("(Categories)",
1542  "Hobby");
1543  xds_program_type[48] = QCoreApplication::translate("(Categories)",
1544  "Hockey");
1545  xds_program_type[49] = QCoreApplication::translate("(Categories)",
1546  "Home");
1547  xds_program_type[50] = QCoreApplication::translate("(Categories)",
1548  "Horror");
1549  xds_program_type[51] = QCoreApplication::translate("(Categories)",
1550  "Information");
1551  xds_program_type[52] = QCoreApplication::translate("(Categories)",
1552  "Instruction");
1553  xds_program_type[53] = QCoreApplication::translate("(Categories)",
1554  "International");
1555  xds_program_type[54] = QCoreApplication::translate("(Categories)",
1556  "Interview");
1557  xds_program_type[55] = QCoreApplication::translate("(Categories)",
1558  "Language");
1559  xds_program_type[56] = QCoreApplication::translate("(Categories)",
1560  "Legal");
1561  xds_program_type[57] = QCoreApplication::translate("(Categories)",
1562  "Live");
1563  xds_program_type[58] = QCoreApplication::translate("(Categories)",
1564  "Local");
1565  xds_program_type[59] = QCoreApplication::translate("(Categories)",
1566  "Math");
1567  xds_program_type[60] = QCoreApplication::translate("(Categories)",
1568  "Medical");
1569  xds_program_type[61] = QCoreApplication::translate("(Categories)",
1570  "Meeting");
1571  xds_program_type[62] = QCoreApplication::translate("(Categories)",
1572  "Military");
1573  xds_program_type[63] = QCoreApplication::translate("(Categories)",
1574  "Miniseries");
1575  xds_program_type[64] = QCoreApplication::translate("(Categories)",
1576  "Music");
1577  xds_program_type[65] = QCoreApplication::translate("(Categories)",
1578  "Mystery");
1579  xds_program_type[66] = QCoreApplication::translate("(Categories)",
1580  "National");
1581  xds_program_type[67] = QCoreApplication::translate("(Categories)",
1582  "Nature");
1583  xds_program_type[68] = QCoreApplication::translate("(Categories)",
1584  "Police");
1585  xds_program_type[69] = QCoreApplication::translate("(Categories)",
1586  "Politics");
1587  xds_program_type[70] = QCoreApplication::translate("(Categories)",
1588  "Premiere");
1589  xds_program_type[71] = QCoreApplication::translate("(Categories)",
1590  "Prerecorded");
1591  xds_program_type[72] = QCoreApplication::translate("(Categories)",
1592  "Product");
1593  xds_program_type[73] = QCoreApplication::translate("(Categories)",
1594  "Professional");
1595  xds_program_type[74] = QCoreApplication::translate("(Categories)",
1596  "Public");
1597  xds_program_type[75] = QCoreApplication::translate("(Categories)",
1598  "Racing");
1599  xds_program_type[76] = QCoreApplication::translate("(Categories)",
1600  "Reading");
1601  xds_program_type[77] = QCoreApplication::translate("(Categories)",
1602  "Repair");
1603  xds_program_type[78] = QCoreApplication::translate("(Categories)",
1604  "Repeat");
1605  xds_program_type[79] = QCoreApplication::translate("(Categories)",
1606  "Review");
1607  xds_program_type[80] = QCoreApplication::translate("(Categories)",
1608  "Romance");
1609  xds_program_type[81] = QCoreApplication::translate("(Categories)",
1610  "Science");
1611  xds_program_type[82] = QCoreApplication::translate("(Categories)",
1612  "Series");
1613  xds_program_type[83] = QCoreApplication::translate("(Categories)",
1614  "Service");
1615  xds_program_type[84] = QCoreApplication::translate("(Categories)",
1616  "Shopping");
1617  xds_program_type[85] = QCoreApplication::translate("(Categories)",
1618  "Soap Opera");
1619  xds_program_type[86] = QCoreApplication::translate("(Categories)",
1620  "Special");
1621  xds_program_type[87] = QCoreApplication::translate("(Categories)",
1622  "Suspense");
1623  xds_program_type[88] = QCoreApplication::translate("(Categories)",
1624  "Talk");
1625  xds_program_type[89] = QCoreApplication::translate("(Categories)",
1626  "Technical");
1627  xds_program_type[90] = QCoreApplication::translate("(Categories)",
1628  "Tennis");
1629  xds_program_type[91] = QCoreApplication::translate("(Categories)",
1630  "Travel");
1631  xds_program_type[92] = QCoreApplication::translate("(Categories)",
1632  "Variety");
1633  xds_program_type[93] = QCoreApplication::translate("(Categories)",
1634  "Video");
1635  xds_program_type[94] = QCoreApplication::translate("(Categories)",
1636  "Weather");
1637  xds_program_type[95] = QCoreApplication::translate("(Categories)",
1638  "Western");
1639 }
CC608Decoder::m_rbuf
unsigned char * m_rbuf
Definition: cc608decoder.h:119
CC_CC4
#define CC_CC4
Definition: format.h:203
CC608Decoder::m_ccMode
int m_ccMode[2]
Definition: cc608decoder.h:96
CC608Decoder::FormatCC
void FormatCC(int tc, int code1, int code2)
Definition: cc608decoder.cpp:49
cc::code2
int code2
Definition: cc.h:14
rowdata
static const std::array< const int, 16 > rowdata
Definition: cc608decoder.cpp:63
bbciplayer.main
main
Definition: bbciplayer.py:263
CC608Decoder::m_newAttr
int m_newAttr[8]
Definition: cc608decoder.h:104
Printable
static char Printable(char c)
Definition: cc608decoder.cpp:833
is_better
static bool is_better(const QString &newStr, const QString &oldStr)
Definition: cc608decoder.cpp:982
init_xds_program_type
static void init_xds_program_type(QString xds_program_type[96])
Definition: cc608decoder.cpp:1445
kHasMPAA
@ kHasMPAA
Definition: cc608decoder.h:28
CC608Decoder::m_lastCode
int m_lastCode[2]
Definition: cc608decoder.h:94
CC608Decoder::m_xdsCurService
int m_xdsCurService
Definition: cc608decoder.h:132
CC608Decoder::m_row
int m_row[8]
Definition: cc608decoder.h:106
CC608Decoder::GetRating
uint GetRating(uint i, bool future) const
Definition: cc608decoder.cpp:1006
CC608Decoder::m_xdsProgramType
vector< uint > m_xdsProgramType[2]
Definition: cc608decoder.h:141
CC608Decoder::m_lineCont
int m_lineCont[8]
Definition: cc608decoder.h:110
CC608Input::AddTextData
virtual void AddTextData(unsigned char *buf, int len, int64_t timecode, char type)=0
kRatingTPG
@ kRatingTPG
Definition: cc608decoder.h:36
d
static const uint16_t * d
Definition: iso6937tables.cpp:1025
CC608Decoder::m_vpsLabel
char m_vpsLabel[20]
Definition: cc608decoder.h:125
format.h
CC608Decoder::m_xdsNetName
QString m_xdsNetName
Definition: cc608decoder.h:144
arg
arg(title).arg(filename).arg(doDelete))
CC608Decoder::m_timeCode
int m_timeCode[8]
Definition: cc608decoder.h:105
CC608Decoder::m_vpsL
int m_vpsL
Definition: cc608decoder.h:126
x0
static int x0
Definition: mythsocket.cpp:59
CC_STYLE_ROLLUP
#define CC_STYLE_ROLLUP
Definition: format.h:192
x3
static int x3
Definition: mythsocket.cpp:62
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
CC_STYLE_POPUP
#define CC_STYLE_POPUP
Definition: format.h:190
b1_to_service
static std::array< const int, 16 > b1_to_service
Definition: cc608decoder.cpp:1109
CC608Input
Definition: cc608decoder.h:19
IsPrintable
static bool IsPrintable(char c)
Definition: cc608decoder.cpp:828
CC608Decoder::GetProgramName
QString GetProgramName(bool future) const
Definition: cc608decoder.cpp:1048
hardwareprofile.distros.mythtv_data.data_mythtv.prefix
string prefix
Definition: data_mythtv.py:40
vbi_bit_reverse
const std::array< const uint8_t, 256 > vbi_bit_reverse
Definition: vbilut.cpp:153
CC608Decoder::m_wssValid
bool m_wssValid
Definition: cc608decoder.h:130
CC608Decoder::XDSDecode
bool XDSDecode(int field, int b1, int b2)
Definition: cc608decoder.cpp:1120
tmp
static guint32 * tmp
Definition: goom_core.cpp:30
CC608Decoder::m_ignoreTimeCode
bool m_ignoreTimeCode
Definition: cc608decoder.h:87
CC608Decoder::ToASCII
static QString ToASCII(const QString &cc608, bool suppress_unknown)
Definition: cc608decoder.cpp:625
CC608Decoder::m_lastSeen
time_t m_lastSeen[4]
Definition: cc608decoder.h:89
cc608decoder.h
CC608Decoder::XDSPacketParse
void XDSPacketParse(const vector< unsigned char > &xds_buf)
Definition: cc608decoder.cpp:1191
mythlogging.h
CC608Decoder::m_xdsCrcPassed
uint m_xdsCrcPassed
Definition: cc608decoder.h:134
vbilut.h
kHasCanEnglish
@ kHasCanEnglish
Definition: cc608decoder.h:30
CC608Decoder::DecodeWSS
void DecodeWSS(const unsigned char *buf)
Definition: cc608decoder.cpp:914
CC608Decoder::XDSPacketParseProgram
bool XDSPacketParseProgram(const vector< unsigned char > &xds_buf, bool future)
Definition: cc608decoder.cpp:1245
CC608Decoder::m_lastRow
int m_lastRow[8]
Definition: cc608decoder.h:101
CC608Decoder::FalseDup
int FalseDup(int tc, int field, int data)
Definition: cc608decoder.cpp:543
CC608Decoder::~CC608Decoder
~CC608Decoder()
Definition: cc608decoder.cpp:44
CC608Decoder::GetRatingString
QString GetRatingString(uint i, bool future) const
Definition: cc608decoder.cpp:1012
CC_CC3
#define CC_CC3
Definition: format.h:202
formats
const std::array< const std::string, 8 > formats
Definition: vbilut.cpp:189
DumpPIL
static void DumpPIL(int pil)
Definition: cc608decoder.cpp:850
CC608Decoder::m_xds
int m_xds[2]
Definition: cc608decoder.h:97
kHasTPG
@ kHasTPG
Definition: cc608decoder.h:29
CC608Decoder::m_lastTc
int m_lastTc[2]
Definition: cc608decoder.h:93
CC_LINE_CONT
#define CC_LINE_CONT
Definition: format.h:195
CC608Decoder::m_xdsBuf
vector< unsigned char > m_xdsBuf[7]
Definition: cc608decoder.h:133
CC608Decoder::GetRatingSystems
uint GetRatingSystems(bool future) const
Definition: cc608decoder.cpp:1000
CC608Decoder::m_xdsNetCall
QString m_xdsNetCall
Definition: cc608decoder.h:143
f
QTextStream t & f
Definition: mythplugins/mytharchive/mytharchivehelper/main.cpp:600
CC608Decoder::m_ccBuf
QString m_ccBuf[8]
Definition: cc608decoder.h:113
CC608Decoder::ResetCC
void ResetCC(int mode)
Definition: cc608decoder.cpp:609
CC608Decoder::m_vpsPrLabel
char m_vpsPrLabel[20]
Definition: cc608decoder.h:124
CC608Decoder::m_reader
CC608Input * m_reader
Definition: cc608decoder.h:85
CC608Decoder::XDSDecodeString
QString XDSDecodeString(const vector< unsigned char > &buf, uint start, uint end) const
Definition: cc608decoder.cpp:954
extendedchar3
static const std::array< const QChar, 32 > extendedchar3
Definition: cc608decoder.cpp:89
uint
unsigned int uint
Definition: compat.h:140
PIL_TIME
#define PIL_TIME(day, mon, hour, min)
CC608Decoder::CharCC
QChar CharCC(int code) const
Definition: cc608decoder.h:70
CC608Decoder::m_xdsCrcFailed
uint m_xdsCrcFailed
Definition: cc608decoder.h:135
musicbrainzngs.compat.unicode
unicode
Definition: compat.py:50
CC608Decoder::m_xdsTsid
uint m_xdsTsid
Definition: cc608decoder.h:145
CC_CC1
#define CC_CC1
Definition: format.h:198
CC608Decoder::m_xdsLock
QMutex m_xdsLock
Definition: cc608decoder.h:137
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
CC608Decoder::m_newCol
int m_newCol[8]
Definition: cc608decoder.h:103
CC608Decoder::m_col
int m_col[8]
Definition: cc608decoder.h:107
CC608Decoder::m_xdsProgramName
QString m_xdsProgramName[2]
Definition: cc608decoder.h:140
CC608Decoder::m_txtMode
int m_txtMode[4]
Definition: cc608decoder.h:98
CC608Decoder::XDSPacketParseChannel
bool XDSPacketParseChannel(const vector< unsigned char > &xds_buf)
Definition: cc608decoder.cpp:1406
CC608Decoder::m_lastClr
int m_lastClr[8]
Definition: cc608decoder.h:112
CC608Decoder::m_badVbi
int m_badVbi[2]
Definition: cc608decoder.h:92
CC_MODE_MASK
#define CC_MODE_MASK
Definition: format.h:196
mythcontext.h
subtitles
const std::array< const std::string, 4 > subtitles
Definition: vbilut.cpp:201
CC608Decoder::m_xdsRatingSystems
uint m_xdsRatingSystems[2]
Definition: cc608decoder.h:138
CC_CC2
#define CC_CC2
Definition: format.h:199
cc::code1
int code1
Definition: cc.h:13
CC608Decoder::m_style
int m_style[8]
Definition: cc608decoder.h:109
CC608Decoder::DecodeVPS
void DecodeVPS(const unsigned char *buf)
Definition: cc608decoder.cpp:876
CC608Decoder::GetServices
void GetServices(uint seconds, bool seen[4]) const
Definition: cc608decoder.cpp:55
CC608Decoder::GetXDS
QString GetXDS(const QString &key) const
Definition: cc608decoder.cpp:1070
CC608Decoder::m_rowCount
int m_rowCount[8]
Definition: cc608decoder.h:108
kRatingCanFrench
@ kRatingCanFrench
Definition: cc608decoder.h:38
CC608Decoder::m_newRow
int m_newRow[8]
Definition: cc608decoder.h:102
CC608Decoder::XDSPacketCRC
bool XDSPacketCRC(const vector< unsigned char > &xds_buf)
Definition: cc608decoder.cpp:1224
CC608Decoder::m_resumeText
int m_resumeText[8]
Definition: cc608decoder.h:111
CC608Decoder::m_lastFormatData
int m_lastFormatData[2]
Definition: cc608decoder.h:121
ccsubtitle
Definition: format.h:180
CC608Decoder::BufferCC
void BufferCC(int mode, int len, int clr)
Definition: cc608decoder.cpp:664
kRatingCanEnglish
@ kRatingCanEnglish
Definition: cc608decoder.h:37
CC608Decoder::m_lastFormatTc
int m_lastFormatTc[2]
Definition: cc608decoder.h:120
kHasCanFrench
@ kHasCanFrench
Definition: cc608decoder.h:31
CC608Decoder::m_lastCodeTc
int m_lastCodeTc[2]
Definition: cc608decoder.h:95
CC608Decoder::FormatCCField
void FormatCCField(int tc, int field, int data)
Definition: cc608decoder.cpp:101
CC608Decoder::m_wssFlags
uint m_wssFlags
Definition: cc608decoder.h:129
CC608Decoder::CC608Decoder
CC608Decoder(CC608Input *ccr)
Definition: cc608decoder.cpp:23
specialchar
static const std::array< const QChar, 16 > specialchar
Definition: cc608decoder.cpp:69
kRatingMPAA
@ kRatingMPAA
Definition: cc608decoder.h:35
CC608Decoder::m_xdsProgramTypeString
QString m_xdsProgramTypeString[96]
Definition: cc608decoder.h:147
CC608Decoder::GetProgramType
QString GetProgramType(bool future) const
Definition: cc608decoder.cpp:1054
CC_STYLE_PAINT
#define CC_STYLE_PAINT
Definition: format.h:191
CC608Decoder::m_stdChar
QChar m_stdChar[128]
Definition: cc608decoder.h:116
CC608Decoder::NewRowCC
int NewRowCC(int mode, int len)
Definition: cc608decoder.cpp:726
CC608Decoder::m_xdsRating
uint m_xdsRating[2][4]
Definition: cc608decoder.h:139
extendedchar2
static const std::array< const QChar, 32 > extendedchar2
Definition: cc608decoder.cpp:77