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